Ver código fonte

我的入室添加入室预约

xukai 5 meses atrás
pai
commit
14e7e4513b

+ 8 - 6
.prettierrc

@@ -1,16 +1,18 @@
 {
-  "printWidth": 100,
+  "printWidth": 120,
   "tabWidth": 2,
   "useTabs": false,
-  "semi": true,
+  "semi": false,
   "singleQuote": true,
   "quoteProps": "as-needed",
   "jsxSingleQuote": true,
-  "trailingComma": "es5",
+  "trailingComma": "all",
   "bracketSpacing": true,
   "bracketSameLine": false,
-  "arrowParens": "avoid",
-  "htmlWhitespaceSensitivity": "css",
+  "arrowParens": "always",
+  "htmlWhitespaceSensitivity": "ignore",
   "vueIndentScriptAndStyle": true,
-  "endOfLine": "lf"
+  "endOfLine": "lf",
+  "embeddedLanguageFormatting": "auto",
+  "singleAttributePerLine": true
 }

+ 7 - 0
package.json

@@ -10,6 +10,11 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
+    "@fullcalendar/core": "^6.1.17",
+    "@fullcalendar/daygrid": "^6.1.17",
+    "@fullcalendar/interaction": "^6.1.17",
+    "@fullcalendar/timegrid": "^6.1.17",
+    "@fullcalendar/vue3": "^6.1.17",
     "@vitejs/plugin-basic-ssl": "^2.0.0",
     "await-to-js": "^3.0.0",
     "axios": "^1.8.2",
@@ -17,6 +22,8 @@
     "dayjs": "^1.11.13",
     "downloadjs": "^1.4.7",
     "element-plus": "^2.9.8",
+    "lodash": "^4.17.21",
+    "mitt": "^3.0.1",
     "moment": "^2.29.4",
     "pinia": "^3.0.1",
     "postcss-px-to-viewport": "^1.1.1",

+ 70 - 4
pnpm-lock.yaml

@@ -1,13 +1,24 @@
 lockfileVersion: '6.0'
 
-settings:
-  autoInstallPeers: false
-  excludeLinksFromLockfile: false
-
 dependencies:
   '@element-plus/icons-vue':
     specifier: ^2.3.1
     version: 2.3.1(vue@3.5.13)
+  '@fullcalendar/core':
+    specifier: ^6.1.17
+    version: 6.1.17
+  '@fullcalendar/daygrid':
+    specifier: ^6.1.17
+    version: 6.1.17(@fullcalendar/core@6.1.17)
+  '@fullcalendar/interaction':
+    specifier: ^6.1.17
+    version: 6.1.17(@fullcalendar/core@6.1.17)
+  '@fullcalendar/timegrid':
+    specifier: ^6.1.17
+    version: 6.1.17(@fullcalendar/core@6.1.17)
+  '@fullcalendar/vue3':
+    specifier: ^6.1.17
+    version: 6.1.17(@fullcalendar/core@6.1.17)(vue@3.5.13)
   '@vitejs/plugin-basic-ssl':
     specifier: ^2.0.0
     version: 2.0.0(vite@4.5.9)
@@ -29,6 +40,12 @@ dependencies:
   element-plus:
     specifier: ^2.9.8
     version: 2.9.8(vue@3.5.13)
+  lodash:
+    specifier: ^4.17.21
+    version: 4.17.21
+  mitt:
+    specifier: ^3.0.1
+    version: 3.0.1
   moment:
     specifier: ^2.29.4
     version: 2.29.4
@@ -314,6 +331,47 @@ packages:
     resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
     dev: false
 
+  /@fullcalendar/core@6.1.17:
+    resolution: {integrity: sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==}
+    dependencies:
+      preact: 10.12.1
+    dev: false
+
+  /@fullcalendar/daygrid@6.1.17(@fullcalendar/core@6.1.17):
+    resolution: {integrity: sha512-K7m+pd7oVJ9fW4h7CLDdDGJbc9szJ1xDU1DZ2ag+7oOo1aCNLv44CehzkkknM6r8EYlOOhgaelxQpKAI4glj7A==}
+    peerDependencies:
+      '@fullcalendar/core': ~6.1.17
+    dependencies:
+      '@fullcalendar/core': 6.1.17
+    dev: false
+
+  /@fullcalendar/interaction@6.1.17(@fullcalendar/core@6.1.17):
+    resolution: {integrity: sha512-AudvQvgmJP2FU89wpSulUUjeWv24SuyCx8FzH2WIPVaYg+vDGGYarI7K6PcM3TH7B/CyaBjm5Rqw9lXgnwt5YA==}
+    peerDependencies:
+      '@fullcalendar/core': ~6.1.17
+    dependencies:
+      '@fullcalendar/core': 6.1.17
+    dev: false
+
+  /@fullcalendar/timegrid@6.1.17(@fullcalendar/core@6.1.17):
+    resolution: {integrity: sha512-K4PlA3L3lclLOs3IX8cvddeiJI9ZVMD7RA9IqaWwbvac771971foc9tFze9YY+Pqesf6S+vhS2dWtEVlERaGlQ==}
+    peerDependencies:
+      '@fullcalendar/core': ~6.1.17
+    dependencies:
+      '@fullcalendar/core': 6.1.17
+      '@fullcalendar/daygrid': 6.1.17(@fullcalendar/core@6.1.17)
+    dev: false
+
+  /@fullcalendar/vue3@6.1.17(@fullcalendar/core@6.1.17)(vue@3.5.13):
+    resolution: {integrity: sha512-0+qi/PK/xCkTQXh2CaeN2AkP+SvQTznPhwBXuIyrtsR0Ua8UpmGEXI+my3qQ6o003r8gPUY2e785ywIyhX2zCA==}
+    peerDependencies:
+      '@fullcalendar/core': ~6.1.17
+      vue: ^3.0.11
+    dependencies:
+      '@fullcalendar/core': 6.1.17
+      vue: 3.5.13(typescript@4.9.5)
+    dev: false
+
   /@jridgewell/sourcemap-codec@1.5.0:
     resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
 
@@ -1254,6 +1312,10 @@ packages:
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
+  /preact@10.12.1:
+    resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
+    dev: false
+
   /proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
     dev: false
@@ -1498,3 +1560,7 @@ packages:
   /weixin-js-sdk@1.6.5:
     resolution: {integrity: sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ==}
     dev: false
+
+settings:
+  autoInstallPeers: false
+  excludeLinksFromLockfile: false

+ 27 - 0
src/api/platform/home/appointment.ts

@@ -0,0 +1,27 @@
+import request from '/@/utils/micro_request.js'
+const basePath = import.meta.env.VITE_PLATFORM_API
+export function useAppointmentApi() {
+  return {
+    getList(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'GetList', params)
+    },
+    getDetail(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'GetEntityById', params)
+    },
+    create(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'Create', params)
+    },
+    updateById(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'UpdateById', params)
+    },
+    deleteById(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'DeleteById', params)
+    },
+    getAppointInfo(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'AppointInfo', params)
+    },
+    cancelAppointment(params?: Object) {
+      return request.postRequest(basePath, 'PlatformAppointment', 'CancelAppointment', params)
+    },
+  }
+}

+ 39 - 0
src/api/platform/home/renewal.ts

@@ -0,0 +1,39 @@
+import request from '/@/utils/micro_request.js';
+const basePath = import.meta.env.VITE_PLATFORM_API;
+export function usePlatformRenewalApi() {
+    // 平台入室续期
+    return {
+        // 列表
+        getList(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'GetList', params);
+        },
+        // 详情
+        getDetail(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'GetEntityById', params);
+        },
+        // 创建
+        create(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'Create', params);
+        },
+        // 更新
+        update(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'UpdateById', params);
+        },
+        updateFileById(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'UpdateFileById', params);
+        },
+        reviewFile(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'ReviewFile', params);
+        },
+        // 删除
+        deleteById(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'DeleteById', params);
+        },
+        approve(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'Approve', params);
+        },
+        quashApprove(params?: Object) {
+            return request.postRequest(basePath, 'PlatPlatformRenewal', 'QuashApprove', params);
+        },
+    };
+}

+ 38 - 0
src/types/mitt.d.ts

@@ -0,0 +1,38 @@
+/**
+ * mitt 事件类型定义
+ *
+ * @method openSetingsDrawer 打开布局设置弹窗
+ * @method restoreDefault 分栏布局,鼠标移入、移出数据显示
+ * @method setSendColumnsChildren 分栏布局,鼠标移入、移出菜单数据传入到 navMenu 下的菜单中
+ * @method setSendClassicChildren 经典布局,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
+ * @method getBreadcrumbIndexSetFilterRoutes 布局设置弹窗,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
+ * @method layoutMobileResize 浏览器窗口改变时,用于适配移动端界面显示
+ * @method openOrCloseSortable 布局设置弹窗,开启 TagsView 拖拽
+ * @method openShareTagsView 布局设置弹窗,开启 TagsView 共用
+ * @method onTagsViewRefreshRouterView tagsview 刷新界面
+ * @method onCurrentContextmenuClick tagsview 右键菜单每项点击时
+ */
+declare type MittType<T = any> = {
+	openSetingsDrawer?: string;
+	restoreDefault?: string;
+	setSendColumnsChildren: T;
+	setSendClassicChildren: T;
+	getBreadcrumbIndexSetFilterRoutes?: string;
+	layoutMobileResize: T;
+	openOrCloseSortable?: string;
+	openShareTagsView?: string;
+	onTagsViewRefreshRouterView?: T;
+	onCurrentContextmenuClick?: T;
+};
+
+// mitt 参数类型定义
+declare type LayoutMobileResize = {
+	layout: string;
+	clientWidth: number;
+};
+
+// mitt 参数菜单类型
+declare type MittMenu = {
+	children: RouteRecordRaw[];
+	item?: RouteItem;
+};

+ 548 - 0
src/types/views.d.ts

