Kaynağa Gözat

Merge branch 'feature/update-entry-mine' of wanglj/labsop_h5 into master

徐凯 7 ay önce
ebeveyn
işleme
98a82534a5

+ 2 - 0
package.json

@@ -9,11 +9,13 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@element-plus/icons-vue": "^2.3.1",
     "@vitejs/plugin-basic-ssl": "^2.0.0",
     "await-to-js": "^3.0.0",
     "axios": "^1.8.2",
     "cropperjs": "1.5.13",
     "downloadjs": "^1.4.7",
+    "element-plus": "^2.9.8",
     "moment": "^2.29.4",
     "pinia": "^3.0.1",
     "postcss-px-to-viewport": "^1.1.1",

+ 163 - 4
pnpm-lock.yaml

@@ -1,10 +1,9 @@
 lockfileVersion: '6.0'
 
-settings:
-  autoInstallPeers: true
-  excludeLinksFromLockfile: false
-
 dependencies:
+  '@element-plus/icons-vue':
+    specifier: ^2.3.1
+    version: 2.3.1(vue@3.5.13)
   '@vitejs/plugin-basic-ssl':
     specifier: ^2.0.0
     version: 2.0.0(vite@4.5.9)
@@ -20,6 +19,9 @@ dependencies:
   downloadjs:
     specifier: ^1.4.7
     version: 1.4.7
+  element-plus:
+    specifier: ^2.9.8
+    version: 2.9.8(vue@3.5.13)
   moment:
     specifier: ^2.29.4
     version: 2.29.4
@@ -99,6 +101,19 @@ packages:
       '@babel/helper-string-parser': 7.25.9
       '@babel/helper-validator-identifier': 7.25.9
 
+  /@ctrl/tinycolor@3.6.1:
+    resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
+    engines: {node: '>=10'}
+    dev: false
+
+  /@element-plus/icons-vue@2.3.1(vue@3.5.13):
+    resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
+    peerDependencies:
+      vue: ^3.2.0
+    dependencies:
+      vue: 3.5.13(typescript@4.9.5)
+    dev: false
+
   /@esbuild/android-arm64@0.18.20:
     resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
     engines: {node: '>=12'}
@@ -275,6 +290,23 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@floating-ui/core@1.6.9:
+    resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
+    dependencies:
+      '@floating-ui/utils': 0.2.9
+    dev: false
+
+  /@floating-ui/dom@1.6.13:
+    resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
+    dependencies:
+      '@floating-ui/core': 1.6.9
+      '@floating-ui/utils': 0.2.9
+    dev: false
+
+  /@floating-ui/utils@0.2.9:
+    resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+    dev: false
+
   /@jridgewell/sourcemap-codec@1.5.0:
     resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
 
@@ -448,15 +480,33 @@ packages:
       picomatch: 4.0.2
     dev: true
 
+  /@sxzz/popperjs-es@2.11.7:
+    resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
+    dev: false
+
   /@types/estree@1.0.6:
     resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
     dev: true
 
+  /@types/lodash-es@4.17.12:
+    resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
+    dependencies:
+      '@types/lodash': 4.17.16
+    dev: false
+
+  /@types/lodash@4.17.16:
+    resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==}
+    dev: false
+
   /@types/node@18.19.80:
     resolution: {integrity: sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==}
     dependencies:
       undici-types: 5.26.5
 
+  /@types/web-bluetooth@0.0.16:
+    resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
+    dev: false
+
   /@vant/auto-import-resolver@1.3.0:
     resolution: {integrity: sha512-lJyWtCyFizR4bHZvMiNMF3w+WTFTUWAvka1eqTnPK9ticUcKTCOx6qEmHcm8JPb3g1t3GaD2W3MnHkBp/nHamw==}
     dev: true
@@ -618,6 +668,31 @@ packages:
   /@vue/shared@3.5.13:
     resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
 
+  /@vueuse/core@9.13.0(vue@3.5.13):
+    resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
+    dependencies:
+      '@types/web-bluetooth': 0.0.16
+      '@vueuse/metadata': 9.13.0
+      '@vueuse/shared': 9.13.0(vue@3.5.13)
+      vue-demi: 0.14.10(vue@3.5.13)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+    dev: false
+
+  /@vueuse/metadata@9.13.0:
+    resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
+    dev: false
+
+  /@vueuse/shared@9.13.0(vue@3.5.13):
+    resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
+    dependencies:
+      vue-demi: 0.14.10(vue@3.5.13)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+    dev: false
+
   /acorn@8.14.1:
     resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
     engines: {node: '>=0.4.0'}
@@ -632,6 +707,10 @@ packages:
       picomatch: 2.3.1
     dev: true
 
+  /async-validator@4.2.5:
+    resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
+    dev: false
+
   /asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
     dev: false
@@ -726,6 +805,10 @@ packages:
   /csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
+  /dayjs@1.11.13:
+    resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+    dev: false
+
   /debug@4.4.0:
     resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
     engines: {node: '>=6.0'}
@@ -763,6 +846,31 @@ packages:
       gopd: 1.2.0
     dev: false
 
+  /element-plus@2.9.8(vue@3.5.13):
+    resolution: {integrity: sha512-srViUaUdfblBKGMeuEPiXxxKlH5aUmKqEwmhb/At9Sj91DbU6od/jYN1955cTnzt3wTSA7GfnZF7UiRX9sdRHg==}
+    peerDependencies:
+      vue: ^3.2.0
+    dependencies:
+      '@ctrl/tinycolor': 3.6.1
+      '@element-plus/icons-vue': 2.3.1(vue@3.5.13)
+      '@floating-ui/dom': 1.6.13
+      '@popperjs/core': /@sxzz/popperjs-es@2.11.7
+      '@types/lodash': 4.17.16
+      '@types/lodash-es': 4.17.12
+      '@vueuse/core': 9.13.0(vue@3.5.13)
+      async-validator: 4.2.5
+      dayjs: 1.11.13
+      escape-html: 1.0.3
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
+      memoize-one: 6.0.0
+      normalize-wheel-es: 1.2.0
+      vue: 3.5.13(typescript@4.9.5)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+    dev: false
+
   /entities@4.5.0:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
@@ -823,6 +931,10 @@ packages:
       '@esbuild/win32-ia32': 0.18.20
       '@esbuild/win32-x64': 0.18.20
 
+  /escape-html@1.0.3:
+    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+    dev: false
+
   /estree-walker@2.0.2:
     resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
 
@@ -985,6 +1097,26 @@ packages:
     engines: {node: '>=14'}
     dev: true
 
+  /lodash-es@4.17.21:
+    resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
+    dev: false
+
+  /lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
+    resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
+    peerDependencies:
+      '@types/lodash-es': '*'
+      lodash: '*'
+      lodash-es: '*'
+    dependencies:
+      '@types/lodash-es': 4.17.12
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+    dev: false
+
+  /lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+    dev: false
+
   /magic-string@0.30.17:
     resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
     dependencies:
@@ -995,6 +1127,10 @@ packages:
     engines: {node: '>= 0.4'}
     dev: false
 
+  /memoize-one@6.0.0:
+    resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
+    dev: false
+
   /merge2@1.4.1:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
     engines: {node: '>= 8'}
@@ -1053,6 +1189,10 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
+  /normalize-wheel-es@1.2.0:
+    resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
+    dev: false
+
   /object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
@@ -1295,6 +1435,21 @@ packages:
     optionalDependencies:
       fsevents: 2.3.3
 
+  /vue-demi@0.14.10(vue@3.5.13):
+    resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
+    engines: {node: '>=12'}
+    hasBin: true
+    requiresBuild: true
+    peerDependencies:
+      '@vue/composition-api': ^1.0.0-rc.1
+      vue: ^3.0.0-0 || ^2.6.0
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+    dependencies:
+      vue: 3.5.13(typescript@4.9.5)
+    dev: false
+
   /vue-router@4.5.0(vue@3.5.13):
     resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==}
     peerDependencies:
@@ -1336,3 +1491,7 @@ packages:
   /weixin-js-sdk@1.6.5:
     resolution: {integrity: sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ==}
     dev: false
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false

+ 67 - 0
src/api/base/system/dict.ts

@@ -0,0 +1,67 @@
+/*
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2023-07-19 13:42:40
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-07-21 10:16:53
+ * @Description: file content
+ * @FilePath: \labsop_meno\frontend\packages\vue-next-admin\src\api\system\dict.ts
+ */
+import request from '/@/utils/micro_request.js'
+const basePath = import.meta.env.VITE_ADMIN
+// 参数设置
+export function useDictApi() {
+  return {
+    // 系统字典分类创建
+    createDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateSysDictType', query)
+    },
+    // 系统字典分类列表
+    getDictTypeList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetSysDictTypeList', query)
+    },
+    // 用户字典分类创建
+    createClientDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateClientDictType', query)
+    },
+    // 字典分类列表
+    getClientDictTypeList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetClientDictTypeList', query)
+    },
+    // 删除字典分类
+    delDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','DeleteDictTypeByIds', query)
+    },
+    // 字典分类详情
+    getDictTypeEntity(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictTypeEntity', query)
+    },
+    // 字典分类更新
+    updateDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','UpdateDictType', query)
+    },
+    // 字典明细创建
+    createDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateDictData', query)
+    },
+    // 字典明细列表
+    getDictDataList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictDataList', query)
+    },
+    // 字典明细删除
+    delDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','DeleteDictDataByIds', query)
+    },
+     // 字典明细详情
+     getDictDataEntity(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictDataEntity', query)
+    },
+     // 字典明细更新
+     updateDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','UpdateDictData', query)
+    },
+    // 批量根据字典类型获取字典项明细
+    getDictDataByType(str: string) {
+      return request.postRequest(basePath,'Dict','GetDictDataByType', { dictType: str })
+    }
+  }
+}

+ 10 - 0
src/api/platform/appoint/index.ts

@@ -66,6 +66,16 @@ export function usePlatformAppointApi() {
     // 获取当前登录人的入室申请
     getUserInfoList(params?: Object) {
       return request.postRequest(basePath, 'PlatPlatformAppoint', 'GetUserInfoList', params)
+    },
+    // 获取入室管理列表
+    getPlatformAppointList(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformAppoint', 'GetPlatformAppointList', params)
+    },
+    getCellEntityById(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformAppoint', 'GetCellEntityById', params)
+    },
+    getMolecularEntityById(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformAppoint', 'GetMolecularEntityById', params)
     }
   }
 }

+ 10 - 6
src/api/platform/home/assign.ts

@@ -13,23 +13,27 @@ export function useCellAssignApi() {
     // 资源分配
     // 资源信息
     assignInfo(params?: Object) {
-      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'AssignInfo', params);
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'AssignInfo', params)
     },
     // 队列
     assignQueue(params?: Object) {
-      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'AssignQueue', params);
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'AssignQueue', params)
     },
     // 分配
     create(params?: Object) {
-      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'Create', params);
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'Create', params)
     },
     // 获取用户平台
     getUserPlat(params?: Object) {
-      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'PlatByMember', params);
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'PlatByMember', params)
     },
     // 下载缴费单
     downBillfile(params?: Object) {
-      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'DownBillfile', params);
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'DownBillfile', params)
+    },
+    // 获取用户资源分配列表
+    getUserAssignResources(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'GetUserAssignResources', params)
     }
-  };
+  }
 }

+ 60 - 52
src/api/platform/home/index.ts