@@ -0,0 +1,548 @@
+/**
+ * views personal
+ */
+type NewInfo = {
+  title: string;
+  date: string;
+  link: string;
+};
+type Recommend = {
+  title: string;
+  msg: string;
+  icon: string;
+  bg: string;
+  iconColor: string;
+};
+declare type PersonalState = {
+  newsInfoList: NewInfo[];
+  recommendList: Recommend[];
+  personalForm: {
+    name: string;
+    email: string;
+    autograph: string;
+    occupation: string;
+    phone: string;
+    sex: string;
+  };
+};
+
+/**
+ * views visualizing
+ */
+declare type Demo2State<T = any> = {
+  time: {
+    txt: string;
+    fun: number;
+  };
+  dropdownList: T[];
+  dropdownActive: string;
+  skyList: T[];
+  dBtnList: T[];
+  chartData4Index: number;
+  dBtnActive: number;
+  earth3DBtnList: T[];
+  chartData4List: T[];
+  myCharts: T[];
+};
+
+/**
+ * views params
+ */
+declare type ParamsState = {
+  value: string;
+  tagsViewName: string;
+  tagsViewNameIsI18n: boolean;
+};
+
+/**
+ * views system
+ */
+// role
+declare interface RowRoleType {
+  id?: number;
+  roleName: string;
+  roleCode: string;
+  sort: number;
+  status: string;
+  createTime?: string;
+  dataScope?: string;
+  menuIds: number[];
+}
+
+interface SysRoleTableType extends TableType {
+  data: RowRoleType[];
+}
+
+declare interface SysRoleState {
+  tableData: SysRoleTableType;
+}
+
+declare type TreeType = {
+  id: number;
+  label: string;
+  children?: TreeType[];
+};
+
+// user
+declare type RowUserType = {
+  id?: number;
+  userName: string;
+  nickName?: string;
+  userType?: string;
+  password: string;
+  confirmPassword?: string;
+  deptId?: null | number;
+  deptName?: string;
+  roleIds?: number[];
+  postIds?: number[];
+  groupIds?: number[];
+  phone?: string;
+  email?: string;
+  sex?: string;
+  status?: string;
+  avatar?: string;
+  idType?: string; //证件类型
+  idCode?: string; // 证件号
+  personnelType?: string; // 人员类型
+};
+
+interface SysUserTableType extends TableType {
+  data: RowUserType[];
+}
+
+declare interface SysUserState {
+  tableData: SysUserTableType;
+}
+
+declare type DeptTreeType = {
+  deptName: string;
+  createTime: string;
+  status: string;
+  sort: number;
+  id: number | string;
+  parentId: number;
+  leader: string;
+  phone: string;
+  hasChildren: boolean;
+  children?: DeptTreeType[];
+};
+
+// dept
+declare interface RowDeptType extends DeptTreeType {
+  deptLevel: string[];
+  phone: string;
+  person: string;
+  email: string;
+}
+
+interface SysDeptTableType extends TableType {
+  data: DeptTreeType[];
+}
+
+declare interface SysDeptState {
+  tableData: SysDeptTableType;
+}
+
+// dic
+type ListType = {
+  id: number;
+  label: string;
+  value: string;
+};
+
+declare interface RowConType {
+  id?: number;
+  configName: string;
+  configKey: string;
+  configType: string;
+  configValue: string;
+}
+interface SysConTableType extends TableType {
+  data: RowConType[];
+}
+
+declare interface SysConState {
+  tableData: SysConTableType;
+}
+declare interface RowDicType {
+  id?: number;
+  dictName: string;
+  dictType: string;
+  remark: string;
+  status: string;
+}
+interface SysDicTableType extends TableType {
+  data: RowDicType[];
+}
+
+declare interface SysDicState {
+  tableData: SysDicTableType;
+}
+declare interface RowDicDataType {
+  id?: number;
+  dictType: string;
+  dictLabel: string;
+  dictValue: string;
+  status: string;
+  isDefault: string;
+  sort: number;
+}
+interface SysDicDataTableType extends TableType {
+  data: RowDicDataType[];
+}
+declare interface SysDicDataState {
+  tableData: SysDicDataTableType;
+}
+declare interface RowPlatType {
+  id?: number;
+  licenseStatus: string;
+  licenseVersion: string;
+  platCode: string;
+  platLogo: string;
+  platName: string;
+  sort: number;
+}
+declare interface PlatInfoState {
+  platList: RowPlatType[];
+}
+interface SysPlatTableType extends TableType {
+  data: RowPlatType[];
+}
+declare interface SysPlatState {
+  tableData: SysPlatTableType;
+}
+declare interface RowNoticeType {
+  id?: number;
+  noticeContent: string;
+  noticeLevel: string;
+  noticeStatus: string;
+  noticeTitle: string;
+  noticeType: string;
+  platId: string;
+  platName: string;
+  imageUrl: string;
+  redirectUrl: string;
+  createdTime?: string;
+  isRead?: string;
+  readTime?: string;
+  isTop?: string;
+}
+interface SysNoticeTableType extends TableType {
+  data: RowNoticeType[];
+}
+declare interface SysNoticeState {
+  tableData: SysNoticeTableType;
+}
+declare interface RowMsgTmplType {
+  id?: number;
+  tempName: string;
+  tempCode: string;
+  tempFormat: string;
+  displayType: string;
+}
+interface SysMsgTmplTableType extends TableType {
+  data: RowMsgTmplType[];
+}
+declare interface SysMsgTmplState {
+  tableData: SysMsgTmplTableType;
+}
+declare interface RowMsgType {
+  id?: number;
+  msgTitle: string;
+  msgType: string;
+  msgContent: string;
+  displayType: string;
+  targetOrgIds: string;
+  targetRoleIds: string;
+  targetUserIds: string;
+  createdTime: string;
+  isRead?: string;
+}
+interface SysMsgTableType extends TableType {
+  data: RowMsgType[];
+}
+declare interface SysMsgState {
+  tableData: SysMsgTableType;
+}
+declare interface RowLogType {
+  id?: number;
+  msgTitle: string;
+  msgType: string;
+  msgContent: string;
+  displayType: string;
+  targetOrgIds: string;
+  targetRoleIds: string;
+  targetUserIds: string;
+}
+interface SysLogTableType extends TableType {
+  data: RowLogType[];
+}
+declare interface SysLogState {
+  tableData: SysLogTableType;
+}
+declare interface RowScedType {
+  id?: number;
+  schContent: string;
+  schRemindTime?: string;
+  schRemindUtil?: string;
+  schTitle: string;
+  schType: string;
+  userIds: number[];
+  schStartTime: string;
+  schEndTime: string;
+}
+interface SysScedTableType extends TableType {
+  data: RowScedType[];
+}
+declare interface SysScedState {
+  tableData: SysScedTableType;
+}
+
+/**
+ * views pages
+ */
+//  filtering
+declare type FilteringChilType = {
+  id: number | string;
+  label: string;
+  active: boolean;
+};
+
+declare type FilterListType = {
+  img: string;
+  title: string;
+  evaluate: string;
+  collection: string;
+  price: string;
+  monSales: string;
+  id: number | string;
+  loading?: boolean;
+};
+
+declare type FilteringRowType = {
+  title: string;
+  isMore: boolean;
+  isShowMore: boolean;
+  id: number | string;
+  children: FilteringChilType[];
+};
+
+// tableRules
+declare type TableRulesHeaderType = {
+  prop: string;
+  width: string | number;
+  label: string;
+  isRequired?: boolean;
+  isTooltip?: boolean;
+  type: string;
+};
+
+declare type TableRulesState = {
+  tableData: {
+    data: EmptyObjectType[];
+    header: TableRulesHeaderType[];
+    option: SelectOptionType[];
+  };
+};
+
+declare type TableRulesOneProps = {
+  name: string;
+  email: string;
+  autograph: string;
+  occupation: string;
+};
+
+// tree
+declare type RowTreeType = {
+  id: number;
+  label: string;
+  label1: string;
+  label2: string;
+  isShow: boolean;
+  children?: RowTreeType[];
+};
+
+// workflow index
+declare type NodeListState = {
+  id: string | number;
+  nodeId: string | undefined;
+  class: HTMLElement | string;
+  left: number | string;
+  top: number | string;
+  icon: string;
+  name: string;
+};
+
+declare type LineListState = {
+  sourceId: string;
+  targetId: string;
+  label: string;
+};
+
+declare type XyState = {
+  x: string | number;
+  y: string | number;
+};
+
+declare type WorkflowState<T = any> = {
+  leftNavList: T[];
+  dropdownNode: XyState;
+  dropdownLine: XyState;
+  isShow: boolean;
+  jsPlumb: T;
+  jsPlumbNodeIndex: null | number;
+  jsplumbDefaults: T;
+  jsplumbMakeSource: T;
+  jsplumbMakeTarget: T;
+  jsplumbConnect: T;
+  jsplumbData: {
+    nodeList: NodeListState[];
+    lineList: LineListState[];
+  };
+};
+
+// workflow drawer
+declare type WorkflowDrawerNodeState<T = any> = {
+  node: { [key: string]: T };
+  nodeRules: T;
+  form: T;
+  tabsActive: string;
+  loading: {
+    extend: boolean;
+  };
+};
+
+declare type WorkflowDrawerLabelType = {
+  type: string;
+  label: string;
+};
+
+declare type WorkflowDrawerState<T = any> = {
+  isOpen: boolean;
+  nodeData: {
+    type: string;
+  };
+  jsplumbConn: T;
+};
+
+/**
+ * views make
+ */
+// tableDemo
+declare type TableDemoPageType = {
+  pageNum: number;
+  pageSize: number;
+};
+
+declare type TableHeaderType = {
+  key: string;
+  width: string;
+  title: string;
+  type: string | number;
+  colWidth: string;
+  width?: string | number;
+  height?: string | number;
+  isCheck: boolean;
+};
+
+declare type TableSearchType = {
+  label: string;
+  prop: string;
+  placeholder: string;
+  required: boolean;
+  type: string;
+  options?: SelectOptionType[];
+};
+
+declare type TableDemoState = {
+  tableData: {
+    data: EmptyObjectType[];
+    header: TableHeaderType[];
+    config: {
+      total: number;
+      loading: boolean;
+      isBorder: boolean;
+      isSelection: boolean;
+      isSerialNo: boolean;
+      isOperate: boolean;
+    };
+    search: TableSearchType[];
+    param: EmptyObjectType;
+    printName: string;
+  };
+};
+declare type RowProType = {
+  id?: number;
+  pgCode: string;
+  pgDiscipline: string;
+  pgLeaderContact: string;
+  leader?: object;
+  pgLeaderId: number | null;
+  pgLeaderName: string;
+  pgName: string;
+  pgOrg?: number | null;
+  pgOrgPaths: string;
+  pgType: string;
+  pgUseArea: string;
+  remark?: string;
+  activation: number;
+  notActivated: number;
+  pgStartDate: string;
+  pgEndDate: string;
+};
+declare type RowProMemType = {
+  id?: number;
+  isExternal: string;
+  memAddress: string;
+  memCode: string;
+  memEndDate: string;
+  memIcCode: string;
+  member: object;
+  memId: number | null;
+  memMail: string;
+  memName: string;
+  memPhone: string;
+  memSex: string;
+  memSpeciality: string;
+  memStartDate: string;
+  memDate?: string[];
+  memType: string;
+  memUnitName: string;
+  pgId: number | null;
+};
+
+declare type ProInfoType = {
+  projName?: string;
+  finAvailBalance?: string; // 可用余
+  finCreditLimit?: string; // 授信
+  finUnpaidBill?: string; // 未出账单
+};
+
+declare interface RowFlowType {
+  id: number;
+  flowName: string;
+  flowCode: string;
+  flowGroup: string;
+  remark: string;
+}
+
+declare interface QueueItemType {
+  id: number;
+  endTime: string;
+  score: number;
+  startTime: string;
+  updatedTime: string;
+  userId: number;
+  userName: string;
+}
+
+declare interface ResourceItemType {
+  visible: Boolean;
+  detail: any;
+  resClass: string;
+  resId: number;
+  resLocation: string;
+  resMaxNum: number;
+  resName: string;
+  resStatus: string;
+}  

+ 8 - 0
src/utils/mitt.ts

@@ -0,0 +1,8 @@
+// https://www.npmjs.com/package/mitt
+import mitt, { Emitter } from 'mitt';
+
+// 类型
+const emitter: Emitter<MittType> = mitt<MittType>();
+
+// 导出
+export default emitter;

+ 840 - 0
src/view/entry/components/appoint.vue

@@ -0,0 +1,840 @@
+<template>
+  <div v-if="visible">
+    <el-dialog
+      :title="title"
+      v-model="visible"
+      :close-on-click-modal="false"
+      @close="dialogClose"
+      width="90%"
+    >
+      <div>
+        <FullCalendar
+          ref="fullCalendar"
+          :options="calendarOptions"
+        >
+          <template v-slot:eventContent="arg">
+            <div
+              style="height: 100%; width: 100%"
+              :title="showTitlt(arg.event)"
+            >
+              <div
+                v-if="arg.event.extendedProps.appointStatus && arg.event.extendedProps.appointStatus != 999"
+                style="color: #ffffff; margin: 0px"
+              >
+                <div class="appoint-details">
+                  {{ arg.event.extendedProps.userName }}{{ '--' + arg.event.extendedProps.tel }}
+                </div>
+                <div
+                  class="appoint-details"
+                  v-if="showAllInfo(arg.event, 2)"
+                >
+                  {{
+                    parseTime(arg.event.start, '{m}月{d}日') == parseTime(arg.event.end, '{m}月{d}日')
+                      ? parseTime(arg.event.start, '{h}:{i}')
+                      : parseTime(arg.event.start, '{m}月{d}日 {h}:{i}')
+                  }}
+                  -
+                  {{
+                    parseTime(arg.event.end, '{m}月{d}日') == parseTime(arg.event.start, '{m}月{d}日')
+                      ? parseTime(arg.event.end, '{h}:{i}')
+                      : parseTime(arg.event.end, '{m}月{d}日 {h}:{i}')
+                  }}
+                </div>
+                <div
+                  class="appoint-details"
+                  v-if="showAllInfo(arg.event, 3)"
+                >
+                  {{ arg.event.extendedProps.platformName }}-{{ arg.event.extendedProps.resName }}
+                </div>
+              </div>
+              <div
+                v-else-if="arg.event.extendedProps.appointStatus && arg.event.extendedProps.appointStatus == 999"
+                style="color: #61616e; margin: 0px"
+              >
+                不允许预约
+              </div>
+              <div
+                v-else
+                style="color: #ffffff; margin: 0px"
+              >
+                {{
+                  parseTime(arg.event.start, '{m}月{d}日') == parseTime(arg.event.end, '{m}月{d}日')
+                    ? parseTime(arg.event.start, '{h}:{i}')
+                    : parseTime(arg.event.start, '{m}月{d}日 {h}:{i}')
+                }}
+                -
+                {{
+                  parseTime(arg.event.end, '{m}月{d}日') == parseTime(arg.event.start, '{m}月{d}日')
+                    ? parseTime(arg.event.end, '{h}:{i}')
+                    : parseTime(arg.event.end, '{m}月{d}日 {h}:{i}')
+                }}
+              </div>
+            </div>
+          </template>
+        </FullCalendar>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button
+            @click="dialogClose()"
+            size="default"
+          >
+            取 消
+          </el-button>
+          <el-button
+            :disabled="!form.startTime"
+            type="primary"
+            size="default"
+            @click="nextStep"
+          >
+            确认
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <create-sub
+      ref="sub"
+      @refresh="emitData"
+    ></create-sub>
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue'
+  import to from 'await-to-js'
+  import _ from 'lodash'
+  import { ElMessage } from 'element-plus'
+  import moment from 'moment'
+  import FullCalendar from '@fullcalendar/vue3'
+  import dayGridPlugin from '@fullcalendar/daygrid'
+  import interactionPlugin from '@fullcalendar/interaction'
+  import timeGridPlugin from '@fullcalendar/timegrid'
+
+  import CreateSub from './edit.vue'
+  import { useAppointmentApi } from '/@/api/platform/home/appointment'
+  import mittBus from '/@/utils/mitt'
+
+  const useAppointApi = useAppointmentApi()
+  export default defineComponent({
+    name: 'FrontendWebTest',
+    components: {
+      FullCalendar,
+      CreateSub,
+    },
+    data() {
+      return {
+        title: '',
+        visible: false,
+        form: {
+          id: 0,
+          appointId: 0,
+          mainId: 0,
+          platformId: 0,
+          platformName: '',
+          resId: 0,
+          resLocation: '',
+          resName: '',
+          userId: 0,
+          userName: '',
+          assignStatus: '',
+          startTime: '',
+          endTime: '',
+          oldStartTime: '',
+          oldEndTime: '',
+          isConfirm: '',
+          location: 0,
+          outApplyTime: '',
+          outStatus: 0,
+          outTime: null,
+        },
+        show: false,
+        split_time: 0,
+        calendarOptions: {
+          plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
+          initialView: 'timeGridWeek', // 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay)
+          validRange: this.setValidRange, //日期有效范围
+          allDaySlot: false, //是否显示日历上方的allDay
+          dayMaxEvents: true, // allow "more" link when too many events,只能选中或拖动一次
+          firstDay: 1, //new Date().getDay(), // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推  new Date().getDay()当前天
+          locale: 'zh-cn', // 切换语言,当前为中文
+          unselectAuto: false, //当点击页⾯⽇历以外的位置时,是否⾃动取消当前的选中状态。false是不取消
+          customButtons: {
+            prev: {
+              click: () => {
+                this.prevWeekClick()
+              },
+            },
+            next: {
+              click: () => {
+                this.nextWeekClick()
+              },
+            },
+            today: {
+              text: '本周',
+              click: () => {
+                this.todayWeekClick()
+              },
+            },
+          },
+          height: 600,
+          dragScroll: true,
+          headerToolbar: {
+            left: null,
+            center: 'prev,title,next,today',
+            right: null,
+          },
+          events: [], //事件事件+文本
+          slotLabelFormat: {
+            hour: '2-digit',
+            minute: '2-digit',
+            meridiem: false,
+            hour12: false, // 设置时间为24小时
+          },
+          editable: false,
+          selectable: true,
+          eventConstraint: {
+            // 设置可以拖放的时间段为当下到100年后
+            start: new Date(), // 只允许选择当前时间之后的时间段
+          },
+          selectConstraint: {
+            start: new Date(),
+            end: '',
+          },
+          selectOverlap: false, //选择重叠
+          businessHours: [],
+          selectMirror: true,
+          weekends: true,
+          select: this.handleDateSelect, //当用户拖拽日期或时间时传递的参数
+        },
+        furtherLimit: '',
+        setValidRange: {
+          start: '',
+          end: '',
+        },
+      }
+    },
+    methods: {
+      emitData() {
+        mittBus.emit('refreshAppointData', 'appoint')
+        this.dialogClose()
+      },
+      // 打开设备预约
+      async openDialog(row) {
+        this.$nextTick(async () => {
+          this.form = row
+          this.title = row.userName + '正在发起对' + row.platformName + '-' + row.resName + '的预约申请'
+          this.visible = true
+          // const params = {
+          // 	instId: row.id,
+          // 	code: 'InstCfgAppoint',
+          // };
+          // const [err, res]: ToResponse = await to(setInstApi.getSettingDetail({ ...params }));
+          // if (err) return;
+          // if (res?.code == 200) {
+          // 	this.show = true;
+          // 	this.split_time = res.data.config.timeSplit;
+          // 	this.form.createForm = res.data.config.createForm;
+          // 	let calendarFunc = this.$refs['fullCalendar'].getApi().view.calendar;
+          // 	calendarFunc.setOption('slotDuration', this.setSplitTime(this.split_time));
+          // 	this.getAppointData();
+          // }
+          // this.initData();
+          // await this.getAppointData();
+          setTimeout(() => {
+            let calendarFunc = this.$refs['fullCalendar'].getApi().view.calendar
+            calendarFunc.setOption('slotDuration', this.setSplitTime(30))
+            this.getAppointData()
+          })
+        })
+      },
+      // 设置一格时间槽代表多长时间
+      setSplitTime(time: number) {
+        let split_time = ''
+        if (time < 60) {
+          split_time = '00:' + (time < 10 ? '0' + time : time) + ':00'
+        } else if (time == 60) {
+          split_time = '01:00:00'
+        }
+        return split_time
+      },
+      // 预约信息
+      getAppointData() {
+        this.$nextTick(async () => {
+          const calendar = await this.$refs.fullCalendar.getApi() // 获取 FullCalendar 实例
+          let params = {
+            dateType: 'week',
+            id: this.form.id,
+            date: moment(calendar.getCurrentData().currentDate).format('YYYY-MM-DD'),
+          }
+          const [err, res]: ToResponse = await to(useAppointApi.getAppointInfo({ ...params }))
+          if (err) return
+          if (res?.code == 200) {
+            const { appoint, unavailable, furtherLimit } = res.data
+            // this.furtherLimit = furtherLimit ? moment(furtherLimit).format('YYYY-MM-DD HH:mm') : '';
+            let arr = [...(appoint || []), ...(unavailable || [])]
+            this.setBusinessHours(calendar)
+            this.initBoard(arr)
+            this.setValidRange = {
+              start: furtherLimit[0] ? moment(furtherLimit[0]).format('YYYY-MM-DD HH:mm') : '',
+              end: furtherLimit[1] ? moment(furtherLimit[1]).format('YYYY-MM-DD HH:mm') : '',
+            }
+            this.$nextTick(() => {
+              // this.calendarOptions.selectConstraint = {
+              // 	start: new Date(),
+              // 	end: this.furtherLimit ? moment(this.furtherLimit).format('YYYY-MM-DD HH:mm') : '',
+              // };
+            })
+          }
+        })
+      },
+      // 上周点击
+      prevWeekClick() {
+        let calendarApi = this.$refs.fullCalendar.getApi()
+        calendarApi.prev()
+        this.getAppointData()
+      },
+      // 下周点击
+      nextWeekClick() {
+        let calendarApi = this.$refs.fullCalendar.getApi()
+        calendarApi.next()
+        this.getAppointData()
+      },
+      // 今天点击
+      todayWeekClick() {
+        let calendarApi = this.$refs.fullCalendar.getApi()
+        calendarApi.today()
+        this.getAppointData()
+      },
+      /**
+       * 设置工作时间
+       * */
+      // 设置工作时间(可预约时间)
+      setBusinessHours(calendar) {
+        // 获取当前视图所显示的日期范围
+        const viewStart = calendar.view.activeStart
+        const viewEnd = calendar.view.activeEnd
+        // 获取今天的日期
+        const today = new Date()
+        // 判断今天是否在当前周里
+        if (today >= viewStart && today <= viewEnd) {
+          // 当前周
+          this.handleSetHours('currentWeek')
+        } else if (today < viewStart) {
+          // 未来的周
+          this.handleSetHours('featureWeek')
+        } else {
+          // 过去的周
+          this.handleSetHours('pastWeek')
+        }
+      },
+      // 设置不同周的工作时间
+      /**
+       * @param {*} weekType 周类型: 当前周 未来周 之前周
+       * @description 设置每周的可预约时间
+       * 优先预约权限的用户 过去周全天不可以预约、未来周全天可预约、当前周今天当前时间之前的时间不可预约
+       * 非优先预约权限的用户 过去周 全天不可以预约 、当前周(包括未来周)的过去时间不可以预约、现在及以后的时间根据提前预约时长设置限制是否可预约
+       * */
+      handleSetHours(weekType) {
+        let businessHours = <any>[]
+        // 获取当前时间
+        const now = moment()
+        // 根据当前天输出三个数组
+        const currentDay = now.day() //当前天 周四 [4]
+        const lodashRangeDay = currentDay === 0 ? 7 : now.day()
+        const pastDays = _.range(1, lodashRangeDay) //过去的时间 [1,2,3]
+        const futureDays = _.range(lodashRangeDay + 1, 8)
+          .concat()
+          .map((num) => (num === 7 ? 0 : num))
+        //未来的时间 [5,6,0]
+        const startTime = now.format('YYYY-MM-DD HH:mm')
+        const endTime = moment(this.furtherLimit).format('YYYY-MM-DD HH:mm') //当前时间增加1小时
+        // 仪器可预约时间为从当前时间开始(startTime)往后推到(endTime)
+        const businessDateRange = [startTime, endTime]
+        // 预约日期的周日期
+        const Monday = moment(this.$refs.fullCalendar.getApi().currentData.dateProfile.activeRange.start).format(
+          'YYYY-MM-DD',
+        )
+        // 根据当前预约日历的周获取周一
+        const Tuesday = moment(Monday).add(1, 'days').format('YYYY-MM-DD') // 周二日期
+
+        const Wednesday = moment(Monday).add(2, 'days').format('YYYY-MM-DD') // 周三日期
+        const Thursday = moment(Monday).add(3, 'days').format('YYYY-MM-DD') // 周四日期
+        const Friday = moment(Monday).add(4, 'days').format('YYYY-MM-DD') // 周五日期
+        const Saturday = moment(Monday).add(5, 'days').format('YYYY-MM-DD') // 周六日期
+        const Sunday = moment(Monday).add(6, 'days').format('YYYY-MM-DD') // 周日日期
+        const weeks = [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
+        if (weekType == 'currentWeek') {
+          if (this.furtherLimit) {
+            businessHours = this.calcBusinessList(weeks, businessDateRange)
+          } else {
+            businessHours = [
+              {
+                daysOfWeek: pastDays,
+                startTime: '00:00',
+                endTime: '00:00',
+              },
+              {
+                daysOfWeek: [currentDay],
+                startTime: this.getCurrentAppointDate().split(' ')[1],
+                endTime: '24:00',
+              },
+              {
+                daysOfWeek: futureDays,
+                startTime: '00:00',
+                endTime: '24:00',
+              },
+            ]
+          }
+        } else if (weekType == 'pastWeek') {
+          businessHours = [
+            {
+              daysOfWeek: [1, 2, 3, 4, 5, 6, 0],
+              startTime: '00:00',
+              endTime: '00:00',
+            },
+          ]
+        } else if (weekType == 'featureWeek') {
+          if (this.furtherLimit) {
+            businessHours = this.calcBusinessList(weeks, businessDateRange)
+          } else {
+            businessHours = [
+              {
+                daysOfWeek: [1, 2, 3, 4, 5, 6, 0],
+                startTime: '00:00',
+                endTime: '24:00',
+              },
+            ]
+          }
+        }
+        let calendarApi = this.$refs['fullCalendar'].getApi()
+        let calendarFunc = calendarApi.view.calendar
+        calendarFunc.setOption('businessHours', businessHours)
+      },
+      calcBusinessList(weeksRange, businessRange) {
+        const businessWorkList = <any>[]
+        // 预约时间的开始日期是否在当前周
+        const businessStartInCurrentWeek = moment(moment(businessRange[0]).format('YYYY-MM-DD')).isBetween(
+          moment(weeksRange[0]),
+          moment(weeksRange[6]),
+          null,
+          '[]',
+        )
+        // 预约时间的结束日期是否在当前周
+        const businessEndInCurrentWeek = moment(moment(businessRange[1]).format('YYYY-MM-DD')).isBetween(
+          moment(weeksRange[0]),
+          moment(weeksRange[6]),
+          null,
+          '[]',
+        )
+        // 预约时间的结束日期和开始日期是否为同一天
+        const businesStartSameEnd =
+          moment(businessRange[0]).format('YYYY-MM-DD') == moment(businessRange[1]).format('YYYY-MM-DD')
+        // 可预约开始时间在当前周
+        if (businessStartInCurrentWeek) {
+          /**
+           * 如果可预约的开始时间在当前周
+           * 首先找出是在哪天
+           * 其次找出结束时间是否在当前周,如果不在开始时间之前全部置灰、之后的不置灰
+           */
+          // 可预约结束时间在当前周
+          if (businessEndInCurrentWeek) {
+            // 可预约结束时间和开始时间是同一天
+            if (businesStartSameEnd) {
+              let startIndex = 0
+              weeksRange.map((item, index) => {
+                // 找出开始时间是当前周的哪一天
+                if (item == moment(businessRange[0]).format('YYYY-MM-DD')) {
+                  startIndex = index
+                  businessWorkList.push(
+                    // 开始之前
+                    {
+                      daysOfWeek: this.generateNumberArray(1, index),
+                      startTime: '00:00',
+                      endTime: '00:00',
+                    },
+                    // 开始当天
+                    {
+                      daysOfWeek: [index + 1],
+                      startTime: this.getCurrentAppointDate().split(' ')[1],
+                      endTime: moment(businessRange[1]).format('HH:mm'),
+                    },
+                  )
+                }
+              })
+              if (startIndex != 6) {
+                businessWorkList.push({
+                  daysOfWeek: this.generateNumberArray(startIndex + 1, 7),
+                  startTime: '00:00',
+                  endTime: '00:00',
+                })
+              }
+              // END
+            } else {
+              // 可预约结束时间和开始时间不是同一天
+              let startIndex = 0
+              let endIndex = 0
+              weeksRange.map((item, index) => {
+                // 找出开始时间是当前周的哪一天
+                if (item == moment(businessRange[0]).format('YYYY-MM-DD')) {
+                  startIndex = index
+                  businessWorkList.push(
+                    // 开始之前
+                    {
+                      daysOfWeek: this.generateNumberArray(1, index),
+                      startTime: '00:00',
+                      endTime: '00:00',
+                    },
+                    // 开始当天
+                    {
+                      daysOfWeek: [index + 1],
+                      startTime: this.getCurrentAppointDate().split(' ')[1],
+                      endTime: '24:00',
+                    },
+                  )
+                }
+              })
+              weeksRange.map((item, index) => {
+                // 找出开始时间是当前周的哪一天
+                if (item == moment(businessRange[1]).format('YYYY-MM-DD')) {
+                  endIndex = index
+                  businessWorkList.push(
+                    {
+                      daysOfWeek: this.generateRangeArray(startIndex + 1, endIndex + 1),
+                      startTime: '00:00',
+                      endTime: '24:00',
+                    },
+                    {
+                      daysOfWeek: [endIndex + 1],
+                      startTime: '00:00',
+                      endTime: moment(businessRange[1]).format('HH:mm'),
+                    },
+                  )
+                }
+              })
+              if (endIndex != 6) {
+                businessWorkList.push({
+                  daysOfWeek: this.generateNumberArray(endIndex + 2, 7),
+                  startTime: '00:00',
+                  endTime: '00:00',
+                })
+              }
+              // END
+            }
+            // END
+          } else if (!businessEndInCurrentWeek) {
+            // 可预约结束时间不在当前周
+            let startIndex = 0
+            weeksRange.map((item, index) => {
+              // 找出开始时间是当前周的哪一天
+              if (item == moment(businessRange[0]).format('YYYY-MM-DD')) {
+                startIndex = index + 1
+                businessWorkList.push(
+                  // 开始之前
+                  {
+                    daysOfWeek: this.generateNumberArray(1, index),
+                    startTime: '00:00',
+                    endTime: '00:00',
+                  },
+                  // 开始当天
+                  {
+                    daysOfWeek: [index + 1],
+                    startTime: this.getCurrentAppointDate().split(' ')[1],
+                    endTime: '24:00',
+                  },
+                )
+              }
+            })
+            if (startIndex != 6) {
+              businessWorkList.push({
+                daysOfWeek: this.generateNumberArray(startIndex + 1, 7),
+                startTime: '00:00',
+                endTime: '24:00',
+              })
+            }
+            // END
+          }
+        } else {
+          if (businessEndInCurrentWeek) {
+            // 结束时间在当前周
+            let endIndex = 0
+            weeksRange.map((item, index) => {
+              // 找出开始时间是当前周的哪一天
+              if (item == moment(businessRange[1]).format('YYYY-MM-DD')) {
+                endIndex = index + 1
+                businessWorkList.push(
+                  // 结束之前
+                  {
+                    daysOfWeek: this.generateNumberArray(1, index),
+                    startTime: '00:00',
+                    endTime: '24:00',
+                  },
+                  // 结束当天
+                  {
+                    daysOfWeek: [index + 1],
+                    startTime: '00:00',
+                    endTime: moment(businessRange[1]).format('HH:mm'),
+                  },
+                )
+              }
+            })
+            if (endIndex != 6) {
+              businessWorkList.push({
+                daysOfWeek: this.generateNumberArray(endIndex + 1, 7),
+                startTime: '00:00',
+                endTime: '00:00',
+              })
+            }
+            // END
+          } else {
+            return [
+              {
+                daysOfWeek: [1, 2, 3, 4, 5, 6, 0],
+                startTime: '00:00',
+                endTime: '00:00',
+              },
+            ]
+          }
+        }
+        const result = businessWorkList.filter((item) => item.daysOfWeek.length > 0)
+        return result
+      },
+      // 获取两个数字的区间数组
+      generateRangeArray(start, end) {
+        if (start > end) {
+          ;[start, end] = [end, start] // 如果开始数字大于结束数字,交换它们的值
+        }
+
+        const result = <any>[]
+        for (let i = start + 1; i < end; i++) {
+          result.push(i)
+        }
+        // 找到数字 7 的索引
+        const indexOfSeven = result.indexOf(7)
+        // 如果找到了数字 7 的索引,则替换为 0
+        if (indexOfSeven !== -1) {
+          result[indexOfSeven] = 0
+        }
+        return result
+      },
+      // 获取两个数字数组包括本身
+      generateNumberArray(start, end) {
+        const result = []
+        for (let i = start; i <= end; i++) {
+          result.push(i)
+        }
+        // 找到数字 7 的索引
+        const indexOfSeven = result.indexOf(7)
+
+        // 如果找到了数字 7 的索引,则替换为 0
+        if (indexOfSeven !== -1) {
+          result[indexOfSeven] = 0
+        }
+        return result
+      },
+      // 获取最近可预约时间段
+      getCurrentAppointDate() {
+        const interval = 30 // 时间间隔(单位:分钟)
+        const now = moment() // 当前时间
+
+        // 将当前时间向上取整到最近的 interval 的倍数
+        const roundedNow = moment(Math.ceil(now.valueOf() / (interval * 60 * 1000)) * interval * 60 * 1000)
+
+        // 如果当前时间已经超过了最近可预约时间,则将最近可预约时间增加一个 interval 的时间间隔
+        if (now.isAfter(roundedNow)) {
+          roundedNow.add(interval, 'minutes')
+        }
+
+        // 将最近可预约时间格式化为 HH:mm:ss 格式
+        const nearestAvailableTime = roundedNow.format('YYYY-MM-DD HH:mm')
+        return nearestAvailableTime
+      },
+      /**
+       * 设置工作时间相关代码 END
+       * */
+      // 更新预约内容视图
+      initBoard(datas: any) {
+        let data = datas
+        if (!data) return
+        let newD = data.map((item: any, index: number) => {
+          //根据返回的status判断当前是待审核  已预约的颜色
+          return {
+            id: index,
+            start: item.id ? item.startTime : item.start,
+            end: item.id ? item.endTime : item.end,
+            color: item.appointStatus == '10' ? 'skyblue' : !item.appointStatus ? '#ccc' : '',
+            platformName: item.platformName || '',
+            resName: item.resName || '',
+            tel: item.userContact || '',
+            appointStatus: item.appointStatus || 999,
+            userName: item.userName || '',
+          }
+        })
+        this.pushData(newD)
+      },
+      // 数据填充到日历
+      pushData(newD: any) {
+        let calendarApi = this.$refs['fullCalendar'].getApi()
+        let calendarFunc = calendarApi.view.calendar
+        calendarFunc.unselect()
+        let getEvents = calendarFunc.getEvents()
+        if (getEvents && getEvents.length > 0) {
+          //如果日历看板之前有数据,那么删除之前的数据
+          getEvents.map((item: any) => {
+            calendarFunc.getEventById(item.id).remove()
+          })
+        }
+        newD.map((item: any) => {
+          calendarFunc.addEvent(item) //数据填充到日历看板中
+        })
+        // this.$nextTick(() => {
+        // 	document.querySelector('.fc-scroller-liquid-absolute').scrollTop = 0;
+        // });
+      },
+      showTitlt(event: any) {
+        let str = `预约时间:   ${
+          this.parseTime(event.start, '{m}月{d}日') == this.parseTime(event.end, '{m}月{d}日')
+            ? this.parseTime(event.start, '{h}:{i}')
+            : this.parseTime(event.start, '{m}月{d}日 {h}:{i}')
+        }-${
+          this.parseTime(event.end, '{m}月{d}日') == this.parseTime(event.start, '{m}月{d}日')
+            ? this.parseTime(event.end, '{h}:{i}')
+            : this.parseTime(event.end, '{m}月{d}日 {h}:{i}')
+        }`
+        if (event.extendedProps.appointStatus && event.extendedProps.appointStatus != 999) {
+          str += `\n平台-资源:   ${event.extendedProps.platformName}-${event.extendedProps.resName}\n预约人:   ${event.extendedProps.userName}\n联系电话:   ${event.extendedProps.tel}`
+        }
+        return str
+      },
+      //判断是否显示全部内容
+      showAllInfo(ev: any, cnt: number) {
+        var start = new Date(ev.start)
+        var end = new Date(ev.end)
+        var minutesDiff = (end - start) / (1000 * 60) // 计算分钟差
+        var intervals = Math.floor(minutesDiff / this.split_time) // 计算间隔数量
+        if (cnt <= intervals) {
+          return true
+        } else {
+          return false
+        }
+      },
+      // 日期格式化
+      parseTime(time: number | string, pattern: string) {
+        if (arguments.length === 0 || !time) {
+          return null
+        }
+        const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+        let date
+        if (typeof time === 'object') {
+          date = time
+        } else {
+          if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
+            time = parseInt(time)
+          } else if (typeof time === 'string') {
+            time = time.replace(new RegExp(/-/gm), '/')
+          }
+          if (typeof time === 'number' && time.toString().length === 10) {
+            time = time * 1000
+          }
+          date = new Date(time)
+        }
+        const formatObj = {
+          y: date.getFullYear(),
+          m: date.getMonth() + 1,
+          d: date.getDate(),
+          h: date.getHours(),
+          i: date.getMinutes(),
+          s: date.getSeconds(),
+          a: date.getDay(),
+        }
+        const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+          let value = formatObj[key]
+          // Note: getDay() returns 0 on Sunday
+          if (key === 'a') {
+            return ['日', '一', '二', '三', '四', '五', '六'][value]
+          }
+          if (result.length > 0 && value < 10) {
+            value = '0' + value
+          }
+          return value || 0
+        })
+        return time_str
+      },
+      // 当选择结束的时候获取开始和结束时间
+      handleDateSelect(info: any) {
+        console.log(info.endStr)
+        this.form.startTime = moment(info.startStr).format('YYYY-MM-DD HH:mm:ss')
+        this.form.endTime = moment(info.endStr).format('YYYY-MM-DD HH:mm:ss')
+        // 计算endTime和startTime的差值
+        const diff = moment(info.endStr).diff(moment(info.startStr), 'minutes')
+        if (diff > 120) {
+          this.form.startTime = null
+          this.form.endTime = null
+          // 单次最多允许预约2小时
+          return ElMessage.error('单次最多允许预约2小时')
+        }
+      },
+      // 关闭
+      dialogClose() {
+        this.visible = false
+        this.form = { startTime: '', endTime: '', instId: 0, instName: '' }
+        this.show = false
+      },
+      // 预约下一步
+      nextStep() {
+        this.$refs.sub.openDialog('add', this.form)
+      },
+    },
+  })
+</script>
+
+<style lang="scss" scoped>
+  :deep(.fc-day-disabled) {
+    opacity: 0.5;
+    pointer-events: none;
+  }
+  :deep(.fc-cell-not-allowed) {
+    background-color: #eee;
+  }
+  :deep(.fc-toolbar-chunk) {
+    div {
+      display: flex;
+    }
+  }
+  :deep(.fc-toolbar-title) {
+    margin: 0;
+    text-align: center;
+  }
+  :deep(.borderBlue) {
+    border-left: 5px solid blue !important;
+    border-radius: 0;
+  }
+  :deep(.borderOrange) {
+    border-left: 5px solid yellow !important;
+    border-radius: 0;
+  }
+  :deep(.fc) {
+    .fc-day-today {
+      background: unset;
+    }
+  }
+  :deep(.fc-past-event) {
+    background-color: #e3e3e3;
+  }
+  :deep(.fc-today-button) {
+    margin-left: 10px;
+  }
+  :deep(.fc .fc-toolbar.fc-header-toolbar) {
+    margin-bottom: 20px;
+  }
+  :deep(.fc .fc-timegrid-slot) {
+    height: 2em;
+  }
+  :deep(.fc-event-main) {
+    overflow: hidden;
+  }
+  .appoint-details {
+    margin-left: 10px;
+    margin-bottom: 3px;
+    line-height: 2em;
+  }
+</style>