@@ -10,58 +10,66 @@ import request from '/@/utils/micro_request.js';
 const basePath = import.meta.env.VITE_PLATFORM_API;
 export function usePlatformApi() {
 	return {
-		// 平台管理
-		// 列表
-		create(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'Create', params);
-		},
-		// 删除
-		deleteByIds(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'DeleteByIds', params);
-		},
-		// 列表
-		getPlatformList(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'GetList', params);
-		},
-		getAllPlatformList(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'GetPlatFormList', params);
-		},
-		// 平台资源类型
-		getResourceTypeDict(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'GetResourceTypeDict', params);
-		},
-		// 详情
-		getDetail(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'GetEntityById', params);
-		},
-		// 编辑
-		update(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatform', 'UpdateById', params);
-		},
+    // 平台管理
+    // 列表
+    create(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'Create', params)
+    },
+    // 删除
+    deleteByIds(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'DeleteByIds', params)
+    },
+    // 列表
+    getPlatformList(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'GetList', params)
+    },
+    getAllPlatformList(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'GetPlatFormList', params)
+    },
+    // 平台资源类型
+    getResourceTypeDict(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'GetResourceTypeDict', params)
+    },
+    // 详情
+    getDetail(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'GetEntityById', params)
+    },
+    // 编辑
+    update(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'UpdateById', params)
+    },
     // 资源
     // 创建
-		createResource(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatformResource', 'CreateResource', params);
-		},
-		// 删除
-		deleteResourceByIds(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatformResource', 'DeleteResourceByIds', params);
-		},
-		// 详情
-		getResourceEntityById(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatformResource', 'GetResourceEntityById', params);
-		},
-		// 列表
-		getResourceList(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatformResource', 'GetResourceList', params);
-		},
-		// 更新
-		updateResourceById(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatformResource', 'UpdateResourceById', params);
-		},
-		// 更新状态
-		updateResourceStatusById(params?: Object) {
-			return request.postRequest(basePath, 'PlatPlatformResource', 'UpdateResourceStatusById', params);
-		},
-	};
+    createResource(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformResource', 'CreateResource', params)
+    },
+    // 删除
+    deleteResourceByIds(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformResource', 'DeleteResourceByIds', params)
+    },
+    // 详情
+    getResourceEntityById(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformResource', 'GetResourceEntityById', params)
+    },
+    // 列表
+    getResourceList(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformResource', 'GetResourceList', params)
+    },
+    // 更新
+    updateResourceById(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformResource', 'UpdateResourceById', params)
+    },
+    // 更新状态
+    updateResourceStatusById(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformResource', 'UpdateResourceStatusById', params)
+    },
+    // 获取资源组
+    getMolecularGroupList(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatform', 'GetMolecularGroupList', params)
+    },
+    // 一键分配
+    batchAssign(params?: Object) {
+      return request.postRequest(basePath, 'PlatPlatformCellAssign', 'BatchAssign', params)
+    }
+  }
 }

+ 59 - 0
src/api/platform/system/dict.ts

@@ -0,0 +1,59 @@
+/*
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2023-07-19 13:42:40
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-07-21 10:16:53
+ * @Description: file content
+ * @FilePath: \labsop_meno\frontend\packages\vue-next-admin\src\api\system\dict.ts
+ */
+import request from '/@/utils/micro_request.js'
+const basePath = import.meta.env.VITE_ADMIN
+// 参数设置
+export function useDictApi() {
+  return {
+    // 字典分类创建
+    createDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateDictType', query)
+    },
+    // 字典分类列表
+    getDictTypeList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictTypeList', query)
+    },
+    // 删除字典分类
+    delDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','DeleteDictTypeByIds', query)
+    },
+    // 字典分类详情
+    getDictTypeEntity(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictTypeEntity', query)
+    },
+    // 字典分类更新
+    updateDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','UpdateDictType', query)
+    },
+    // 字典明细创建
+    createDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateDictData', query)
+    },
+    // 字典明细列表
+    getDictDataList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictDataList', query)
+    },
+    // 字典明细删除
+    delDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','DeleteDictDataByIds', query)
+    },
+     // 字典明细详情
+     getDictDataEntity(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictDataEntity', query)
+    },
+     // 字典明细更新
+     updateDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','UpdateDictData', query)
+    },
+    // 根据字典类型获取字典项明细
+    getDictDataByType(str: string) {
+      return request.postRequest(basePath,'Dict','GetDictDataByType', { dictType: str })
+    },
+  }
+}

+ 43 - 0
src/api/platform/system/index.ts

@@ -0,0 +1,43 @@
+/*
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2023-07-19 13:42:40
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-07-21 10:16:53
+ * @Description: file content
+ * @FilePath: \labsop_meno\frontend\packages\vue-next-admin\src\api\system\dict.ts
+ */
+import request from '/@/utils/micro_request.js';
+const basePath = import.meta.env.VITE_ADMIN;
+// 参数设置
+export function useSystemApi() {
+	return {
+		// 根据字典类型获取字典项明细
+		getDictDataByType(str: string) {
+			return request.postRequest(basePath, 'Dict', 'GetDictDataByType', { dictType: str });
+		},
+		// 获取用户列表
+		getUserList(query?: object) {
+			return request.postRequest(basePath, 'User', 'GetList', query);
+		},
+		// 获取用户字典
+		getUserDict(query?: object) {
+			return request.postRequest(basePath, 'User', 'GetDictList', query);
+    },
+    // 获取仪器责任人角色用户列表
+		getInstUserList(query?: object) {
+			return request.postRequest(basePath, 'User', 'GetUserByRoleCode', query);
+		},
+		// 部门树
+		getDeptTree(query?: object) {
+			return request.postRequest(basePath, 'Dept', 'GetDeptTree', query);
+		},
+		// 部门列表
+		getDeptList(query?: object) {
+			return request.postRequest(basePath, 'Dept', 'GetList', query);
+		},
+		// 课题组
+		getProjectGroupList(query?: object) {
+			return request.postRequest(basePath, 'ProjectGroup', 'GetProjectGroupList', query);
+		},
+	};
+}

+ 59 - 0
src/api/scientific/system/dict.ts

@@ -0,0 +1,59 @@
+/*
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2023-07-19 13:42:40
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-07-21 10:16:53
+ * @Description: file content
+ * @FilePath: \labsop_meno\frontend\packages\vue-next-admin\src\api\system\dict.ts
+ */
+import request from '/@/utils/micro_request.js'
+const basePath = import.meta.env.VITE_ADMIN
+// 参数设置
+export function useDictApi() {
+  return {
+    // 字典分类创建
+    createDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateDictType', query)
+    },
+    // 字典分类列表
+    getDictTypeList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictTypeList', query)
+    },
+    // 删除字典分类
+    delDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','DeleteDictTypeByIds', query)
+    },
+    // 字典分类详情
+    getDictTypeEntity(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictTypeEntity', query)
+    },
+    // 字典分类更新
+    updateDictType(query?: object) {
+      return request.postRequest(basePath,'Dict','UpdateDictType', query)
+    },
+    // 字典明细创建
+    createDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','CreateDictData', query)
+    },
+    // 字典明细列表
+    getDictDataList(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictDataList', query)
+    },
+    // 字典明细删除
+    delDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','DeleteDictDataByIds', query)
+    },
+     // 字典明细详情
+     getDictDataEntity(query?: object) {
+      return request.postRequest(basePath,'Dict','GetDictDataEntity', query)
+    },
+     // 字典明细更新
+     updateDictData(query?: object) {
+      return request.postRequest(basePath,'Dict','UpdateDictData', query)
+    },
+    // 根据字典类型获取字典项明细
+    getDictDataByType(str: string) {
+      return request.postRequest(basePath,'Dict','GetDictDataByType', { dictType: str })
+    },
+  }
+}

+ 39 - 0
src/api/scientific/system/index.ts

@@ -0,0 +1,39 @@
+/*
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2023-07-19 13:42:40
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-07-21 10:16:53
+ * @Description: file content
+ * @FilePath: \labsop_meno\frontend\packages\vue-next-admin\src\api\system\dict.ts
+ */
+import request from '/@/utils/micro_request.js';
+const basePath = import.meta.env.VITE_ADMIN;
+// 参数设置
+export function useSystemApi() {
+	return {
+		// 根据字典类型获取字典项明细
+		getDictDataByType(str: string) {
+			return request.postRequest(basePath, 'Dict', 'GetDictDataByType', { dictType: str });
+		},
+		// 获取用户列表
+		getUserList(query?: object) {
+			return request.postRequest(basePath, 'User', 'GetList', query);
+		},
+		// 获取用户字典
+		getUserDict(query?: object) {
+			return request.postRequest(basePath, 'User', 'GetDictList', query);
+    },
+    // 获取仪器责任人角色用户列表
+		getInstUserList(query?: object) { 
+			return request.postRequest(basePath, 'User', 'GetUserByRoleCode', query);
+		},
+		// 部门树
+		getDeptTree(query?: object) {
+			return request.postRequest(basePath, 'Dept', 'GetDeptTree', query);
+		},
+		// 部门列表
+		getDeptList(query?: object) {
+			return request.postRequest(basePath, 'Dept', 'GetList', query);
+		},
+	};
+}

+ 35 - 0
src/api/scientific/system/scientific.ts

@@ -0,0 +1,35 @@
+/*
+ * @Author: wanglj 471442253@qq.com
+ * @Date: 2023-07-19 13:42:40
+ * @LastEditors: wanglj
+ * @LastEditTime: 2023-08-17 09:37:26
+ * @Description: file content
+ * @FilePath: \labsop_meno\frontend\packages\vue-next-admin\src\api\system\equip.ts
+ */
+import request from '/@/utils/micro_request.js'
+const basePath = import.meta.env.VITE_SCI
+// 参数设置
+export function useSciApi() {
+  return {
+    // 检测科研人员登录
+    checkRsrLogin(query?: object) {
+      return request.postRequest(basePath,'SciTeam','CheckRsrLogin', query)
+    },
+    // 创建科研人员
+    create(query?: object) {
+      return request.postRequest(basePath, 'SciTeam', 'CreateReser', query);
+    },
+    // 获取科研人员信息
+    getRsrEntityByAccountId(query?: object) {
+      return request.postRequest(basePath, 'SciTeam', 'GetRsrEntityByAccountId', query);
+    },
+    // 修改科研人员信息
+    updateById(query?: object) {
+      return request.postRequest(basePath, 'SciTeam', 'UpdateReserById', query);
+    },
+    // 获取学科分类列表
+    getList(query?: object) {
+      return request.postRequest(basePath, 'SciBaseDiscipline', 'GetList', query);
+    },
+  }
+}

+ 1 - 0
src/layout/entry.vue

@@ -10,6 +10,7 @@
   <router-view></router-view>
   <van-tabbar route :placeholder="true" v-if="route.path !== '/entry/add'">
     <van-tabbar-item replace to="/entry" icon="send-gift-o">入室申请</van-tabbar-item>
+    <van-tabbar-item replace to="/entry/manage" icon="send-gift-o">入室管理</van-tabbar-item>
     <van-tabbar-item replace to="/entry/mine" icon="coupon-o">我的入室</van-tabbar-item>
     <!-- <van-tabbar-item replace to="/entry/appoint" icon="cluster-o">入室预约</van-tabbar-item> -->
   </van-tabbar>

+ 7 - 0
src/main.ts

@@ -10,13 +10,20 @@ import { createApp } from 'vue'
 import App from './App.vue'
 import 'vant/es/toast/style'
 import 'vant/es/dialog/style'
+import ElementPlus from 'element-plus'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import 'element-plus/dist/index.css'
 import router from './router'
 import './theme/index.scss'
 import './theme/vant.scss'
 import pinia from '/@/stores/index';
 import { directive } from '/@/directive/index';
 const app = createApp(App)
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component)
+}
 directive(app)
 app.use(pinia)
 app.use(router)
+app.use(ElementPlus)
 app.mount('#app')

+ 9 - 1
src/router.ts

@@ -198,6 +198,14 @@ const routes = [
           title: '入室申请'
         }
       },