+ 340 - 0
src/view/entry/components/edit.vue

@@ -0,0 +1,340 @@
+<template>
+  <div class="facilities-dialog-container">
+    <el-dialog
+      :title="state.dialog.title"
+      @close="onCancel"
+      :close-on-click-modal="false"
+      v-model="state.dialog.isShowDialog"
+      width="800px"
+    >
+      <h4 class="mb8 mt8">申请信息</h4>
+      <el-descriptions
+        border
+        :column="2"
+        class="mb20"
+        direction="vertical"
+      >
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="入室申请名称"
+        >
+          {{ state.oldForm.userName }}的{{ state.oldForm.platformName }}的入室申请
+        </el-descriptions-item>
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="申请平台"
+          width="50%"
+        >
+          {{ state.oldForm.platformName }}
+        </el-descriptions-item>
+        <!--        <el-descriptions-item label-class-name="cell-item" label="申请时长">{{ state.oldForm.platformTime }}个月</el-descriptions-item>-->
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="入室周期"
+          width="50%"
+        >
+          {{ state.oldForm?.startTime ? formatDate(new Date(state.oldForm?.startTime), 'YYYY-mm-dd') : '' }} ~
+          {{ state.oldForm?.endTime ? formatDate(new Date(state.oldForm?.endTime), 'YYYY-mm-dd') : '' }}
+        </el-descriptions-item>
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="预约房间"
+          width="50%"
+        >
+          {{ state.oldForm.resName }}
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-form
+        ref="expertDialogFormRef"
+        :model="state.form"
+        :rules="rules"
+        size="default"
+        label-width="140px"
+        label-position="top"
+      >
+        <el-row :gutter="35">
+          <el-col
+            :span="12"
+            class="mb20"
+          >
+            <el-form-item
+              label="开始时间"
+              prop="startTime"
+            >
+              <el-date-picker
+                type="datetime"
+                placeholder="请输入"
+                disabled
+                class="w100"
+                v-model="state.form.startTime"
+                value-format="YYYY-MM-DD HH:mm"
+                format="YYYY/MM/DD HH:mm"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col
+            :span="12"
+            class="mb20"
+          >
+            <el-form-item
+              label="结束时间"
+              prop="endTime"
+            >
+              <el-date-picker
+                type="datetime"
+                placeholder="请输入"
+                disabled
+                class="w100"
+                v-model="state.form.endTime"
+                value-format="YYYY-MM-DD HH:mm"
+                format="YYYY/MM/DD HH:mm"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col
+            :span="12"
+            class="mb24"
+          >
+            <el-form-item
+              label="预约时长(分钟)"
+              prop="duration"
+            >
+              <el-input
+                disabled
+                placeholder="请输入"
+                class="w100"
+                maxlength="11"
+                v-model="state.form.duration"
+              />
+            </el-form-item>
+          </el-col>
+          <!--          <el-col :span="12" class="mb24">-->
+          <!--            <el-form-item label="预约人" prop="userId">-->
+          <!--              <el-select v-model="state.form.userId" class="w100" filterable placeholder="请选择" disabled @change="changeUser">-->
+          <!--                <el-option :value="v.id" :label="v.nickName" :key="v.id" v-for="v in userList" />-->
+          <!--              </el-select>-->
+          <!--            </el-form-item>-->
+          <!--          </el-col>-->
+          <!--          <el-col :span="12" class="mb24">-->
+          <!--            <el-form-item label="联系电话" prop="userContact">-->
+          <!--              <el-input placeholder="请输入" class="w100" maxlength="11" v-model="state.form.userContact" />-->
+          <!--            </el-form-item>-->
+          <!--          </el-col>-->
+          <el-col
+            :span="24"
+            class="mb20"
+          >
+            <el-form-item
+              label="备注"
+              prop="remark"
+            >
+              <el-input
+                type="textarea"
+                v-model="state.form.remark"
+                placeholder="请输入备注"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button
+            type="info"
+            @click="onCancel"
+            size="default"
+          >
+            取 消
+          </el-button>
+          <el-button
+            color="#2c78ff"
+            @click="onSubmit('20')"
+            size="default"
+          >
+            {{ state.dialog.submitTxt }}
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="systemProDialog">
+  import { nextTick, reactive, ref } from 'vue'
+  import to from 'await-to-js'
+  import { ElMessage } from 'element-plus'
+  import moment from 'moment/moment'
+  import { storeToRefs } from 'pinia'
+
+  import { useSystemApi } from '/@/api/platform/system'
+  import { deepClone } from '/@/utils/other'
+  import { useAppointmentApi } from '/@/api/platform/home/appointment'
+  import { useUserInfo } from '/@/stores/userInfo'
+  import { formatDate } from '/@/utils/formatTime'
+  import { useCellAssignApi } from '/@/api/platform/home/assign'
+
+  // 定义子组件向父组件传值/事件
+  const emit = defineEmits(['refresh'])
+
+  // 定义变量内容
+  const Api = useAppointmentApi()
+  const assignApi = useCellAssignApi()
+  const systemApi = useSystemApi()
+  const expertDialogFormRef = ref()
+  const stores = useUserInfo()
+  const { userInfos } = storeToRefs(stores)
+  const rules = {
+    startTime: { required: true, message: '不能为空', trigger: 'change' },
+    endTime: { required: true, message: '不能为空', trigger: 'change' },
+    userId: { required: true, message: '不能为空', trigger: 'change' },
+    userContact: { required: true, message: '不能为空', trigger: 'blur' },
+  }
+  const state = reactive({
+    form: {
+      id: 0,
+      assignId: 0,
+      userId: 0,
+      userName: '',
+      userDeptId: null,
+      userDeptName: '',
+      userContact: '',
+      startTime: null,
+      endTime: null,
+      remark: '',
+      duration: 0,
+    },
+    oldForm: {
+      id: 0,
+      appointId: 0,
+      mainId: 0,
+      platformId: 0,
+      platformName: '',
+      resId: 0,
+      resLocation: '',
+      resName: '',
+      userId: 0,
+      userName: '',
+      assignStatus: '',
+      startTime: '',
+      endTime: '',
+      oldStartTime: '',
+      oldEndTime: '',
+      isConfirm: '',
+      location: 0,
+      outApplyTime: '',
+      outStatus: 0,
+      outTime: null,
+    },
+    dialog: {
+      isShowDialog: false,
+      type: '',
+      title: '',
+      submitTxt: '',
+    },
+  })
+
+  const belongOrgOption = ref<DeptTreeType[]>([])
+  const userList = ref<any[]>([]) //用户列表
+  const getDicts = async () => {
+    await Promise.all([systemApi.getDeptTree(), systemApi.getUserList({ noPage: true })]).then(([dept, user]) => {
+      belongOrgOption.value = dept?.data || []
+      userList.value = user?.data.list || []
+    })
+  }
+  // 打开弹窗
+  const openDialog = async (type: string, row: any) => {
+    await getDicts()
+    state.dialog.type = type
+    state.dialog.title = '入室预约'
+    if (type == 'add') {
+      state.form = {
+        id: 0,
+        assignId: row?.id,
+        userId: userInfos.value.id,
+        userName: userInfos.value.nickName,
+        userDeptId: userInfos.value.deptId,
+        userDeptName: userInfos.value.deptName,
+        userContact: userInfos.value.phone,
+        startTime: row?.startTime,
+        endTime: row?.endTime,
+        remark: '',
+      }
+      const [err, res]: ToResponse = await to(assignApi.getDetail({ id: row?.id }))
+      if (err) return
+      state.oldForm = res?.data || {}
+
+      // 计算startTime和endTime的分钟差
+      const diff = moment(state.form.endTime).diff(moment(state.form.startTime), 'minutes')
+      state.form.duration = diff
+    } else {
+      const [err, res]: ToResponse = await to(Api.getDetail({ id: row?.id }))
+      if (err) return
+      state.form = res?.data || {}
+    }
+
+    state.dialog.submitTxt = '提 交'
+    state.dialog.isShowDialog = true
+    // 清空表单,此项需加表单验证才能使用
+    await nextTick()
+  }
+
+  // 关闭弹窗
+  const closeDialog = () => {
+    expertDialogFormRef.value.resetFields()
+    state.dialog.isShowDialog = false
+  }
+
+  const onCancel = () => {
+    closeDialog()
+  }
+
+  // 提交
+  const onSubmit = async (type: string) => {
+    expertDialogFormRef.value.validate(async (valid: boolean) => {
+      if (!valid) return
+      state.form.isTemporary = type
+      const params = deepClone(state.form)
+      const post = state.dialog.type == 'add' ? Api.create : Api.updateById
+      const [err]: ToResponse = await to(post(params))
+      if (err) return
+      ElMessage.success('操作成功')
+      closeDialog()
+      emit('refresh')
+    })
+  }
+  // 暴露变量
+  defineExpose({
+    openDialog,
+  })
+</script>
+<style lang="scss" scoped>
+  h4 {
+    font-size: 18px;
+  }
+  :deep(.disUoloadSty .el-upload--picture-card) {
+    display: none; /* 上传按钮隐藏 */
+  }
+  .avatar-uploader {
+    :deep(.el-upload) {
+      width: 100%;
+      height: 100%;
+      border: 1px dashed var(--el-border-color);
+      border-radius: 6px;
+      cursor: pointer;
+      position: relative;
+      overflow: hidden;
+      transition: var(--el-transition-duration-fast);
+      &:hover {
+        border-color: var(--el-color-primary);
+      }
+    }
+  }
+
+  .el-icon.avatar-uploader-icon {
+    font-size: 28px;
+    color: #8c939d;
+    width: 178px;
+    height: 178px;
+    text-align: center;
+  }
+</style>

+ 379 - 0
src/view/entry/components/renewalEdit.vue

@@ -0,0 +1,379 @@
+<template>
+  <div class="facilities-dialog-container">
+    <el-dialog
+      :title="state.dialog.title"
+      @close="onCancel"
+      :close-on-click-modal="false"
+      v-model="state.dialog.isShowDialog"
+      width="90%"
+    >
+      <h4 class="mb8 mt8">原申请信息</h4>
+      <el-descriptions
+        border
+        :column="1"
+        class="mb20"
+        direction="vertical"
+      >
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="入室申请名称"
+        >
+          {{ state.oldForm.userName }}的{{ state.oldForm.platformName }}的入室申请
+        </el-descriptions-item>
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="申请平台"
+          width="50%"
+        >
+          {{ state.oldForm.platformName }}
+        </el-descriptions-item>
+        <!--        <el-descriptions-item label-class-name="cell-item" label="申请时长">{{ state.oldForm.platformTime }}个月</el-descriptions-item>-->
+        <el-descriptions-item
+          label-class-name="cell-item"
+          label="入室周期"
+          width="50%"
+        >
+          {{ state.oldForm?.startTime ? formatDate(new Date(state.oldForm?.startTime), 'YYYY-mm-dd') : '' }} ~
+          {{ state.oldForm?.endTime ? formatDate(new Date(state.oldForm?.endTime), 'YYYY-mm-dd') : '' }}
+        </el-descriptions-item>
+      </el-descriptions>
+      <el-form
+        ref="expertDialogFormRef"
+        :model="state.form"
+        :rules="rules"
+        size="default"
+        label-width="140px"
+        label-position="top"
+      >
+        <h4 class="mb8 mt8">续期信息</h4>
+        <el-row :gutter="35">
+          <el-col
+            :span="24"
+            class="mb20"
+          >
+            <el-form-item
+              label="续期时长(个月)"
+              prop="platformTime"
+            >
+              <el-input-number
+                v-model="state.form.platformTime"
+                :min="1"
+                :precision="0"
+                class="w100"
+                @change="dateChange"
+              ></el-input-number>
+            </el-form-item>
+          </el-col>
+          <el-col
+            :span="24"
+            class="mb20"
+          >
+            <el-form-item label="续期后周期">
+              <el-date-picker
+                disabled
+                v-model="state.oldForm.startTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择时间"
+                clearable
+                style="width: 45%"
+              />
+              ~
+              <el-date-picker
+                disabled
+                v-model="state.form.endTime"
+                type="date"
+                value-format="YYYY-MM-DD"
+                placeholder="请选择时间"
+                clearable
+                style="width: 45%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col
+            :span="24"
+            class="mb20"
+          >
+            <el-form-item
+              label="其他说明"
+              prop="remark"
+            >
+              <el-input
+                type="textarea"
+                v-model="state.form.remark"
+                placeholder="请输入其他说明"
+              ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <h4 class="mb8 mt8">安全承诺</h4>
+        <el-checkbox v-model="state.safePromise">
+          本人承诺:如时候遵守实验室及个平台的各项规章制度,遵守在室的积分管理制度。
+        </el-checkbox>
+        <el-checkbox
+          v-model="state.safeRead"
+          :disabled="!state.isRead"
+        >
+          我已完整阅读并同意
+          <el-button
+            link
+            type="primary"
+            @click.stop="onRead"
+          >
+            《预约须知》
+          </el-button>
+          的内容,承诺如时候遵守实验室及个平台的各项规章制度,遵守在室的积分管理制度。
+        </el-checkbox>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button
+            type="info"
+            @click="onCancel"
+            size="default"
+          >
+            取 消
+          </el-button>
+          <el-button
+            color="#2c78ff"
+            @click="onSubmit('20')"
+            size="default"
+          >
+            {{ state.dialog.submitTxt }}
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+    <el-dialog
+      v-model="noticeShow"
+      :title="noticeInfo.noticeTitle"
+      width="800px"
+      :close-on-click-modal="false"
+    >
+      <div>
+        <div
+          class="ck-editor"
+          v-html="noticeInfo.noticeContent"
+        ></div>
+      </div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button
+            type="info"
+            @click="noticeShow = false"
+            size="default"
+          >
+            取 消
+          </el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="systemProDialog">
+  import to from 'await-to-js'
+  import { nextTick, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { usePlatformApi } from '/@/api/platform/home'
+  import { useSystemApi } from '/@/api/platform/system'
+  import { deepClone } from '/@/utils/other'
+  import { formatDate } from '/@/utils/formatTime'
+  import { usePlatformRenewalApi } from '/@/api/platform/home/renewal'
+
+  // 定义子组件向父组件传值/事件
+  const emit = defineEmits(['refresh'])
+
+  // 定义变量内容
+  const Api = usePlatformRenewalApi()
+  const systemApi = useSystemApi()
+  const platformApi = usePlatformApi()
+  const expertDialogFormRef = ref()
+  const rules = {
+    memberName: { required: true, message: '不能为空', trigger: 'blur' },
+    memberType: { required: true, message: '不能为空', trigger: 'change' },
+    pgName: { required: true, message: '不能为空', trigger: 'blur' },
+  }
+  const state = reactive({
+    oldForm: {
+      id: 0,
+      userId: null,
+      userName: '',
+      platformId: 0,
+      platformName: '',
+      assignStatus: '',
+      startTime: null,
+      endTime: null,
+      resId: null,
+      resName: '',
+      resLocation: '',
+      platformTime: null,
+    },
+    form: {
+      id: 0,
+      assignId: 0,
+      platformId: 0,
+      platformName: '',
+      resId: null,
+      resName: '',
+      userId: null,
+      userName: '',
+      platformTime: 1,
+      remark: '',
+    },
+    safePromise: false,
+    safeRead: false,
+    isRead: false,
+    dialog: {
+      isShowDialog: false,
+      type: '',
+      title: '',
+      submitTxt: '',
+    },
+  })
+  const noticeShow = ref(false)
+  const noticeInfo = reactive({
+    noticeTitle: '',
+    noticeContent: '',
+  })
+  const belongOrgOption = ref<DeptTreeType[]>([])
+  const getDicts = async () => {
+    await Promise.all([systemApi.getDeptTree()]).then(([dept]) => {
+      belongOrgOption.value = dept?.data || []
+    })
+  }
+  // 打开弹窗
+  const openDialog = async (type: string, row: any) => {
+    await getDicts()
+    state.dialog.type = type
+    state.dialog.title = '续期申请'
+    if (type == 'add') {
+      state.oldForm = row
+      state.form = {
+        id: 0,
+        assignId: row?.id,
+        platformId: row?.platformId,
+        platformName: row?.platformName,
+        resId: row?.resId,
+        resName: row?.resName,
+        userId: row?.userId,
+        userName: row?.userName,
+        platformTime: 1,
+        remark: '',
+      }
+    }
+
+    dateChange()
+    state.dialog.submitTxt = '提 交'
+    state.dialog.isShowDialog = true
+    // 清空表单,此项需加表单验证才能使用
+    await nextTick()
+  }
+
+  const dateChange = () => {
+    // 在endTime上添加月份
+    state.form.endTime = addMonths(state.oldForm.endTime, state.form.platformTime)
+  }
+
+  const addMonths = (dateStr, x) => {
+    let date = new Date(dateStr) // 解析日期字符串
+    let year = date.getFullYear() // 获取年份
+    let month = date.getMonth() + 1 // 获取月份 (从 0 开始,所以加 1)
+    let day = date.getDate() // 获取日期
+
+    // 计算新日期
+    let newMonth = month + x
+    let newYear = year + Math.floor((newMonth - 1) / 12)
+    let finalMonth = ((newMonth - 1) % 12) + 1
+
+    // 创建新的日期对象,并处理月份天数不足的情况
+    let finalDate = new Date(newYear, finalMonth, 0) // 获取该月最后一天
+    let finalDay = day > finalDate.getDate() ? finalDate.getDate() : day // 如果超过天数,则取最后一天
+
+    return formatDate(new Date(newYear, finalMonth - 1, finalDay), 'YYYY-mm-dd') // 返回结果日期
+  }
+
+  // 关闭弹窗
+  const closeDialog = () => {
+    expertDialogFormRef.value.resetFields()
+    state.isRead = false
+    state.safePromise = false
+    state.safeRead = false
+    state.dialog.isShowDialog = false
+  }
+  // 取消
+  const onCancel = () => {
+    closeDialog()
+  }
+  const onRead = async () => {
+    const [err, res]: ToResponse = await to(platformApi.getDetail({ id: state.form.platformId }))
+    if (err) return
+    state.isRead = true
+    noticeShow.value = true
+    noticeInfo.noticeTitle = '预约须知'
+    noticeInfo.noticeContent = res?.data.platformDesc
+  }
+  // 提交
+  const onSubmit = async (type: string) => {
+    if (!state.safePromise) {
+      ElMessage.error('请阅读并勾选安全承诺!')
+      return
+    }
+    if (!state.safeRead) {
+      ElMessage.error('请阅读并勾选预约须知!')
+      return
+    }
+    expertDialogFormRef.value.validate(async (valid: boolean) => {
+      if (!valid) return
+      state.form.isTemporary = type
+      const params = deepClone(state.form)
+      const post = state.dialog.type == 'add' ? Api.create : Api.update
+      const [err]: ToResponse = await to(post(params))
+      if (err) return
+      ElMessage.success('操作成功')
+      closeDialog()
+      emit('refresh')
+    })
+  }
+  // 暴露变量
+  defineExpose({
+    openDialog,
+  })
+</script>
+<style lang="scss" scoped>
+  h4 {
+    font-size: 18px;
+  }
+  :deep(.disUoloadSty .el-upload--picture-card) {
+    display: none; /* 上传按钮隐藏 */
+  }
+  .avatar-uploader {
+    :deep(.el-upload) {
+      width: 100%;
+      height: 100%;
+      border: 1px dashed var(--el-border-color);
+      border-radius: 6px;
+      cursor: pointer;
+      position: relative;
+      overflow: hidden;
+      transition: var(--el-transition-duration-fast);
+      &:hover {
+        border-color: var(--el-color-primary);
+      }
+    }
+  }
+
+  .el-icon.avatar-uploader-icon {
+    font-size: 28px;
+    color: #8c939d;
+    width: 178px;
+    height: 178px;
+    text-align: center;
+  }
+  :deep(.el-checkbox) {
+    padding-bottom: 30px;
+    .el-checkbox__label {
+      white-space: pre-wrap;
+    }
+  }
+</style>

+ 127 - 29
src/view/entry/index.vue

@@ -8,31 +8,116 @@
 -->
 <template>
   <div class="entry-container">
-    <van-tabs v-model:active="state.queryParams.approveStatus" @change="changeType">
-      <van-tab title="审批中" name="20"></van-tab>
-      <van-tab title="已通过" name="30"></van-tab>
-      <van-tab title="全部申请" name=""></van-tab>
+    <van-tabs
+      v-model:active="state.queryParams.approveStatus"
+      @change="changeType"
+    >
+      <van-tab
+        title="审批中"
+        name="20"
+      ></van-tab>
+      <van-tab
+        title="已通过"
+        name="30"
+      ></van-tab>
+      <van-tab
+        title="全部申请"
+        name=""
+      ></van-tab>
     </van-tabs>
     <div class="list-container">
-      <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad">
-        <van-cell v-for="item in state.list" :key="item" @click="toDetail(item.id)">
+      <van-list
+        v-model:loading="state.loading"
+        :finished="state.finished"
+        finished-text="没有更多了"
+        @load="onLoad"
+      >
+        <van-cell
+          v-for="item in state.list"
+          :key="item"
+          @click="toDetail(item.id)"
+        >
           <template #default>
             <div class="list">
               <header class="flex justify-between">
                 <strong class="title">{{ `${item.memberName}的${item.platformName}入室申请` }}</strong>
-                <van-tag v-if="item.approveStatus == 10" type="warning">待提交</van-tag>
-                <van-tag v-else-if="item.approveStatus == 20" type="primary">审批中</van-tag>
-                <van-tag v-else-if="item.approveStatus == 30" type="success">审批通过</van-tag>
-                <van-tag v-else-if="item.approveStatus == 40" type="danger">审批退回</van-tag>
+                <van-tag
+                  v-if="item.approveStatus == 10"
+                  type="warning"
+                >
+                  待提交
+                </van-tag>
+                <van-tag
+                  v-else-if="item.approveStatus == 20"
+                  type="primary"
+                >
+                  审批中
+                </van-tag>
+                <van-tag
+                  v-else-if="item.approveStatus == 30"
+                  type="success"
+                >
+                  审批通过
+                </van-tag>
+                <van-tag
+                  v-else-if="item.approveStatus == 40"
+                  type="danger"
+                >
+                  审批退回
+                </van-tag>
                 <!-- 05 待确认 10 待上传 20 待审核 30 已驳回 35 已撤回 40 待分配 50 可入室 60 已出室 -->
-                <van-tag v-if="item.appointStatus == '05'" class="ml4">待确认</van-tag>
-                <van-tag v-else-if="item.appointStatus == '10'" class="ml4">待上传</van-tag>
-                <van-tag v-else-if="item.appointStatus == '20'" type="primary" class="ml4">待审核</van-tag>
-                <van-tag v-else-if="item.appointStatus == '30'" type="danger" class="ml4">已驳回</van-tag>
-                <van-tag v-else-if="item.appointStatus == '35'" type="warning" class="ml4">已撤回</van-tag>
-                <van-tag v-else-if="item.appointStatus == '40'" class="ml4">待分配</van-tag>
-                <van-tag v-else-if="item.appointStatus == '50'" type="success" class="ml4">可入室</van-tag>
-                <van-tag v-else-if="item.appointStatus == '60'" class="ml4">已出室</van-tag>
+                <van-tag
+                  v-if="item.appointStatus == '05'"
+                  class="ml4"
+                >
+                  待确认
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '10'"
+                  class="ml4"
+                >
+                  待上传
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '20'"
+                  type="primary"
+                  class="ml4"
+                >
+                  待审核
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '30'"
+                  type="danger"
+                  class="ml4"
+                >
+                  已驳回
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '35'"
+                  type="warning"
+                  class="ml4"
+                >
+                  已撤回
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '40'"
+                  class="ml4"
+                >
+                  待分配
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '50'"
+                  type="success"
+                  class="ml4"
+                >
+                  可入室
+                </van-tag>
+                <van-tag
+                  v-else-if="item.appointStatus == '60'"
+                  class="ml4"
+                >
+                  已出室
+                </van-tag>
               </header>
               <p class="inst-title">
                 <span>课题名称</span>
@@ -53,11 +138,16 @@
               </p>
               <p class="inst-title">
                 <span>入室周期</span>
-                <span class="title ml8">{{
-                  item.appointEndDate
-                    ? `${formatDate(new Date(item.appointEndDate), 'YYYY-mm-dd')}~${formatDate(new Date(item.appointEndDate), 'YYYY-mm-dd')}`
-                    : '-'
-                }}</span>
+                <span class="title ml8">
+                  {{
+                    item.appointEndDate
+                      ? `${formatDate(new Date(item.appointEndDate), 'YYYY-mm-dd')}~${formatDate(
+                          new Date(item.appointEndDate),
+                          'YYYY-mm-dd',
+                        )}`
+                      : '-'
+                  }}
+                </span>
               </p>
               <footer class="flex justify-between mt4">
                 <span class="title">{{ item.memberName }}</span>
@@ -68,7 +158,12 @@
         </van-cell>
       </van-list>
     </div>
-    <van-floating-bubble v-model:offset="offset" icon="plus" @click="onClick" axis="y" />
+    <van-floating-bubble
+      v-model:offset="offset"
+      icon="plus"
+      @click="onClick"
+      axis="y"
+    />
     <!-- <van-popup v-model:show="showEntryAddDialog" position="bottom" :style="{ padding: '64px' }">
       <EntryAdd @entry-add-success="handleEntryAddSuccess" />
     </van-popup> -->
@@ -91,11 +186,11 @@
     queryParams: {
       approveStatus: '20',
       pageNum: 1,
-      pageSize: 10
+      pageSize: 10,
     },
     finished: false,
     loading: true,
-    list: [] as any[]
+    list: [] as any[],
   })
 
   const formatData = (cellRes: any[], molecularRes: any[]) => {
@@ -105,9 +200,12 @@
     if (molecularRes && molecularRes.length && (!cellRes || !cellRes.length)) {
       return { platformName: molecularRes[0].platformName, platformTime: molecularRes[0].platformTime }
     }
+    if (!cellRes || !molecularRes) {
+      return { platformName: '', platformTime: '' }
+    }
     return {
       platformName: `${cellRes[0].platformName} / ${molecularRes[0].platformName}`,
-      platformTime: `${cellRes[0].platformTime}个月 / ${molecularRes[0].platformTime}个月`
+      platformTime: `${cellRes[0].platformTime}个月 / ${molecularRes[0].platformTime}个月`,
     }
   }
 