+      {
+        name: 'entryManage',
+        path: '/entry/manage',
+        component: () => import('/@/view/entry/manage.vue'),
+        meta: {
+          title: '入室管理'
+        }
+      },
       {
         name: 'entryMine',
         path: '/entry/mine',
@@ -329,7 +337,7 @@ router.beforeEach(async (to, from, next) => {
       if (!storesUseUserInfo.userInfos.id) {
         await storesUseUserInfo.setUserInfos()
       }
-      if(to.path == '/login') {
+      if (to.path == '/login') {
         const code = to.query.code
         storesUseUserInfo.setOpenId(code as string)
         next('/home')

+ 31 - 0
src/utils/base64ToFile.js

@@ -0,0 +1,31 @@
+
+const util = {
+    // 创建一个a标签,并做下载点击事件
+    downloadFile: function (blob, fileName) {
+        const link = document.createElement('a')
+        link.href = window.URL.createObjectURL(blob)
+        link.download = fileName
+        // 此写法兼容可火狐浏览器
+        document.body.appendChild(link)
+        const evt = document.createEvent('MouseEvents')
+        evt.initEvent('click', false, false)
+        link.dispatchEvent(evt)
+        document.body.removeChild(link)
+    },
+    // 将Base64文件转为 Blob
+    buildBlobByByte: function (data) {
+        const raw = window.atob(data)
+        const rawLength = raw.length
+        const uInt8Array = new Uint8Array(rawLength)
+        for (let i = 0; i < rawLength; ++i) {
+            uInt8Array[i] = raw.charCodeAt(i)
+        }
+        return new Blob([uInt8Array])
+    },
+    // 二进制数组 生成文件
+    downloadFileByByte: function (data, fileName) {
+        const blob = this.buildBlobByByte(data)
+        this.downloadFile(blob, fileName)
+    }
+}
+export default util

+ 649 - 0
src/view/entry/components/allocate.vue

@@ -0,0 +1,649 @@
+<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%">
+			<header class="mb8">
+				<span>资源分配</span>
+				<el-form inline size="mini" label-width="0">
+					<el-form-item label="资源组" label-width="60px">
+						<el-select v-model="groupId" placeholder="全部资源组" clearable style="width: 120px" @change="filterOutListByGroupId">
+							<el-option v-for="item in molecularGroupList" :key="item.id" :label="item.groupName" :value="item.id"></el-option>
+						</el-select>
+					</el-form-item>
+					<el-form-item class="ml20">
+						<el-radio-group v-model="resClass" size="small" @change="filterOutList">
+							<el-radio label="">全部</el-radio>
+							<el-radio v-for="item in cellType" :key="item.dictValue" :label="item.dictValue">{{ item.dictLabel }}</el-radio>
+						</el-radio-group>
+					</el-form-item>
+					<!-- <el-form-item class="ml12">
+						<el-button color="#2c78ff" @click="onSubmit('20')" size="default">分配</el-button>
+					</el-form-item> -->
+				</el-form>
+			</header>
+			<div class="table-container">
+				<div class="confirm-table">
+					<el-alert :title="`待分配队列(已确认${comfirmTotal}人)`" type="warning" :closable="false" class="mb8" />
+					<el-empty v-if="state.confirmList.length == 0" description="暂无队列数据"></el-empty>
+					<ul v-else>
+						<li v-for="(item, index) in state.confirmList" :key="index" :class="{ active: index === state.active }" @click="state.active = index">
+							<!-- <el-checkbox v-model="item.checked" class="mr8"></el-checkbox> -->
+							<div class="text">
+								<p>
+									分配房间: <span class="assignRoom">{{ item.resName }}</span>
+								</p>
+								<p class="mb4 mt4">
+									{{ item.memberName }}
+									【{{ formatDate(new Date(item.appointStartDate), 'YYYY-mm-dd') }}~{{ formatDate(new Date(item.appointEndDate), 'mm-dd') }}】
+								</p>
+								<p>【{{ getDictLabel(cellType, item.cellSourceType) }}】{{ formatDate(new Date(item.queueTime), 'YYYY-mm-dd HH:MM:SS') }}</p>
+							</div>
+							<!-- <el-icon v-if="item.checked">
+								<ele-Select />
+							</el-icon> -->
+							<div class="btns">
+								<el-button v-if="item.resId" link type="primary" @click="cancelAllocate(item)">取消</el-button>
+								<el-button link type="primary" @click="onDetail(item)">查看</el-button>
+							</div>
+						</li>
+					</ul>
+				</div>
+				<div class="out-table mr12">
+					<el-alert :title="`资源列表(共${state.resourceList.length}个)`" type="warning" :closable="false" class="mb8" />
+					<div class="out-table-wrap">
+						<el-row :gutter="20" class="out-table-wrap-row">
+							<!-- 资源列表 -->
+							<el-col :span="24" class="resourceList-col">
+								<el-row :gutter="10" v-if="state.resourceList.length">
+									<el-col :span="8" v-for="item in state.resourceList" :key="item.id" class="mb8" @click="onSelectResource(item)">
+										<div class="items" :class="{ active: curSelectedResourceId === item.id }">
+											<header>
+												<div class="title">
+													<span class="mr8">{{ item.resName }}</span>
+													<!-- <el-checkbox v-model="item.checked" class="mr8"></el-checkbox> -->
+												</div>
+												<div class="btns">
+													<el-tag size="small">{{ getDictLabel(cellType, item.resClass) }}</el-tag>
+												</div>
+											</header>
+											<p>{{ item.resLocation }}</p>
+											<footer>
+												<p>{{ item.usingSource || 0 }}/{{ item.maxNum }}</p>
+												<ul>
+													<li v-for="(p, index) in item.usedList" :key="index" :class="getUsedClass(p)"></li>
+												</ul>
+											</footer>
+										</div>
+									</el-col>
+								</el-row>
+							</el-col>
+						</el-row>
+					</div>
+				</div>
+				<div class="use-table out-table">
+					<el-alert title="房间使用情况" type="warning" :closable="false" class="mb8" />
+					<div class="out-table-wrap">
+						<el-row :gutter="20" class="out-table-wrap-row">
+							<!-- 资源列表 -->
+							<el-col :span="24" class="allocation-situation-col">
+								<!-- 资源分配情况页面 -->
+								<div class="allocation-situation">
+									<ul class="position-choose">
+										<li v-for="(p, index) in allocationSituationList" :key="index" class="flex">
+											<i :class="getUsedClass(p)"></i>
+											<!-- <span v-if="p.userObj"
+													>{{ p.userObj.memberName }}【{{ formatDate(new Date(new Date(p.userObj.appointStartDate)), 'YYYY-mm-dd') }}~{{
+														formatDate(new Date(new Date(p.userObj.appointEndDate)), 'mm-dd')
+													}}】</span
+												>
+												<span v-else-if="p.userName"
+													>{{ p.userName }}【{{ formatDate(new Date(p.startTime), 'YYYY-mm-dd') }}~{{
+														formatDate(new Date(p.endTime), 'mm-dd')
+													}}】</span
+												> -->
+											<div class="txt-flex">
+												<span v-if="p.userObj" :title="p.userObj.memberName">{{ p.userObj.memberName }}</span>
+												<span v-else-if="p.userName" :title="p.userName">{{ p.userName }}</span>
+												<span v-else>空闲</span>
+											</div>
+											<el-button v-if="!p.userObj && (p.assignStatus == '10' || p.assignStatus == '40')" link type="primary" @click="onAllocate(p)"
+												>分配</el-button
+											>
+										</li>
+									</ul>
+								</div>
+							</el-col>
+						</el-row>
+					</div>
+				</div>
+			</div>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button type="info" @click="onCancel" size="default">取 消</el-button>
+					<!-- <el-button type="success" @click="handelBatchAssign()" size="default">一键分配</el-button> -->
+					<el-button color="#2c78ff" @click="onSubmit('20')" size="default">确 定</el-button>
+				</span>
+			</template>
+		</el-dialog>
+		<DetailsDialog ref="detailsDialog" />
+	</div>
+</template>
+
+<script setup lang="ts" name="entryAllocate">
+import to from 'await-to-js';
+import { reactive, ref, defineAsyncComponent, computed } from 'vue';
+import { getDictLabel } from '/@/utils/other';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import { usePlatformApi } from '/@/api/platform/home';
+import { useSystemApi } from '/@/api/platform/system';
+import { useUserInfo } from '/@/stores/userInfo';
+import { storeToRefs } from 'pinia';
+import { useDictApi } from '/@/api/base/system/dict';
+import { useCellAssignApi } from '/@/api/platform/home/assign';
+import { formatDate } from '/@/utils/formatTime';
+
+const DetailsDialog = defineAsyncComponent(() => import('/@/view/entry/components/details.vue'));
+const stores = useUserInfo();
+const { userInfos } = storeToRefs(stores);
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+
+// 定义变量内容
+const systemApi = useSystemApi();
+const platformApi = usePlatformApi();
+const dictApi = useDictApi();
+const userTypeList = ref<RowDicDataType[]>([]);
+const projectGroupList = ref<any[]>([]);
+const cellType = ref<RowDicDataType[]>([]);
+const resourceList = ref<any[]>([]);
+const molecularGroupList = ref<any[]>([]);
+const cellAssignApi = useCellAssignApi();
+
+const state = reactive({
+	form: {
+		id: 0,
+		memberId: 0,
+		memberName: '',
+		memberPhone: '',
+		memberSex: '',
+		memberNo: '',
+		memberIden: '',
+		startDate: '',
+		endDate: '',
+		memberType: '',
+		deptId: 0,
+		deptName: '',
+		workPlace: '',
+		mentorId: 0,
+		mentorName: '',
+		mentorPhone: '',
+		mentorDeptId: 0,
+		mentorDeptName: '',
+		platformId: 0,
+		platformName: '',
+		platformType: '',
+		platformTime: 0,
+		other: '',
+		isTemporary: '',
+	},
+	confirmList: [] as any[],
+	resourceList: [] as any[],
+	active: -1,
+	dialog: {
+		isShowDialog: false,
+		type: '',
+		title: '',
+		submitTxt: '',
+	},
+});
+const comfirmTotal = computed(() => {
+	return state.confirmList.reduce((pre: number, cur: any) => {
+		if (cur.resId) return pre + 1;
+		return pre;
+	}, 0);
+});
+const confirmList = ref<any[]>([]);
+const belongOrgOption = ref<DeptTreeType[]>([]);
+const userList = ref<RowUserType[]>([]);
+const platformList = ref();
+const resClass = ref('');
+const groupId = ref(null); // 资源组ID
+const detailsDialog = ref();
+const curSelectedResourceId = ref(0); // 当前选中的资源ID
+const allocationSituationList = ref<any[]>([]); // 资源分配情况列表
+const getDicts = () => {
+	Promise.all([
+		systemApi.getDeptTree(),
+		systemApi.getUserList({ noPage: true }),
+		platformApi.getAllPlatformList({ noPage: true }),
+		dictApi.getDictDataByType('sys_user_type'),
+		systemApi.getProjectGroupList({ noPage: true }),
+		userInfos.value.platformId != 0 ? platformApi.getResourceTypeDict({ id: userInfos.value.platformId }) : '',
+	]).then(([dept, user, plat, type, pjt, cell]) => {
+		belongOrgOption.value = dept?.data || [];
+		userList.value = user?.data?.list || [];
+		platformList.value = plat?.data?.list || [];
+		userTypeList.value = type?.data?.values || [];
+		projectGroupList.value = pjt?.data?.list || [];
+		cellType.value = cell?.data || [];
+	});
+};
+const getMolecularGroupList = async () => {
+	const [err, res]: ToResponse = await to(platformApi.getMolecularGroupList({ id: userInfos.value.platformId }));
+	if (err) return;
+	molecularGroupList.value = res?.data || [];
+};
+const getResourceList = async () => {
+	const [err, res]: ToResponse = await to(platformApi.getResourceList({ platformId: userInfos.value.platformId, resStatus: '10' }));
+	if (err) return;
+	resourceList.value = res?.data?.list || [];
+	state.resourceList = JSON.parse(JSON.stringify(resourceList.value));
+};
+// 待分配列表
+const getConfirmList = async () => {
+	const [err, res]: ToResponse = await to(cellAssignApi.assignQueue({ platformId: userInfos.value.platformId }));
+	if (err) return;
+	confirmList.value = res?.data || [];
+	state.confirmList = JSON.parse(JSON.stringify(confirmList.value));
+};
+// 打开弹窗
+const openDialog = async (type: string, row: number) => {
+	getDicts();
+	getResourceList();
+	getConfirmList();
+	getMolecularGroupList();
+	state.dialog.type = type;
+	state.dialog.title = '入室资源分配';
+	state.dialog.isShowDialog = true;
+};
+// 前端筛选类型
+const filterOutList = (val: string) => {
+	if (val) {
+		state.resourceList = resourceList.value.filter((item: any) => item.resClass == val);
+		state.confirmList = confirmList.value.filter((item: any) => item.cellSourceType == val);
+	} else {
+		state.resourceList = [...resourceList.value];
+		state.confirmList = [...confirmList.value];
+	}
+};
+// 前端筛选资源组
+const filterOutListByGroupId = (val: string) => {
+	if (val) {
+		state.resourceList = resourceList.value.filter((item: any) => item.groupId == val);
+	} else {
+		state.resourceList = [...resourceList.value];
+	}
+};
+// 选择资源房间
+const onSelectResource = (row: any) => {
+	curSelectedResourceId.value = row.id;
+	allocationSituationList.value = row.usedList;
+};
+// 确认
+const onConfirm = (row: any) => {
+	// 验证同类型 下个月出室人员 = 已确认人数 提示无法确认
+};
+// 关闭弹窗
+const closeDialog = () => {
+	state.resourceList = [];
+	state.confirmList = [];
+	resourceList.value = [];
+	confirmList.value = [];
+	state.dialog.isShowDialog = false;
+};
+// 取消
+const onCancel = () => {
+	closeDialog();
+};
+
+const handelBatchAssign = async () => {
+	const checkedUser = state.confirmList.filter((item: any) => item.checked);
+	console.log(checkedUser);
+	if (!checkedUser.length) {
+		ElMessage.warning('请选择待分配队列人员');
+		return;
+	}
+	ElMessageBox.confirm(`是否将选中队列人员一键分配资源?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+	})
+		.then(async () => {
+			const [err]: ToResponse = await to(platformApi.batchAssign({ appointIds: checkedUser.map((item: any) => item.id) }));
+			if (err) return;
+			ElMessage.warning('一键分配成功');
+			getResourceList();
+			getConfirmList();
+		})
+		.catch(() => {});
+};
+// 提交
+const onSubmit = async (type: string) => {
+	const arr = confirmList.value.filter((item: any) => item.resId);
+	if (!arr.length) {
+		ElMessage.warning('请分配资源');
+		return;
+	}
+
+	const [err]: ToResponse = await to(
+		cellAssignApi.create({
+			assignList: arr.map((item: any) => {
+				return {
+					appointId: item.id,
+					resId: item.resId,
+					location: item.location,
+					replaceId: item.replaceId,
+					mainId: item.id || 0,
+					platformType:item.platformType || '10',
+				};
+			}),
+		})
+	);
+	if (err) return;
+	ElMessage.success('操作成功');
+	emit('refresh');
+	closeDialog();
+};
+const onDetail = (val: number) => {
+	detailsDialog.value.openDialog('get', val);
+};
+// 分配
+const onAllocate = (pos: any) => {
+	if (state.active < 0) {
+		ElMessage.warning('请先选择一个待分配人员');
+		return;
+	}
+	const activeObj = state.confirmList[state.active];
+	const room = state.resourceList.find((item: any) => item.id == curSelectedResourceId.value);
+	if (room.resClass != activeObj.cellSourceType) {
+		ElMessage.warning('请选择正确的资源');
+		return;
+	}
+	activeObj.resId = room.id;
+	activeObj.resName = room.resName;
+	activeObj.location = pos.location;
+	activeObj.replaceId = pos.id;
+	const user = confirmList.value.find((item: any) => item.id == activeObj.id);
+	user.resId = room.id;
+	user.resName = room.resName;
+	user.location = pos.location;
+	user.replaceId = pos.id;
+	pos.userObj = { ...activeObj };
+	const resource = resourceList.value.find((item: any) => item.id == room.id);
+	const used = resource.usedList.find((item: any) => item.location == pos.location);
+	used.userObj = { ...activeObj };
+};
+// 取消分配
+const cancelAllocate = (obj: any) => {
+	for (const room of resourceList.value) {
+		if (room.id == obj.resId) {
+			const used = room.usedList.find((i: any) => {
+				return i.location == obj.location;
+			});
+			used.userObj = null;
+		}
+	}
+	const user = confirmList.value.find((item) => item.resId == obj.resId);
+	user.resId = null;
+	user.resName = null;
+	user.location = null;
+	user.replaceId = null;
+	filterOutList(resClass.value);
+};
+const getUsedClass = (row: any) => {
+	if (row.assignStatus == '10') {
+		// 空
+		if (row.userObj) return 'empty-allocate';
+		return 'empty';
+	} else if (row.assignStatus == '40') {
+		// 次月离室
+		if (row.userObj) return 'leave-allocate';
+		return 'leave';
+	} else {
+		// 占用20 已分配45
+		return 'used';
+	}
+};
+// 暴露变量
+defineExpose({
+	openDialog,
+});
+</script>
+<style lang="scss" scoped>
+h4 {
+	font-size: 18px;
+}
+
+header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	height: 32px;
+	line-height: 32px;
+
+	.el-form.el-form--inline .el-form-item {
+		margin: 0;
+	}
+}
+
+.table-container {
+	/* display: flex; */
+	.use-table {
+		/* width: 240px !important; */
+	}
+	.out-table {
+		/* width: 848px; */
+		.out-table-wrap {
+			height: 500px;
+			.out-table-wrap-row {
+				height: 100%;
+			}
+			.resourceList-col {
+				height: 100%;
+				overflow: auto;
+			}
+		}
+		.items {
+			height: 146px;
+			padding: 12px;
+			border-radius: 4px;
+			background-color: #f2f2f2;
+			display: flex;
+			flex-direction: column;
+			justify-content: space-between;
+			overflow: hidden;
+			cursor: pointer;
+			&:hover {
+				box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3);
+			}
+			&.active {
+				background: #ebf3ff;
+				box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3);
+			}
+			header {
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+
+				.title {
+					display: flex;
+					align-items: center;
+
+					.el-switch {
+						line-height: 24px;
+					}
+				}
+			}
+
+			footer {
+				display: flex;
+				align-items: center;
+
+				ul {
+					flex: 1;
+					list-style: none;
+					display: flex;
+					flex-wrap: wrap;
+
+					li {
+						flex: 0 0 20%;
+						display: flex;
+						justify-content: center;
+						margin-bottom: 2px;
+
+						&::before {
+							display: block;
+							content: '';
+							height: 10px;
+							width: 10px;
+							border-radius: 50%;
+							background-color: #70b603;
+						}
+
+						&.used::before {
+							background-color: #fe5d5a;
+						}
+						&.empty-allocate::before {
+							background: linear-gradient(to right, #70b603 0%, #70b603 50%, #2c78ff 50%, #2c78ff 100%);
+						}
+						&.leave::before {
+							background-color: #f59a23;
+						}
+						&.leave-allocate::before {
+							background: linear-gradient(to right, #f59a23 0%, #f59a23 50%, #2c78ff 50%, #2c78ff 100%);
+						}
+					}
+				}
+			}
+
+			.el-icon {
+				font-size: 20px !important;
+			}
+		}
+	}
+}
+
+.confirm-table {
+	margin-right: 12px;
+	width: 300px;
+	display: flex;
+	flex-direction: column;
+	ul {
+		height: 500px;
+		overflow-y: auto;
+		li {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			border-radius: 4px;
+			border: 1px solid #eee;
+			padding: 8px;
+			cursor: pointer;
+			& + li {
+				margin-top: 8px;
+			}
+			&.active {
+				background-color: #ebf3ff;
+			}
+			.text {
+				flex: 1;
+			}
+			.assignRoom {
+				font-weight: bold;
+				color: #2c78ff;
+			}
+			.el-icon {
+				color: #fff;
+				background-color: #0dda83;
+				font-size: 20px;
+				padding: 4px;
+				border-radius: 50%;
+				margin: 0 12px;
+			}
+			.btns {
+				display: flex;
+				flex-direction: column;
+				.el-button {
+					margin-left: 0;
+				}
+			}
+		}
+	}
+}
+
+: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;
+}
+.allocation-situation-col {
+	height: 100%;
+	.allocation-situation {
+		height: 100%;
+		overflow: auto;
+		.position-choose {
+			list-style: none;
+			border: 1px solid #eee;
+			height: 100%;
+			overflow: auto;
+			li {
+				height: 32px;
+				line-height: 32px;
+				display: flex;
+				// justify-content: space-between;
+				align-items: center;
+				.txt-flex {
+					flex: 1;
+					white-space: nowrap;
+					overflow: hidden;
+					text-overflow: ellipsis;
+				}
+				i {
+					display: inline-block;
+					height: 10px;
+					width: 10px;
+					border-radius: 50%;
+					margin: 0 4px;
+					background-color: #70b603;
+					&.used {
+						background-color: #fe5d5a;
+					}
+					&.empty-allocate {
+						background: linear-gradient(to right, #70b603 0%, #70b603 50%, #2c78ff 50%, #2c78ff 100%);
+					}
+					&.leave {
+						background-color: #f59a23;
+					}
+					&.leave-allocate {
+						background: linear-gradient(to right, #f59a23 0%, #f59a23 50%, #2c78ff 50%, #2c78ff 100%);
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 454 - 0
src/view/entry/components/confirm.vue

@@ -0,0 +1,454 @@
+<!--
+ * @Author: wanglj wanglijie@dashoo.cn
+ * @Date: 2025-02-27 14:50:41
+ * @LastEditors: wanglj wanglijie@dashoo.cn
+ * @LastEditTime: 2025-03-06 18:28:53
+ * @Description: file content
+ * @FilePath: \labsop_backup2\frontend\packages\platform\src\views\entry\components\confirm.vue
+-->
+<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%">
+      <header class="mb8">
+        <span>确认</span>
+        <el-form inline size="mini" label-width="0">
+          <el-form-item>
+            <el-radio-group v-model="resClass" v-if="cellType.length > 1" size="small" @change="filterOutList">
+              <el-radio label="">全部</el-radio>
+              <el-radio v-for="item in cellType" :key="item.dictValue" :label="item.dictValue">{{ item.dictLabel }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item class="ml12">
+            <el-button color="#2c78ff" @click="autoConfirm()" size="default" :loading="autoFlag">自动确认</el-button>
+          </el-form-item>
+        </el-form>
+      </header>
+      <div class="table-container">
+        <div class="out-table">
+          <el-alert :title="`下个月出室人员(共${state.outList.length}人)`" type="warning" :closable="false" class="mb8" />
+
+          <van-list finished-text="没有更多了">
+            <van-cell v-for="item in state.outList" :key="item">
+              <template #default>
+                <div class="list">
+                  <p class="inst-title">
+                    <span>出室人员</span>
+                    <span class="title ml8">{{ item.userName }}</span>
+                  </p>
+                  <p class="inst-title">
+                    <span>联系方式</span>
+                    <span class="title ml8">{{ item.memberPhone }}</span>
+                  </p>
+                  <p class="inst-title">
+                    <span>预约平台</span>
+                    <span class="title ml8">
+                      {{ item.platformName }}
+                    </span>
+                  </p>
+                  <p class="inst-title">
+                    <span>分配类型</span>
+                    <span class="title ml8">
+                      {{ item.resClass }}
+                    </span>
+                  </p>
+                  <p class="inst-title">
+                    <span>分配房间</span>
+                    <span class="title ml8">
+                      {{ item.resName }}
+                    </span>
+                  </p>
+                  <p class="inst-title">
+                    <span>入室日期</span>
+                    <span class="title ml8"> {{ item.startTime }}个月 </span>
+                  </p>
+                  <p class="inst-title">
+                    <span>出室日期</span>
+                    <span class="title ml8">{{ item.endTime }}</span>
+                  </p>
+                  <p class="inst-title">
+                    <span>课题组</span>
+                    <span class="title ml8">{{ item.pgName }}</span>
+                  </p>
+                  <p class="inst-title">
+                    <span>课题组负责人</span>
+                    <span class="title ml8">{{ item.mentorName }}</span>
+                  </p>
+                  <footer class="flex justify-end mt4 actions-footer"></footer>
+                </div>
+              </template>
+            </van-cell>
+          </van-list>
+
+          <!-- <vxe-table :height="500" :data="state.outList" ref="outRef" show-overflow="title" border align="center">
+            <vxe-column type="seq" title="序号" width="60" align="center" fixed="left" />
+            <vxe-column field="userName" title="出室人员" width="140"></vxe-column>
+            <vxe-column field="memberPhone" title="联系方式" width="120"></vxe-column>
+            <vxe-column field="platformName" title="预约平台" width="120"></vxe-column>
+            <vxe-column field="resClass" title="分配类型" width="100">
+              <template #default="{ row }">{{ getDictLabel(cellType, row.resClass) }}</template>
+            </vxe-column>
+            <vxe-column field="resName" title="分配房间" width="140"></vxe-column>
+            <vxe-column field="startTime" title="入室日期" width="120">
+              <template #default="{ row }">{{ formatDate(new Date(row.startTime), 'YYYY-mm-dd') }}</template>
+            </vxe-column>
+            <vxe-column field="endTime" title="出室日期" width="120">
+              <template #default="{ row }">{{ formatDate(new Date(row.endTime), 'YYYY-mm-dd') }}</template>
+            </vxe-column>
+            <vxe-column field="pgName" title="课题组" width="100"></vxe-column>
+            <vxe-column field="mentorName" title="课题组负责人" width="120"></vxe-column>
+          </vxe-table> -->
+        </div>
+        <div class="confirm-table">
+          <el-alert :title="`待确认队列 (已确认${comfirmTotal}人)`" type="warning" :closable="false" class="mb8" />
+          <ul>
+            <li v-for="(item, index) in state.confirmList" :key="index">
+              <div class="text">
+                <p>
+                  {{ item.memberName }}【{{ formatDate(new Date(item.appointStartDate), 'mm-dd') }}~{{
+                    formatDate(new Date(item.appointEndDate), 'mm-dd')
+                  }}】-{{ getDictLabel(cellType, item.cellSourceType) }}
+                </p>
+                <p>{{ item.pgName }}-{{ item.mentorName }}</p>
+              </div>
+              <el-icon v-if="item.checked">
+                <ele-Select />
+              </el-icon>
+              <div class="btns">
+                <el-button v-if="!item.checked" link type="primary" @click="onConfirm(item)">确认</el-button>
+                <el-button v-else link @click="item.checked = false">取消</el-button>
+                <el-button link type="primary" @click="onDetail(item)">查看</el-button>
+              </div>
+            </li>
+          </ul>
+        </div>
+      </div>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="info" @click="onCancel" size="default">取 消</el-button>
+          <el-button color="#2c78ff" @click="onSubmit()" size="default">确 定</el-button>
+        </span>
+      </template>
+    </el-dialog>
+    <DetailsDialog ref="detailsDialog" />
+  </div>
+</template>
+
+<script setup lang="ts" name="systemProDialog">
+  import to from 'await-to-js'
+  import { defineAsyncComponent, reactive, ref, computed, onMounted } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { usePlatformAppointApi } from '/@/api/platform/appoint'
+  import { usePlatformApi } from '/@/api/platform/home'
+  import { useSystemApi } from '/@/api/platform/system'
+  import { deepClone, getDictLabel } from '/@/utils/other'
+  import { useUserInfo } from '/@/stores/userInfo'
+  import { storeToRefs } from 'pinia'
+  import { useDictApi } from '/@/api/base/system/dict'
+  import { useCellAssignApi } from '/@/api/platform/home/assign'
+  import { formatDate } from '/@/utils/formatTime'
+
+  const DetailsDialog = defineAsyncComponent(() => import('/@/view/entry/components/details.vue'))
+
+  const stores = useUserInfo()
+  const { userInfos } = storeToRefs(stores)
+
+  // 定义子组件向父组件传值/事件
+  const emit = defineEmits(['refresh'])
+
+  // 定义变量内容
+  const Api = usePlatformAppointApi()
+  const systemApi = useSystemApi()
+  const platformApi = usePlatformApi()
+  const cellAssignApi = useCellAssignApi()
+  const dictApi = useDictApi()
+  const deptRef = ref()
+  const detailsDialog = ref()
+  const userTypeList = ref<RowDicDataType[]>([])
+  const projectGroupList = ref<any[]>([])
+  const cellType = ref<RowDicDataType[]>([])
+
+  const rules = {
+    memberName: { required: true, message: '不能为空', trigger: 'blur' },
+    memberType: { required: true, message: '不能为空', trigger: 'change' },
+    deptId: { required: true, message: '不能为空', trigger: 'change' },
+    platformName: { required: true, message: '不能为空', trigger: 'blur' },
+    platformTime: { required: true, message: '不能为空', trigger: 'blur' },
+    startDate: { required: true, message: '不能为空', trigger: 'change' },
+    endDate: { required: true, message: '不能为空', trigger: 'change' },
+    mentorName: { required: true, message: '不能为空', trigger: 'blur' }
+  }
+  const state = reactive({
+    form: {
+      id: 0,
+      deptId: null,
+      deptName: '',
+      isTemporary: '10',
+      memberId: 0,
+      memberName: '',
+      memberPhone: '',
+      memberType: '',
+      memberDeptId: null,
+      memberDeptName: '',
+      mentorObj: null,
+      pgId: null,
+      pgName: '',
+      mentorId: null,
+      mentorName: '',
+      mentorDeptName: '',
+      mentorPhone: '',
+      platformId: null,
+      platformName: '',
+      platformTime: null,
+      platformType: '',
+      isCellChecked: '20',
+      cellType: '',
+      cellSourceType: '',
+      isMolecularChecked: '20',
+      molecularTime: null,
+      platOtherNeed: ''
+    },
+    outList: [] as any[],
+    confirmList: [] as any[],
+    dialog: {
+      isShowDialog: false,
+      type: '',
+      title: '',
+      submitTxt: ''
+    }
+  })
+  const autoFlag = ref(false)
+  const comfirmTotal = computed(() => {
+    return state.confirmList.reduce((pre: number, cur: any) => {
+      if (cur.checked) return pre + 1
+      return pre
+    }, 0)
+  })
+  const outList = ref<any[]>([])
+  const confirmList = ref<any[]>([])
+  const belongOrgOption = ref<DeptTreeType[]>([])
+  const userList = ref<RowUserType[]>([])
+  const resClass = ref('')
+  const platform = ref()
+  const getDicts = async () => {
+    Promise.all([
+      systemApi.getDeptTree(),
+      systemApi.getUserList({ noPage: true }),
+      platformApi.getDetail({ id: userInfos.value.platformId }),
+      dictApi.getDictDataByType('sys_user_type'),
+      systemApi.getProjectGroupList({ noPage: true }),
+      userInfos.value.platformId != 0 ? platformApi.getResourceTypeDict({ id: userInfos.value.platformId }) : ''
+    ]).then(([dept, user, plat, type, pjt, cell]) => {
+      belongOrgOption.value = dept?.data || []
+      userList.value = user?.data?.list || []
+      userTypeList.value = type?.data?.values || []
+      projectGroupList.value = pjt?.data?.list || []
+      cellType.value = cell?.data || []
+      platform.value = plat?.data || ''
+      getOutList()
+      getConfirmList()
+    })
+  }
+  // 打开弹窗
+  const openDialog = async (type: string, row: number) => {
+    await getDicts()
+    state.dialog.type = type
+    state.dialog.title = '入室资源确认'
+    state.dialog.isShowDialog = true
+  }
+  const getOutList = async () => {
+    const [err, res]: ToResponse = await to(Api.nextMonthPerson({ id: userInfos.value.platformId, platformType: platform.value.platformType }))
+    if (err) return
+    outList.value = res?.data || []
+    state.outList = JSON.parse(JSON.stringify(outList.value))
+  }
+  const getConfirmList = async () => {
+    const [err, res]: ToResponse = await to(Api.getConfirmedQueue({ id: userInfos.value.platformId, platformType: platform.value.platformType }))
+    if (err) return
+    confirmList.value = res?.data || []
+    state.confirmList = JSON.parse(JSON.stringify(confirmList.value))
+  }
+  // 前端筛选类型
+  const filterOutList = (val?: string) => {
+    if (val) {
+      state.outList = outList.value.filter((item: any) => item.resClass == val)
+      state.confirmList = confirmList.value.filter((item: any) => item.cellSourceType == val)
+    } else {
+      state.outList = [...outList.value]
+      state.confirmList = [...confirmList.value]
+    }
+  }
+  // 确认
+  const onConfirm = (row: any) => {
+    // 验证同类型 下个月出室人员 = 已确认人数 提示无法确认
+    const total = outList.value.filter((item: any) => item.resClass == row.cellSourceType).length
+
+    if (total == confirmList.value.filter((item: any) => item.cellSourceType == row.cellSourceType && item.checked).length) {
+      ElMessage.warning('该类型下已确认人数与出室人员人数一致,无法确认')
+      return
+    }
+    row.checked = true
+    const obj = confirmList.value.find((item: any) => item.id == row.id)
+    obj.checked = true
+  }
+  // 自动确认
+  const autoConfirm = () => {
+    autoFlag.value = true
+    // 按待确认列表顺序依次确认
+    filterOutList()
+    state.confirmList.forEach((item: any) => {
+      item.checked = false
+    })
+    for (const item of cellType.value) {
+      const total = state.outList.filter((i: any) => i.resClass == item.dictValue).length
+      const confirm = state.confirmList.filter((i: any) => i.cellSourceType == item.dictValue)
+      for (let i = 0; i < total; i++) {
+        confirm[i].checked = true
+      }
+    }
+    autoFlag.value = false
+  }
+  // 关闭弹窗
+  const closeDialog = () => {
+    state.outList = []
+    state.confirmList = []
+    outList.value = []
+    confirmList.value = []
+    state.dialog.isShowDialog = false
+  }
+  // 取消
+  const onCancel = () => {
+    closeDialog()
+  }
+  // 提交
+  const onSubmit = async () => {
+    // 验证 所有下个月出室人员 是否全部确认 二次确认
+    const total = state.outList.length
+    const arr = state.confirmList.filter((item: any) => item.checked)
+    // 获取对应下月出室人员后端标记 不再显示在列表中
+    const outArr: number[] = []
+    for (const item of cellType.value) {
+      const confirm = arr.filter((i: any) => i.cellSourceType == item.dictValue)
+      const out = state.outList.filter((i: any) => i.resClass == item.dictValue)
+      for (let i = 0; i < confirm.length; i++) {
+        if (out[i]) outArr.push(out[i].id)
+      }
+    }
+    let confirm: Error | null = null
+    if (arr.length < total) {
+      ;[confirm] = await to(
+        ElMessageBox.confirm('下个月出室人员未全部确认,是否继续提交?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+      )
+    }
+    if (confirm !== null) return
+    const [err]: ToResponse = await to(
+      Api.markNextMonthEntry({ ids: arr.map((item: any) => item.id), assignIds: outArr, platformType: platform.value.platformType })
+    )
+    if (err) return
+    ElMessage.success('操作成功')
+    emit('refresh')
+    closeDialog()
+  }
+  const onDetail = (val: number) => {
+    detailsDialog.value.openDialog('get', val)
+  }
+  // 暴露变量
+  defineExpose({
+    openDialog
+  })
+</script>
+<style lang="scss" scoped>
+  h4 {
+    font-size: 18px;
+  }
+  header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 32px;
+    line-height: 32px;
+    .el-form.el-form--inline .el-form-item {
+      margin: 0;
+    }
+  }
+  .table-container {
+    /* display: flex; */
+    .out-table {
+      /* width: 808px; */
+    }
+    .confirm-table {
+      margin-top: 12px;
+      /* width: 340px; */
+      display: flex;
+      flex-direction: column;
+      ul {
+        height: 500px;
+        overflow-y: auto;
+        li {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          border-radius: 4px;
+          border: 1px solid #eee;
+          padding: 8px;
+          & + li {
+            margin-top: 8px;
+          }
+          .text {
+            flex: 1;
+            overflow: hidden;
+            p {
+              white-space: nowrap;
+              overflow: hidden;
+              text-overflow: ellipsis;
+            }
+          }
+          .el-icon {
+            color: #fff;
+            background-color: #0dda83;
+            font-size: 20px;
+            padding: 4px;
+            border-radius: 50%;
+            margin: 0 12px;
+          }
+          .btns {
+            display: flex;
+            flex-direction: column;
+            .el-button {
+              margin-left: 0;
+            }
+          }
+        }
+      }
+    }
+  }
+  :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>

+ 271 - 0
src/view/entry/components/details.vue

@@ -0,0 +1,271 @@
+<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="mb10">申请人信息</h4>
+      <el-descriptions border :column="2" class="mb20" direction="vertical">
+        <el-descriptions-item label-class-name="cell-item" label="申请人姓名">{{ state.form.memberName }}</el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="申请人员类型">
+          {{ getDictLabel(userTypeList, state.form.memberType) }}
+        </el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="申请人手机号">{{ state.form.memberPhone }}</el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="申请人科室/工作单位">{{ state.form.deptName }}</el-descriptions-item>
+      </el-descriptions>
+      <h4 class="mb10">申请人课题组信息</h4>
+      <el-descriptions border :column="2" class="mb20" direction="vertical">
+        <el-descriptions-item label-class-name="cell-item" label="课题组">
+          {{ state.form.pgName }}
+        </el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="课题组负责人">{{ state.form.mentorName }}</el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="课题组负责人科室">{{ state.form.mentorDeptName }}</el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="课题组负责人电话">
+          {{ state.form.mentorPhone }}
+        </el-descriptions-item>
+      </el-descriptions>
+      <h4 class="mb10">申请入室平台</h4>
+      <el-descriptions
+        border
+        :column="2"
+        class="mb20"
+        direction="vertical"
+        v-for="itemCell in formatPlatformAppointCell(state.form.platPlatformAppointCellRes, state.form.platPlatformAppointMolecularRes)"
+        :key="itemCell"
+      >
+        <!-- <tempalte>
+					<div v-for="item in state.form.platPlatformAppointCellRes" :key="item.id">
+						<el-descriptions-item label-class-name="cell-item" label="申请平台"> {{ item.platformName }}</el-descriptions-item>
+						<el-descriptions-item label-class-name="cell-item" label="申请时长">{{ item.platformTime }}个月</el-descriptions-item>
+					</div>
+				</tempalte> -->
+
+        <el-descriptions-item label-class-name="cell-item" label="申请平台">
+          <tempalte>
+            <div>
+              {{ itemCell.platformName }}
+            </div>
+          </tempalte>
+        </el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="费用">
+          <tempalte>
+            <div>{{ itemCell.price }}元</div>
+          </tempalte>
+        </el-descriptions-item>
+        <el-descriptions-item label-class-name="cell-item" label="申请时长">
+          <tempalte>
+            <div>{{ itemCell.platformTime }}个月</div>
+          </tempalte>
+        </el-descriptions-item>
+
+        <tempalte v-if="state.form.platformTime">
+          <!-- <el-descriptions-item label-class-name="cell-item" label="申请平台"> 细胞 </el-descriptions-item>
+					<el-descriptions-item label-class-name="cell-item" label="申请时长">{{ state.form.platformTime }}个月</el-descriptions-item> -->
+          <el-descriptions-item label-class-name="cell-item" label="拟培养细胞种类">{{ state.form.cellType }}</el-descriptions-item>
+          <el-descriptions-item label-class-name="cell-item" label="细胞房预约需求">
+            {{ state.form.cellSourceType == '10' ? '普通' : '层流' }}
+          </el-descriptions-item>
+        </tempalte>
+        <template v-else>
+          <!-- <el-descriptions-item label-class-name="cell-item" label="申请平台">分子生物平台</el-descriptions-item>
+					<el-descriptions-item label-class-name="cell-item" label="申请时长"> {{ state.form.molecularTime }}个月 </el-descriptions-item> -->
+          <el-descriptions-item label-class-name="cell-item" label="其他需求">
+            {{ state.form.platOtherNeed }}
+          </el-descriptions-item>
+        </template>
+      </el-descriptions>
+      <template v-if="state.form.fileList?.length">
+        <h4 class="mb10">入室申请附件</h4>
+        <el-descriptions border :column="2" class="mb20" direction="vertical">
+          <el-descriptions-item v-for="item in state.form.fileList" label-class-name="cell-item" :label="item.fileName">
+            <el-link type="primary" target="_blank" :href="item.fileUrl">下载</el-link>
+          </el-descriptions-item>
+        </el-descriptions>
+      </template>
+      <h4 class="mb10">审批流</h4>
+      <FlowTable :id="state.form.id" :businessCode="state.form.id + ''" defCode="plat_platform_appoint" />
+      <template v-if="state.dialog.type == 'audit'">
+        <el-form class="mt20" ref="formRef" :model="state.auditForm" :rules="rules" size="default" label-width="140px" label-position="top">
+          <el-form-item label="审核结果" prop="approveResult">
+            <el-radio-group v-model="state.auditForm.approveResult" placeholder="请选择" clearable>
+              <el-radio label="10">通过</el-radio>
+              <el-radio label="20">不通过</el-radio>
+            </el-radio-group>
+          </el-form-item>
+          <el-form-item label="审核意见" prop="approveDesc">
+            <el-input
+              v-model="state.auditForm.approveDesc"
+              type="textarea"
+              :rows="3"
+              maxlength="300"
+              show-word-limit
+              placeholder="请输入审核意见"
+              clearable
+            ></el-input>
+          </el-form-item>
+        </el-form>
+      </template>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="info" @click="onCancel" size="default">取 消</el-button>
+          <el-button v-if="state.dialog.type == 'audit'" type="primary" @click="onAudit" size="default">确 认</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="systemProDialog">
+  import to from 'await-to-js'
+  import { defineAsyncComponent, nextTick, reactive, ref } from 'vue'
+  import { usePlatformAppointApi } from '/@/api/platform/appoint'
+  import { useSystemApi } from '/@/api/scientific/system'
+  import { getDictLabel } from '/@/utils/other' // 字典转换
+  import { ElMessage } from 'element-plus'
+  const emit = defineEmits(['refresh'])
+  const FlowTable = defineAsyncComponent(() => import('/@/components/FlowTable.vue'))
+
+  // 定义子组件向父组件传值/事件
+  // 定义变量内容
+  const Api = usePlatformAppointApi()
+  const sysApi = useSystemApi()
+  const formRef = ref()
+  const state = reactive({
+    form: {
+      id: 0,
+      deptId: null,
+      deptName: '',
+      isTemporary: '10',
+      memberId: 0,
+      memberName: '',
+      memberPhone: '',
+      memberType: '',
+      mentorObj: null,
+      pgId: null,
+      pgName: '',
+      mentorId: null,
+      mentorName: '',
+      mentorDeptName: '',
+      mentorPhone: '',
+      platformId: null,
+      platformName: '',
+      platformTime: null,
+      platformType: '',
+      platformList: [],
+      isCellChecked: '20',
+      cellType: '',
+      cellSourceType: '',
+      isMolecularChecked: '20',
+      molecularTime: null,
+      platOtherNeed: '',
+      platPlatformAppointCellRes: [],
+      platPlatformAppointMolecularRes: []
+    },
+    auditForm: {
+      id: 0,
+      platformType: '',
+      approveResult: '10',
+      approveDesc: ''
+    },
+    dialog: {
+      type: '',
+      isShowDialog: false,
+      title: '',
+      submitTxt: ''
+    }
+  })
+  const rules = {
+    approveResult: { required: true, message: '不能为空', trigger: 'change' },
+    approveDesc: { required: true, message: '不能为空', trigger: 'blur' }
+  }
+  const userTypeList = ref<RowDicDataType[]>([]) // 人员类型
+  const userSexList = ref<RowDicDataType[]>([]) // 人员性别
+  // 打开弹窗
+  const openDialog = async (type: string, row: any) => {
+    getDicts()
+    state.dialog.type = type
+    state.auditForm.platformType = row?.platformType
+    state.dialog.title = '入室申请'
+    if (type == 'manageDetails' || type == 'audit') {
+      const post = row?.platformType == '10' ? Api.getCellEntityById : Api.getMolecularEntityById
+      const [err, res]: ToResponse = await to(post({ id: row?.id }))
+      if (err) return
+      await nextTick()
+      state.form = res?.data
+    } else if (type === 'get') {
+      const [err, res]: ToResponse = await to(Api.getDetail({ id: row?.id }))
+      if (err) return
+      await nextTick()
+      state.form = res?.data
+    }
+    state.dialog.isShowDialog = true
+  }
+
+  const formatPlatformAppointCell = (cellRes: any[], molecularRes: any[]) => {
+    return [...(cellRes || []), ...(molecularRes || [])]
+  }
+
+  const getDicts = () => {
+    Promise.all([
+      sysApi.getDictDataByType('sys_user_type'), // 人员类型
+      sysApi.getDictDataByType('sys_com_sex') // 人员性别
+    ]).then(([type, sex]) => {
+      userTypeList.value = type?.data?.values || []
+      userSexList.value = sex?.data?.values || []
+    })
+  }
+
+  // 关闭弹窗
+  const closeDialog = () => {
+    state.form.id = 0
+    formRef.value?.resetFields()
+    state.dialog.isShowDialog = false
+  }
+
+  // 取消
+  const onCancel = () => {
+    closeDialog()
+  }
+  const onAudit = () => {
+    formRef.value.validate(async (valid: any) => {
+      if (valid) {
+        const params = JSON.parse(JSON.stringify(state.auditForm))
+        params.id = state.form.id
+        const [err]: ToResponse = await to(Api.updateById(params))
+        if (err) return
+        ElMessage.success('操作成功')
+        closeDialog()
+        emit('refresh')
+      }
+    })
+  }
+
+  // 暴露变量
+  defineExpose({
+    openDialog
+  })
+</script>
+<style lang="scss" scoped>
+  :deep(.disUoloadSty .el-upload--picture-card) {
+    display: none; /* 上传按钮隐藏 */
+  }
+  :deep(.cell-item) {
+    width: 25%;
+    & + .el-descriptions__cell,
+    & + .el-descriptions__content {
+      width: 25%;
+      white-space: pre-wrap;
+      word-wrap: break-word;
+    }
+  }
+  .el-upload-list--picture-card .el-upload-list__item-thumbnail {
+    height: 120px;
+  }
+  .fileName {
+    position: absolute;
+    width: 100%;
+    left: 0;
+    bottom: 0;
+    text-align: center;
+    font-size: 11px;
+    line-height: 1;
+    margin: 0;
+  }
+</style>

+ 235 - 0
src/view/entry/components/upload.vue

@@ -0,0 +1,235 @@
+<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%">
+			<el-form ref="expertDialogFormRef" :model="state.form" :rules="rules" size="default" label-width="140px" label-position="top">
+				<h4 class="mb8 mt8">上传须知</h4>
+				<div class="text">
+					<p class="mt10 mb10">
+						根据遵义医科大学附属医院临床医学公共实验中心要求,申请入室人员需要上传缴费单:通过审批后,点击缴费单按钮下载缴费单模板,打印并带到财务处完成缴费和确认后经财务盖章,将盖章的单据拍照上传。
+					</p>
+				</div>
+				<h4 class="mb8 mt8">信息上传</h4>
+				<el-form-item label="缴费单" prop="billList">
+					<el-upload :action="uploadUrl" :on-success="billSuccess" :limit="1" clearable class="w100" v-model:file-list="state.form.billList">
+						<template #trigger>
+							<el-button type="primary">点击上传</el-button>
+						</template>
+					</el-upload>
+				</el-form-item>
+				<el-form-item label="其它附件" prop="group">
+					<el-upload :action="uploadUrl" :on-success="otherSuccess" clearable class="w100" v-model:file-list="state.form.fileList">
+						<template #trigger>
+							<el-button type="primary">点击上传</el-button>
+						</template>
+					</el-upload>
+				</el-form-item>
+			</el-form>
+			<template #footer>
+				<span class="dialog-footer">
+					<el-button type="info" @click="onCancel" size="default">取 消</el-button>
+					<el-button color="#2c78ff" @click="onSubmit()" size="default">{{ state.dialog.submitTxt }}</el-button>
+				</span>
+			</template>
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts" name="systemProDialog">
+import to from 'await-to-js';
+import { reactive, ref } from 'vue';
+import { ElMessage, UploadFile, UploadFiles } from 'element-plus';
+import { usePlatformAppointApi } from '/@/api/platform/appoint';
+import { usePlatformApi } from '/@/api/platform/home';
+import { useSystemApi } from '/@/api/platform/system';
+import { deepClone } from '/@/utils/other';
+import { useUserInfo } from '/@/stores/userInfo';
+import { storeToRefs } from 'pinia';
+import { useDictApi } from '/@/api/base/system/dict';
+
+const stores = useUserInfo();
+const { userInfos } = storeToRefs(stores);
+
+// 定义子组件向父组件传值/事件
+const emit = defineEmits(['refresh']);
+
+// 定义变量内容
+const uploadUrl = import.meta.env.VITE_UPLOAD;
+const Api = usePlatformAppointApi();
+const systemApi = useSystemApi();
+const platformApi = usePlatformApi();
+const dictApi = useDictApi();
+const expertDialogFormRef = ref();
+const deptRef = ref();
+const mentorDept = ref();
+const userTypeList = ref<RowDicDataType[]>([]);
+const projectGroupList = ref<any[]>([]);
+const cellType = ref<RowDicDataType[]>([]);
+
+const rules = {
+	billList: { required: true, message: '不能为空', trigger: 'change' },
+};
+const state = reactive({
+	form: {
+		id: 0,
+		appointId: 0,
+    platformType: '',
+		billList: [] as UploadFiles,
+		fileList: [] as UploadFiles,
+	},
+	safePromise: false,
+	safeRead: false,
+	dialog: {
+		isShowDialog: false,
+		type: '',
+		title: '',
+		submitTxt: '',
+	},
+});
+const belongOrgOption = ref<DeptTreeType[]>([]);
+const userList = ref<RowUserType[]>([]);
+const platformList = ref();
+const getDicts = () => {
+	Promise.all([
+		systemApi.getDeptTree(),
+		systemApi.getUserList({ noPage: true }),
+		platformApi.getAllPlatformList({ noPage: true }),
+		dictApi.getDictDataByType('sys_user_type'),
+		systemApi.getProjectGroupList({ noPage: true }),
+		platformApi.getResourceTypeDict({ id: 10 }),
+	]).then(([dept, user, plat, type, pjt, cell]) => {
+		belongOrgOption.value = dept?.data || [];
+		userList.value = user?.data?.list || [];
+		platformList.value = plat?.data?.list || [];
+		userTypeList.value = type?.data?.values || [];
+		projectGroupList.value = pjt?.data?.list || [];
+		cellType.value = cell?.data || [];
+	});
+};
+// 打开弹窗
+const openDialog = async (row: any, type: 'add' | 'edit') => {
+	getDicts();
+	state.dialog.type = type;
+	if (type == 'add') {
+		state.form.appointId = row?.id;
+		state.form.platformType = row?.platformType;
+		state.dialog.title = '入室资料上传';
+		state.dialog.submitTxt = '提 交';
+	} else if (type == 'edit') {
+		state.dialog.title = '入室资料重新上传';
+		state.dialog.submitTxt = '修 改';
+    const post = row?.platformType == '10' ? Api.getCellEntityById : Api.getMolecularEntityById
+		const [err, res]: ToResponse = await to(post({ id: row?.id, platformType: row?.platformType }));
+		if (err) return;
+		const files = res?.data?.fileList || [];
+		let billList: UploadFiles = [];
+		if (files.length) {
+			const file = files.pop();
+			billList = [
+				{
+					name: file.fileName,
+					url: file.fileUrl,
+					status: 'success',
+					uid: 1,
+				},
+			];
+		}
+		state.form = {
+			id: res?.data.id,
+			appointId: res?.data?.appointId,
+      platformType: res?.data?.platformType,
+			billList,
+			fileList: files.map((item: any, idx: number) => {
+				return {
+					name: item.fileName,
+					url: item.fileUrl,
+					status: 'success',
+					uid: idx,
+				};
+			}),
+		};
+	}
+	state.dialog.isShowDialog = true;
+};
+// 关闭弹窗
+const closeDialog = () => {
+	expertDialogFormRef.value.resetFields();
+	state.dialog.isShowDialog = false;
+};
+// 取消
+const onCancel = () => {
+	closeDialog();
+};
+const billSuccess = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+	state.form.billList = [
+		{
+			uid: uploadFile.uid,
+			status: uploadFile.status,
+			name: uploadFile.name,
+			url: response.Data,
+		},
+	];
+};
+const otherSuccess = (response: any, uploadFile: UploadFile, uploadFiles: UploadFiles) => {
+	// const files = [...uploadFiles];
+	// files.shift();
+	// console.log(files, 'files');
+
+	// state.form.fileList = [
+	// 	...files,
+	// 	{
+	// 		uid: uploadFile.uid,
+	// 		status: uploadFile.status,
+	// 		name: uploadFile.name,
+	// 		url: response.Data,
+	// 	},
+	// ];
+	state.form.fileList = uploadFiles.map((item) => {
+		return {
+			uid: item.uid,
+			status: item.status,
+			name: item.name,
+			url: item?.response?.Data || item.url,
+		};
+	});
+};
+// 提交
+const onSubmit = async () => {
+	expertDialogFormRef.value.validate(async (valid: boolean) => {
+		if (!valid) return;
+		const params = deepClone(state.form);
+		params.fileList = [...params.billList, ...params.fileList].map((item) => {
+			return {
+				fileName: item.name,
+				fileUrl: item.url,
+			};
+		});
+		delete params.billList;
+		const post = state.dialog.type == 'add' ? Api.createFile : Api.updateByIdAfterRefuse;
+		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;
+}
+ul {
+	padding-left: 20px;
+}
+.text {
+	p {
+		text-indent: 2em;
+	}
+}
+.el-upload + .el-button {
+	vertical-align: top;
+}
+</style>

+ 330 - 0
src/view/entry/manage.vue

@@ -0,0 +1,330 @@
+<template>
+  <div class="entry-container">
+    <div class="list-container">
+      <div class="search-wrap" ref="searchWrapRef">
+        <el-form :model="state.queryParams" ref="queryRef">
+          <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" />
+            </div>
+          </el-form-item>
+
+          <el-form-item style="margin-bottom: 0" prop="serialNo">
+            <el-input v-model="state.queryParams.memberName" placeholder="入室申请名称" clearable @keyup.enter="search" />
+          </el-form-item>
+        </el-form>
+        <div class="buttons">
+          <el-button v-auth="'platform_entry_allocate'" color="#2c78ff" @click="onAllocate()">分配</el-button>
+          <el-button v-auth="'platform_entry_confirm'" color="#2c78ff" @click="onConfirm()">确认</el-button>
+        </div>
+      </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" @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.appointStatus === ApplicationStatus.IN_QUEUE" type="primary">队列中</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.PENDING_UPLOAD" type="primary">待上传</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.PENDING_REVIEW" type="primary">待审核</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.REJECTED" type="danger">已驳回</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.WITHDRAWN" type="warning">已撤回</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.PENDING_ASSIGNMENT" type="primary">待分配</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.AVAILABLE_FOR_ADMISSION" type="success">可入室</van-tag>
+                <van-tag v-else-if="item.appointStatus === ApplicationStatus.OUT_OF_ROOM" type="primary">已出室</van-tag>
+                <van-tag v-else type="primary">初始</van-tag>
+              </header>
+              <p class="inst-title">
+                <span>课题名称</span>
+                <span class="title ml8">{{ item.pgName }}</span>
+              </p>
+              <p class="inst-title">
+                <span>审批状态</span>
+
+                <span class="title ml8" v-if="item.approveStatus === ApproveStatus.NEED_SUBMIT">待提交</span>
+                <span class="title ml8" v-else-if="item.approveStatus === ApproveStatus.IN_REVIEW">审批中</span>
+                <span class="title ml8" v-else-if="item.approveStatus === ApproveStatus.APPROVED">审批通过</span>
+                <span class="title ml8" v-else-if="item.approveStatus === ApproveStatus.REJECTED">审批退回</span>
+                <span class="title ml8" v-else-if="item.approveStatus === ApproveStatus.WITHDRAWN">审批撤回</span>
+              </p>
+              <p class="inst-title">
+                <span>申请人</span>
+                <span class="title ml8">
+                  {{ item.memberName }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>联系方式</span>
+                <span class="title ml8">
+                  {{ item.memberPhone }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>申请平台</span>
+                <span class="title ml8">
+                  {{ item.platformName }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>申请时长</span>
+                <span class="title ml8"> {{ item.platformTime }}个月 </span>
+              </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>
+              </p>
+              <p class="inst-title">
+                <span>费用(¥)</span>
+                <span class="title ml8">{{ item.price }}</span>
+              </p>
+              <p class="inst-title">
+                <span>提交申请时间</span>
+                <span class="title ml8">{{ formatDate(new Date(item.createdTime), 'YYYY-mm-dd HH:MM') }}</span>
+              </p>
+              <footer class="flex justify-end mt4 actions-footer">
+                <van-button
+                  v-auth="'platform_entry_bill'"
+                  link
+                  type="primary"
+                  v-if="item.appointStatus === ApplicationStatus.PENDING_UPLOAD && item.downLoadButton && item.createdBy === userInfos.id"
+                  @click.stop="onDownload(item.id)"
+                  >缴费单</van-button
+                >
+                <van-button
+                  v-auth="'platform_entry_upload'"
+                  link
+                  type="primary"
+                  v-if="item.appointStatus === '10' && item.downLoadButton && item.createdBy === userInfos.id"
+                  @click.stop="onUpload(item, 'add')"
+                  >上传</van-button
+                >
+                <van-button v-auth="'platform_entry_audit'" link type="primary" v-if="item.appointStatus === '20'" @click.stop="onAudit(item)">审核</van-button>
+                <van-button
+                  v-auth="'platform_entry_upload'"
+                  link
+                  type="primary"
+                  v-if="item.appointStatus === '30' && item.createdBy === userInfos.id"
+                  @click.stop="onUpload(item, 'edit')"
+                  >重新上传</van-button
+                >
+              </footer>
+            </div>
+          </template>
+        </van-cell>
+      </van-list>
+    </div>
+    <UploadDialog ref="uploadRef" @refresh="onLoad" />
+    <DetailsDialog ref="detailsDialog" @refresh="onLoad" />
+    <AllocateDialog ref="allocateRef" @refresh="onLoad" />
+    <ConfirmDialog ref="confirmRef" @refresh="onLoad" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, defineAsyncComponent } from 'vue'
+  import { useRouter } from 'vue-router'
+  import to from 'await-to-js'
+  import { storeToRefs } from 'pinia'
+
+  import { usePlatformAppointApi } from '/@/api/platform/appoint'
+  import { useCellAssignApi } from '/@/api/platform/home/assign'
+  import { formatDate } from '/@/utils/formatTime'
+  import { useUserInfo } from '/@/stores/userInfo'
+  import exportFileUtil from '/@/utils/base64ToFile'
+
+  const UploadDialog = defineAsyncComponent(() => import('/@/view/entry/components/upload.vue'))
+  const DetailsDialog = defineAsyncComponent(() => import('/@/view/entry/components/details.vue'))
+  const ConfirmDialog = defineAsyncComponent(() => import('/@/view/entry/components/confirm.vue'))
+  const AllocateDialog = defineAsyncComponent(() => import('/@/view/entry/components/allocate.vue'))
+
+  // 05 队列中 10 待上传 20 待审核 30 已驳回 35 已撤回 40 待分配 50 可入室 60 已出室
+  enum ApplicationStatus {
+    IN_QUEUE = '05',
+    PENDING_UPLOAD = '10',
+    PENDING_REVIEW = '20',
+    REJECTED = '30',
+    WITHDRAWN = '35',
+    PENDING_ASSIGNMENT = '40',
+    AVAILABLE_FOR_ADMISSION = '50',
+    OUT_OF_ROOM = '60'
+  }
+
+  // 10 待提交 20 审批中 30 审批通过 40 审批退回 35 审批撤回
+  enum ApproveStatus {
+    NEED_SUBMIT = '10',
+    IN_REVIEW = '20',
+    APPROVED = '30',
+    REJECTED = '40',
+    WITHDRAWN = '35'
+  }
+
+  const storesUserInfo = useUserInfo()
+  const { userInfos } = storeToRefs(storesUserInfo)
+  const router = useRouter()
+  const platformAppointApi = usePlatformAppointApi()
+  const cellAssignApi = useCellAssignApi()
+
+  const uploadRef = ref()
+  const detailsDialog = ref()
+  const allocateRef = ref()
+  const confirmRef = ref()
+  const startTime = ref('')
+  const endTime = ref('')
+  const state = reactive({
+    queryParams: {
+      pageNum: 1,
+      pageSize: 10,
+      dateTime: [],
+      memberName: ''
+    },
+    finished: false,
+    loading: true,
+    list: [] as any[]
+  })
+
+  const onLoad = async () => {
+    const [err, res]: ToResponse = await to(platformAppointApi.getPlatformAppointList(state.queryParams))
+    if (err) return
+    const list = res?.data?.list || []
+    for (const item of list) {
+      state.list.push(item)
+    }
+    state.loading = false
+    state.queryParams.pageNum++
+    if (list.length < state.queryParams.pageSize) {
+      state.finished = true
+    }
+  }
+
+  const toDetail = (id: number) => {
+    router.push({
+      path: '/entry/detail',
+      query: {
+        id
+      }
+    })
+  }
+
+  const onDownload = async (id: number) => {
+    const [err, res]: ToResponse = await to(cellAssignApi.downBillfile({ id }))
+    if (err) return
+    exportFileUtil.downloadFileByByte(res?.data, `入室申请缴费单.pdf`)
+    onLoad()
+  }
+
+  const onUpload = (row: any, type: 'add' | 'edit') => {
+    uploadRef.value.openDialog(row, type)
+  }
+
+  const onAudit = (row: any) => {
+    detailsDialog.value.openDialog('audit', row)
+  }
+
+  const onAllocate = () => {
+    allocateRef.value.openDialog()
+  }
+
+  const onConfirm = () => {
+    confirmRef.value.openDialog()
+  }
+
+  const resetQuery = () => {
+    state.queryParams = {
+      ...state.queryParams,
+      pageNum: 1,
+      pageSize: 20
+    }
+    state.list = []
+    state.finished = false
+    state.loading = true
+  }
+
+  const search = () => {
+    if (startTime.value) {
+      state.queryParams.dateTime[0] = startTime.value
+    } else {
+      state.queryParams.dateTime[0] = ''
+    }
+    if (endTime.value) {
+      state.queryParams.dateTime[1] = endTime.value
+    } else {
+      state.queryParams.dateTime[1] = ''
+    }
+    resetQuery()
+    onLoad()
+  }
+
+  onMounted(() => {
+    onLoad()
+  })
+</script>
+
+<style lang="scss" scoped>
+  .entry-container {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    .list-container {
+      overflow-y: auto;
+      padding: 10px;
+      border-radius: 4px;
+      flex: 1;
+      .van-list {
+        .van-cell {
+          background-color: #fff;
+          + .van-cell {
+            margin-top: 10px;
+          }
+          header,
+          footer {
+            color: #333;
+          }
+          .title {
+            flex: 1;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            text-align: left;
+          }
+          .inst-title {
+            color: #333;
+            text-align: left;
+            flex: 1;
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            margin-top: 4px;
+            span:first-child {
+              color: rgb(120, 120, 120);
+            }
+          }
+          .time {
+            color: #f69a4d;
+          }
+        }
+      }
+    }
+    .actions-footer {
+      .van-button {
+        height: 30px;
+      }
+    }
+    .search-wrap {
+      background: #fff;
+      margin-bottom: 10px;
+      padding: 15px;
+    }
+    .buttons {
+      display: flex;
+      justify-content: end;
+      margin-top: 15px;
+    }
+  }
+</style>

+ 107 - 76
src/view/entry/mine.vue

@@ -1,68 +1,64 @@
-<!--
- * @Author: wanglj wanglijie@dashoo.cn
- * @Date: 2025-03-11 18:02:10
- * @LastEditors: wanglj wanglijie@dashoo.cn
- * @LastEditTime: 2025-03-20 17:51:51
- * @FilePath: \vant-demo-master\vant\vue3-ts\src\view\login\index.vue
- * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
--->
 <template>
   <div class="entry-container">
-    <van-tabs v-model:active="state.queryParams.appointStatus" @change="changeType">
-      <van-tab title="待缴费" :name="EntryMineStatus.TO_BE_PAID" />
-      <van-tab title="待分配" :name="EntryMineStatus.TO_BE_ASSIGNED" />
-      <van-tab title="可入室" :name="EntryMineStatus.CAN_ENTER" />
-    </van-tabs>
     <div class="list-container">
+      <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>
+          </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" />
+            </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-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" @click="toDetail(item.id)">
+        <van-cell v-for="item in state.list" :key="item">
           <template #default>
             <div class="list">
               <header class="flex justify-between">
-                <strong class="title">我的入室申请</strong>
+                <strong class="title">{{ item.userName }}的{{ 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> -->
-
-                <!-- 05 待确认 10 待上传 20 待审核 30 已驳回 35 已撤回 40 待分配 50 可入室 60 已出室 -->
-                <van-tag class="ml4">待缴费</van-tag>
-                <!-- <van-tag class="ml4">待分配</van-tag> -->
-                <!-- <van-tag type="success" class="ml4">可入室</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>
-                <span class="title ml8">{{ item.pgName }}</span>
-              </p>
               <p class="inst-title">
                 <span>申请平台</span>
                 <span class="title ml8">
-                  {{ formatData(item.platPlatformAppointCellRes, item.platPlatformAppointMolecularRes).platformName }}
+                  {{ item.platformName }}
                 </span>
               </p>
               <p class="inst-title">
-                <span>申请时长</span>
+                <span>入室时间</span>
                 <span class="title ml8">
-                  {{ formatData(item.platPlatformAppointCellRes, item.platPlatformAppointMolecularRes).platformTime }}
+                  {{ item?.startTime ? formatDate(new Date(item?.startTime), 'YYYY-mm-dd') : '' }} ~
+                  {{ item?.endTime ? formatDate(new Date(item?.endTime), 'YYYY-mm-dd') : '' }}
                 </span>
               </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>资源名称</span>
+                <span class="title ml8">
+                  {{ item.resName }}
+                </span>
               </p>
               <p class="inst-title">
-                <span>缴费金额(¥)</span>
-                <span class="title ml8">{{ item.price }}</span>
+                <span>位置信息</span>
+                <span class="title ml8">
+                  {{ item.resLocation }}
+                </span>
               </p>
-              <footer class="flex justify-between mt4">
-                <span class="title">{{ item.memberName }}</span>
-                <span class="time">{{ formatDate(new Date(item.createdTime), 'YYYY-mm-dd') }}</span>
-              </footer>
+              <footer class="flex justify-between mt4"></footer>
             </div>
           </template>
         </van-cell>
@@ -72,42 +68,59 @@
 </template>
 
 <script name="entryMine" lang="ts" setup>
-  import { reactive, onMounted } from 'vue'
+  import { reactive, onMounted, ref } from 'vue'
   import { useRouter } from 'vue-router'
   import to from 'await-to-js'
 
   import { usePlatformAppointApi } from '/@/api/platform/appoint'
+  import { useCellAssignApi } from '/@/api/platform/home/assign'
+  import { usePlatformApi } from '/@/api/platform/home'
   import { formatDate } from '/@/utils/formatTime'
 
-  enum EntryMineStatus {
-    TO_BE_PAID = '10',
-    TO_BE_ASSIGNED = '40',
-    CAN_ENTER = '50'
+  enum EntryAssignStatus {
+    TO_BE_ENTERED = '10', // 待入室
+    NEXT_MONTH_ENTERED = '15', // 次月入室
+    ENTERED = '20', // 已入室
+    OUT_ROOM = '30', // 已出室
+    NEXT_MONTH_OUT_ROOM = '40' // 次月出室
   }
 
   const platformAppointApi = usePlatformAppointApi()
+  const platformApi = usePlatformApi()
+  const cellAssignApi = useCellAssignApi()
   const router = useRouter()
 
+  const startTime = ref('')
+  const endTime = ref('')
+  const platformList = ref<any[]>([])
   const state = reactive({
     queryParams: {
-      appointStatus: EntryMineStatus.TO_BE_PAID,
+      platformId: null,
+      dateTime: [],
+      resName: '',
       pageNum: 1,
-      pageSize: 10
+      orderBy: '',
+      pageSize: 20
     },
     finished: false,
     loading: true,
     list: [] as any[]
   })
 
-  const changeType = (name: EntryMineStatus) => {
-    state.queryParams.pageNum = 1
-    state.queryParams.appointStatus = name
-    state.list = []
-    onLoad()
-  }
-
   const onLoad = async () => {
-    const [err, res]: ToResponse = await to(platformAppointApi.getList(state.queryParams))
+    state.loading = true
+
+    const payload: any = {
+      ...state.queryParams
+    }
+
+    Object.keys(payload).map((item) => {
+      if (payload[item] === '' || payload[item] === null) {
+        delete payload[item]
+      }
+    })
+
+    const [err, res]: ToResponse = await to(cellAssignApi.getUserAssignResources(payload))
     if (err) return
     const list = res?.data?.list || []
     for (const item of list) {
@@ -118,33 +131,42 @@
     if (list.length < state.queryParams.pageSize) {
       state.finished = true
     }
+  }
 
-    console.log('response', res)
+  const getDicts = () => {
+    Promise.all([platformApi.getPlatformList()]).then(([plat]) => {
+      platformList.value = plat?.data?.list || []
+    })
   }
 
-  const formatData = (cellRes: any[], molecularRes: any[]) => {
-    if (cellRes && cellRes.length && (!molecularRes || !molecularRes.length)) {
-      return { platformName: cellRes[0].platformName, platformTime: cellRes[0].platformTime }
-    }
-    if (molecularRes && molecularRes.length && (!cellRes || !cellRes.length)) {
-      return { platformName: molecularRes[0].platformName, platformTime: molecularRes[0].platformTime }
-    }
-    return {
-      platformName: `${cellRes[0].platformName} / ${molecularRes[0].platformName}`,
-      platformTime: `${cellRes[0].platformTime}个月 / ${molecularRes[0].platformTime}个月`
+  const resetQuery = () => {
+    state.queryParams = {
+      ...state.queryParams,
+      pageNum: 1,
+      pageSize: 20
     }
+    state.list = []
+    state.finished = false
+    state.loading = true
   }
 
-  const toDetail = (id: number) => {
-    router.push({
-      path: '/entry/detail',
-      query: {
-        id
-      }
-    })
+  const search = () => {
+    if (startTime.value) {
+      state.queryParams.dateTime[0] = startTime.value
+    } else {
+      state.queryParams.dateTime[0] = ''
+    }
+    if (endTime.value) {
+      state.queryParams.dateTime[1] = endTime.value
+    } else {
+      state.queryParams.dateTime[1] = ''
+    }
+    resetQuery()
+    onLoad()
   }
 
   onMounted(() => {
+    getDicts()
     onLoad()
   })
 </script>
@@ -194,5 +216,14 @@
         }
       }
     }
+    .asterisk-left {
+      display: flex;
+      margin-right: 0;
+    }
+    .search-wrap {
+      background: #fff;
+      margin-bottom: 10px;
+      padding: 15px;
+    }
   }
 </style>