@@ -133,8 +231,8 @@
     router.push({
       path: '/entry/detail',
       query: {
-        id
-      }
+        id,
+      },
     })
   }
   const onClick = () => {

+ 179 - 33
src/view/entry/mine.vue

@@ -1,37 +1,108 @@
 <template>
   <div class="entry-container">
     <div class="list-container">
-      <div class="search-wrap" ref="searchWrapRef">
-        <el-form :model="state.queryParams" ref="queryRef" :inline="true">
+      <div
+        class="search-wrap"
+        ref="searchWrapRef"
+      >
+        <el-form
+          :model="state.queryParams"
+          ref="queryRef"
+          :inline="true"
+        >
           <el-form-item prop="serialNo">
-            <el-select v-model="state.queryParams.platformId" style="width: 100%" placeholder="申请平台" clearable @change="search">
-              <el-option v-for="item in platformList" :key="item.id" :label="item.platformName" :value="item.id"></el-option>
+            <el-select
+              v-model="state.queryParams.platformId"
+              style="width: 100%"
+              placeholder="申请平台"
+              clearable
+              @change="search"
+            >
+              <el-option
+                v-for="item in platformList"
+                :key="item.id"
+                :label="item.platformName"
+                :value="item.id"
+              ></el-option>
             </el-select>
           </el-form-item>
           <el-form-item prop="serialNo">
             <div class="flex justify-between">
-              <el-date-picker v-model="startTime" style="width: 48%" type="date" placeholder="开始时间" clearable @change="search" />
-              <el-date-picker v-model="endTime" style="width: 48%" type="date" placeholder="结束时间" clearable @change="search" />
+              <el-date-picker
+                v-model="startTime"
+                style="width: 48%"
+                type="date"
+                placeholder="开始时间"
+                clearable
+                @change="search"
+              />
+              <el-date-picker
+                v-model="endTime"
+                style="width: 48%"
+                type="date"
+                placeholder="结束时间"
+                clearable
+                @change="search"
+              />
             </div>
           </el-form-item>
           <el-form-item prop="serialNo">
-            <el-input v-model="state.queryParams.resName" style="width: 100%" placeholder="资源名称" clearable @keyup.enter="search" />
+            <el-input
+              v-model="state.queryParams.resName"
+              style="width: 100%"
+              placeholder="资源名称"
+              clearable
+              @keyup.enter="search"
+            />
           </el-form-item>
         </el-form>
       </div>
 
-      <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad">
-        <van-cell v-for="item in state.list" :key="item">
+      <van-list
+        v-model:loading="state.loading"
+        :finished="state.finished"
+        finished-text="没有更多了"
+        @load="onLoad"
+      >
+        <van-cell
+          v-for="item in state.list"
+          :key="item"
+        >
           <template #default>
             <div class="list">
               <header class="flex justify-between">
                 <strong class="title">{{ item.userName }}的{{ item.platformName }}入室申请</strong>
 
-                <van-tag v-if="item.assignStatus == EntryAssignStatus.TO_BE_ENTERED" type="primary">待入室</van-tag>
-                <van-tag v-else-if="item.assignStatus == EntryAssignStatus.NEXT_MONTH_ENTERED" type="primary">次月入室</van-tag>
-                <van-tag v-else-if="item.assignStatus == EntryAssignStatus.ENTERED" type="success">已入室</van-tag>
-                <van-tag v-else-if="item.assignStatus == EntryAssignStatus.OUT_ROOM" type="primary">已出室</van-tag>
-                <van-tag v-else-if="item.assignStatus == EntryAssignStatus.NEXT_MONTH_OUT_ROOM" type="primary">次月出室</van-tag>
+                <van-tag
+                  v-if="item.assignStatus == EntryAssignStatus.TO_BE_ENTERED"
+                  type="primary"
+                >
+                  待入室
+                </van-tag>
+                <van-tag
+                  v-else-if="item.assignStatus == EntryAssignStatus.NEXT_MONTH_ENTERED"
+                  type="primary"
+                >
+                  次月入室
+                </van-tag>
+                <van-tag
+                  v-else-if="item.assignStatus == EntryAssignStatus.ENTERED"
+                  type="success"
+                >
+                  已入室
+                </van-tag>
+                <van-tag
+                  v-else-if="item.assignStatus == EntryAssignStatus.OUT_ROOM"
+                  type="primary"
+                >
+                  已出室
+                </van-tag>
+                <van-tag
+                  v-else-if="item.assignStatus == EntryAssignStatus.NEXT_MONTH_OUT_ROOM"
+                  type="primary"
+                >
+                  次月出室
+                </van-tag>
               </header>
               <p class="inst-title">
                 <span>申请平台</span>
@@ -42,7 +113,8 @@
               <p class="inst-title">
                 <span>入室时间</span>
                 <span class="title ml8">
-                  {{ item?.startTime ? formatDate(new Date(item?.startTime), 'YYYY-mm-dd') : '' }} ~
+                  {{ item?.startTime ? formatDate(new Date(item?.startTime), 'YYYY-mm-dd') : '' }}
+                  ~
                   {{ item?.endTime ? formatDate(new Date(item?.endTime), 'YYYY-mm-dd') : '' }}
                 </span>
               </p>
@@ -59,8 +131,46 @@
                 </span>
               </p>
               <footer class="flex justify-between mt16">
-                <div v-if="item.assignStatus === EntryAssignStatus.ENTERED && item.userId === userInfos.id" style="text-align: right; width: 100%">
-                  <van-button type="primary" style="height: 30px" @click="handleOutRoom(item)">申请出室</van-button>
+                <div
+                  v-if="item.assignStatus === '20' && item.userId === userInfos.id"
+                  style="text-align: right; width: 100%"
+                >
+                  <van-button
+                    type="primary"
+                    style="height: 30px"
+                    @click="handleAppointment(item)"
+                  >
+                    入室预约
+                  </van-button>
+                </div>
+                <div
+                  v-if="
+                    item.assignStatus === '20' &&
+                    item.userId === userInfos.id &&
+                    item.outStatus !== 20 &&
+                    item.outStatus !== 30
+                  "
+                  style="text-align: right; width: 100%"
+                >
+                  <van-button
+                    type="primary"
+                    style="height: 30px"
+                    @click="handleRenewal(item)"
+                  >
+                    申请续期
+                  </van-button>
+                </div>
+                <div
+                  v-if="item.assignStatus === EntryAssignStatus.ENTERED && item.userId === userInfos.id"
+                  style="text-align: right; width: 100%"
+                >
+                  <van-button
+                    type="primary"
+                    style="height: 30px"
+                    @click="handleOutRoom(item)"
+                  >
+                    申请出室
+                  </van-button>
                 </div>
               </footer>
             </div>
@@ -79,8 +189,17 @@
         }
       "
     >
-      <el-form ref="outRoomFormRef" :model="outRoomForm" :rules="rules" label-position="top">
-        <el-form-item style="display: block" label="申请出室日期" prop="outRoomDate">
+      <el-form
+        ref="outRoomFormRef"
+        :model="outRoomForm"
+        :rules="rules"
+        label-position="top"
+      >
+        <el-form-item
+          style="display: block"
+          label="申请出室日期"
+          prop="outRoomDate"
+        >
           <el-date-picker
             :disabled-date="disabledTime"
             v-model="outRoomForm.outRoomDate"
@@ -98,13 +217,21 @@
                   showOutDialog = false
                 }
               "
-              >取消</el-button
             >
-            <el-button type="primary" @click="handleSubmitOutRoom">申请</el-button>
+              取消
+            </el-button>
+            <el-button
+              type="primary"
+              @click="handleSubmitOutRoom"
+            >
+              申请
+            </el-button>
           </div>
         </el-form-item>
       </el-form>
     </el-dialog>
+    <Appoint ref="appointRef" />
+    <RenewalEdit ref="renewalEditRef" />
   </div>
 </template>
 
@@ -121,13 +248,15 @@
   import { usePlatformApi } from '/@/api/platform/home'
   import { formatDate } from '/@/utils/formatTime'
   import { useUserInfo } from '/@/stores/userInfo'
+  import Appoint from './components/appoint.vue'
+  import RenewalEdit from './components/renewalEdit.vue'
 
   enum EntryAssignStatus {
     TO_BE_ENTERED = '10', // 待入室
     NEXT_MONTH_ENTERED = '15', // 次月入室
     ENTERED = '20', // 已入室
     OUT_ROOM = '30', // 已出室
-    NEXT_MONTH_OUT_ROOM = '40' // 次月出室
+    NEXT_MONTH_OUT_ROOM = '40', // 次月出室
   }
 
   const stores = useUserInfo()
@@ -139,10 +268,13 @@
   const router = useRouter()
 
   const outRoomFormRef = ref()
+  const renewalEditRef = ref()
+  const appointRef = ref()
   const showOutDialog = ref<boolean>(false)
   const startTime = ref('')
   const endTime = ref('')
   const platformList = ref<any[]>([])
+
   const state = reactive({
     queryParams: {
       platformId: null,
@@ -150,29 +282,35 @@
       resName: '',
       pageNum: 1,
       orderBy: '',
-      pageSize: 20
+      pageSize: 20,
     },
     finished: false,
     loading: true,
-    list: [] as any[]
+    list: [] as any[],
   })
   const currentSelectEntryItem = ref({
     id: null,
     endTime: '',
-    startTime: ''
+    startTime: '',
   })
 
   const outRoomForm = reactive({
-    outRoomDate: ''
+    outRoomDate: '',
   })
 
   const rules = {
-    outRoomDate: { required: true, message: '不能为空', trigger: 'change' }
+    outRoomDate: { required: true, message: '不能为空', trigger: 'change' },
   }
 
   const disabledTime = (date: Date) => {
-    let startTime = formatDate(new Date(dayjs(currentSelectEntryItem.value.startTime).add(1, 'month').format('YYYY-MM-DD')), 'YYYY-mm-dd')
-    let endTime = formatDate(new Date(dayjs(currentSelectEntryItem.value.endTime).subtract(1, 'month').format('YYYY-MM-DD')), 'YYYY-mm-dd')
+    let startTime = formatDate(
+      new Date(dayjs(currentSelectEntryItem.value.startTime).add(1, 'month').format('YYYY-MM-DD')),
+      'YYYY-mm-dd',
+    )
+    let endTime = formatDate(
+      new Date(dayjs(currentSelectEntryItem.value.endTime).subtract(1, 'month').format('YYYY-MM-DD')),
+      'YYYY-mm-dd',
+    )
 
     let now = formatDate(new Date(), 'YYYY-mm-dd')
     let time = formatDate(new Date(date), 'YYYY-mm-dd')
@@ -183,7 +321,7 @@
     state.loading = true
 
     const payload: any = {
-      ...state.queryParams
+      ...state.queryParams,
     }
 
     Object.keys(payload).map((item) => {
@@ -215,7 +353,7 @@
     state.queryParams = {
       ...state.queryParams,
       pageNum: 1,
-      pageSize: 20
+      pageSize: 20,
     }
     state.list = []
     state.finished = false
@@ -263,8 +401,8 @@
           mentorName: projectGroup.pgLeaderName,
           mentorDeptName: projectGroup.pgOrgPaths,
           mentorPhone: projectGroup.pgLeaderContact,
-          isTemporary: '20' // 是否暂存,10是,20否
-        })
+          isTemporary: '20', // 是否暂存,10是,20否
+        }),
       )
       if (err) return
       showOutDialog.value = false
@@ -275,6 +413,14 @@
     })
   }
 
+  const handleAppointment = (item: any) => {
+    appointRef.value.openDialog(item)
+  }
+
+  const handleRenewal = (row) => {
+    renewalEditRef.value.openDialog('add', row)
+  }
+
   onMounted(() => {
     getDicts()
     onLoad()