Kaynağa Gözat

Merge branch 'dev' of https://git.citupro.com/zhengnaiwen_citu/menduner into dev

lifanagju_citu 10 ay önce
ebeveyn
işleme
3b98ea82c3

+ 146 - 12
pnpm-lock.yaml

@@ -11,12 +11,39 @@ importers:
       '@mdi/font':
         specifier: 7.0.96
         version: 7.0.96
+      '@vuepic/vue-datepicker':
+        specifier: ^8.7.0
+        version: 8.8.1(vue@3.4.25)
       axios:
         specifier: ^1.6.8
         version: 1.6.8
+      bignumber.js:
+        specifier: ^9.1.2
+        version: 9.1.2
+      buffer:
+        specifier: ^6.0.3
+        version: 6.0.3
+      crypto-js:
+        specifier: ^4.2.0
+        version: 4.2.0
+      curve25519-js:
+        specifier: ^0.0.4
+        version: 0.0.4
+      echarts:
+        specifier: ^5.4.3
+        version: 5.5.1
+      js-base64:
+        specifier: ^3.7.7
+        version: 3.7.7
       js-cookie:
         specifier: ^3.0.5
         version: 3.0.5
+      lodash:
+        specifier: ^4.17.21
+        version: 4.17.21
+      md5-typescript:
+        specifier: ^1.0.5
+        version: 1.0.5
       nprogress:
         specifier: ^0.2.0
         version: 0.2.0
@@ -45,8 +72,11 @@ importers:
         specifier: ^4.3.0
         version: 4.3.2(vue@3.4.25)
       vuetify:
-        specifier: ^3.5.0
-        version: 3.5.17(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25)
+        specifier: ^3.6.0
+        version: 3.6.13(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25)
+      wukongimjssdk:
+        specifier: ^1.2.10
+        version: 1.2.10
     devDependencies:
       '@rushstack/eslint-patch':
         specifier: ^1.8.0
@@ -83,7 +113,7 @@ importers:
         version: 5.2.10(sass@1.75.0)
       vite-plugin-vuetify:
         specifier: ^2.0.3
-        version: 2.0.3(vite@5.2.10(sass@1.75.0))(vue@3.4.25)(vuetify@3.5.17)
+        version: 2.0.3(vite@5.2.10(sass@1.75.0))(vue@3.4.25)(vuetify@3.6.13)
 
 packages:
 
@@ -400,6 +430,13 @@ packages:
   '@rushstack/eslint-patch@1.10.2':
     resolution: {integrity: sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==}
 
+  '@types/bignumber.js@5.0.0':
+    resolution: {integrity: sha512-0DH7aPGCClywOFaxxjE6UwpN2kQYe9LwuDQMv+zYA97j5GkOMo8e66LYT+a8JYU7jfmUFRZLa9KycxHDsKXJCA==}
+    deprecated: This is a stub types definition for bignumber.js (https://github.com/MikeMcl/bignumber.js/). bignumber.js provides its own type definitions, so you don't need @types/bignumber.js installed!
+
+  '@types/crypto-js@4.2.2':
+    resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
+
   '@types/estree@1.0.5':
     resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
 
@@ -451,6 +488,11 @@ packages:
   '@vue/shared@3.4.25':
     resolution: {integrity: sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA==}
 
+  '@vuepic/vue-datepicker@8.8.1':
+    resolution: {integrity: sha512-8ehfUz1m69Vuc16Pm4ukgb3Mg1VT14x4EsG1ag4O/qbSNRWztTo+pUV4JnFt0FGLl5gGb6NXlxIvR7EjLgD7Gg==}
+    peerDependencies:
+      vue: '>=3.2.0'
+
   '@vuetify/loader-shared@2.0.3':
     resolution: {integrity: sha512-Ss3GC7eJYkp2SF6xVzsT7FAruEmdihmn4OCk2+UocREerlXKWgOKKzTN5PN3ZVN5q05jHHrsNhTuWbhN61Bpdg==}
     peerDependencies:
@@ -498,6 +540,12 @@ packages:
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
+  base64-js@1.5.1:
+    resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+  bignumber.js@9.1.2:
+    resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==}
+
   binary-extensions@2.3.0:
     resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
     engines: {node: '>=8'}
@@ -515,6 +563,9 @@ packages:
     resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
     engines: {node: '>=8'}
 
+  buffer@6.0.3:
+    resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
   call-bind@1.0.7:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
     engines: {node: '>= 0.4'}
@@ -549,6 +600,9 @@ packages:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
 
+  crypto-js@4.2.0:
+    resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
     engines: {node: '>=4'}
@@ -561,10 +615,16 @@ packages:
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
+  curve25519-js@0.0.4:
+    resolution: {integrity: sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==}
+
   data-urls@5.0.0:
     resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
     engines: {node: '>=18'}
 
+  date-fns@3.6.0:
+    resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+
   debug@4.3.4:
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
     engines: {node: '>=6.0'}
@@ -592,6 +652,9 @@ packages:
     resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
     engines: {node: '>=6.0.0'}
 
+  echarts@5.5.1:
+    resolution: {integrity: sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==}
+
   entities@4.5.0:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
@@ -797,6 +860,9 @@ packages:
     resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
     engines: {node: '>=0.10.0'}
 
+  ieee754@1.2.1:
+    resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
   ignore@5.3.1:
     resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
     engines: {node: '>= 4'}
@@ -847,6 +913,9 @@ packages:
   isexe@2.0.0:
     resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
 
+  js-base64@3.7.7:
+    resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
+
   js-cookie@3.0.5:
     resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
     engines: {node: '>=14'}
@@ -901,6 +970,9 @@ packages:
   magic-string@0.30.10:
     resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
 
+  md5-typescript@1.0.5:
+    resolution: {integrity: sha512-ovAc4EtiNt2dY8JPhPr/wkC9h4U5k/nuClNVcG0Ga3V1rMlYpAY24ZaaymFXJlz+ccJ6UMPo3FSaVKe7czBsXw==}
+
   merge2@1.4.1:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
     engines: {node: '>= 8'}
@@ -1174,6 +1246,9 @@ packages:
     resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
     engines: {node: '>=18'}
 
+  tslib@2.3.0:
+    resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
+
   tslib@2.6.2:
     resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
 
@@ -1300,8 +1375,8 @@ packages:
       typescript:
         optional: true
 
-  vuetify@3.5.17:
-    resolution: {integrity: sha512-/Veklxxyu/l63q7QQOqJZeZukIKI2sBxY7FKMDcNup2KSGMjyjT+oYXy1DOdl7wlU3c3fKGQMFHqVWb0HDsyDw==}
+  vuetify@3.6.13:
+    resolution: {integrity: sha512-Gz7jxXAkmff2m6CM0EUWOo/72TM322/3I6aDna++k1nPOW1/hNx4td1MZG4u75fzdn3r+uIe0dbF7SWuhu6DWA==}
     engines: {node: ^12.20 || >=14.13}
     peerDependencies:
       typescript: '>=4.7'
@@ -1370,6 +1445,9 @@ packages:
       utf-8-validate:
         optional: true
 
+  wukongimjssdk@1.2.10:
+    resolution: {integrity: sha512-MX4NJoXGV+KnxZ6kK8UwsjLWEewGQudmCGV2d4/vrtI99Z78EkfWARPyVGX3jkqX0vwDzxid2JrcQewuo3vXGA==}
+
   xml-name-validator@4.0.0:
     resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
     engines: {node: '>=12'}
@@ -1388,6 +1466,9 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
+  zrender@5.6.0:
+    resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==}
+
 snapshots:
 
   '@antfu/utils@0.7.7': {}
@@ -1598,6 +1679,12 @@ snapshots:
 
   '@rushstack/eslint-patch@1.10.2': {}
 
+  '@types/bignumber.js@5.0.0':
+    dependencies:
+      bignumber.js: 9.1.2
+
+  '@types/crypto-js@4.2.2': {}
+
   '@types/estree@1.0.5': {}
 
   '@ungap/structured-clone@1.2.0': {}
@@ -1671,11 +1758,16 @@ snapshots:
 
   '@vue/shared@3.4.25': {}
 
-  '@vuetify/loader-shared@2.0.3(vue@3.4.25)(vuetify@3.5.17(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25))':
+  '@vuepic/vue-datepicker@8.8.1(vue@3.4.25)':
+    dependencies:
+      date-fns: 3.6.0
+      vue: 3.4.25
+
+  '@vuetify/loader-shared@2.0.3(vue@3.4.25)(vuetify@3.6.13(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25))':
     dependencies:
       upath: 2.0.1
       vue: 3.4.25
-      vuetify: 3.5.17(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25)
+      vuetify: 3.6.13(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25)
 
   acorn-jsx@5.3.2(acorn@8.11.3):
     dependencies:
@@ -1721,6 +1813,10 @@ snapshots:
 
   balanced-match@1.0.2: {}
 
+  base64-js@1.5.1: {}
+
+  bignumber.js@9.1.2: {}
+
   binary-extensions@2.3.0: {}
 
   boolbase@1.0.0: {}
@@ -1738,6 +1834,11 @@ snapshots:
     dependencies:
       fill-range: 7.0.1
 
+  buffer@6.0.3:
+    dependencies:
+      base64-js: 1.5.1
+      ieee754: 1.2.1
+
   call-bind@1.0.7:
     dependencies:
       es-define-property: 1.0.0
@@ -1783,6 +1884,8 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
+  crypto-js@4.2.0: {}
+
   cssesc@3.0.0: {}
 
   cssstyle@4.0.1:
@@ -1791,11 +1894,15 @@ snapshots:
 
   csstype@3.1.3: {}
 
+  curve25519-js@0.0.4: {}
+
   data-urls@5.0.0:
     dependencies:
       whatwg-mimetype: 4.0.0
       whatwg-url: 14.0.0
 
+  date-fns@3.6.0: {}
+
   debug@4.3.4:
     dependencies:
       ms: 2.1.2
@@ -1816,6 +1923,11 @@ snapshots:
     dependencies:
       esutils: 2.0.3
 
+  echarts@5.5.1:
+    dependencies:
+      tslib: 2.3.0
+      zrender: 5.6.0
+
   entities@4.5.0: {}
 
   es-define-property@1.0.0:
@@ -2076,6 +2188,8 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
+  ieee754@1.2.1: {}
+
   ignore@5.3.1: {}
 
   immutable@4.3.5: {}
@@ -2116,6 +2230,8 @@ snapshots:
 
   isexe@2.0.0: {}
 
+  js-base64@3.7.7: {}
+
   js-cookie@3.0.5: {}
 
   js-yaml@4.1.0:
@@ -2183,6 +2299,8 @@ snapshots:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
 
+  md5-typescript@1.0.5: {}
+
   merge2@1.4.1: {}
 
   micromatch@4.0.5:
@@ -2436,6 +2554,8 @@ snapshots:
     dependencies:
       punycode: 2.3.1
 
+  tslib@2.3.0: {}
+
   tslib@2.6.2: {}
 
   type-check@0.4.0:
@@ -2491,14 +2611,14 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-plugin-vuetify@2.0.3(vite@5.2.10(sass@1.75.0))(vue@3.4.25)(vuetify@3.5.17):
+  vite-plugin-vuetify@2.0.3(vite@5.2.10(sass@1.75.0))(vue@3.4.25)(vuetify@3.6.13):
     dependencies:
-      '@vuetify/loader-shared': 2.0.3(vue@3.4.25)(vuetify@3.5.17(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25))
+      '@vuetify/loader-shared': 2.0.3(vue@3.4.25)(vuetify@3.6.13(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25))
       debug: 4.3.4
       upath: 2.0.1
       vite: 5.2.10(sass@1.75.0)
       vue: 3.4.25
-      vuetify: 3.5.17(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25)
+      vuetify: 3.6.13(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25)
     transitivePeerDependencies:
       - supports-color
 
@@ -2548,11 +2668,11 @@ snapshots:
       '@vue/server-renderer': 3.4.25(vue@3.4.25)
       '@vue/shared': 3.4.25
 
-  vuetify@3.5.17(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25):
+  vuetify@3.6.13(vite-plugin-vuetify@2.0.3)(vue-i18n@9.13.1(vue@3.4.25))(vue@3.4.25):
     dependencies:
       vue: 3.4.25
     optionalDependencies:
-      vite-plugin-vuetify: 2.0.3(vite@5.2.10(sass@1.75.0))(vue@3.4.25)(vuetify@3.5.17)
+      vite-plugin-vuetify: 2.0.3(vite@5.2.10(sass@1.75.0))(vue@3.4.25)(vuetify@3.6.13)
       vue-i18n: 9.13.1(vue@3.4.25)
 
   w3c-xmlserializer@5.0.0:
@@ -2586,6 +2706,16 @@ snapshots:
 
   ws@8.17.0: {}
 
+  wukongimjssdk@1.2.10:
+    dependencies:
+      '@types/bignumber.js': 5.0.0
+      '@types/crypto-js': 4.2.2
+      bignumber.js: 9.1.2
+      buffer: 6.0.3
+      crypto-js: 4.2.0
+      curve25519-js: 0.0.4
+      md5-typescript: 1.0.5
+
   xml-name-validator@4.0.0: {}
 
   xml-name-validator@5.0.0: {}
@@ -2595,3 +2725,7 @@ snapshots:
   yallist@4.0.0: {}
 
   yocto-queue@0.1.0: {}
+
+  zrender@5.6.0:
+    dependencies:
+      tslib: 2.3.0

+ 8 - 1
src/api/common/index.js

@@ -218,7 +218,14 @@ export const getUserAccount = async () => {
 // 获取聊天秘钥信息
 export const getChatKey = async (data) => {
   return await request.post({
-    url: 'admin-api/im/user/get',
+    url: '/app-api/im/user/get',
+    data
+  })
+}
+// 同步最近会话
+export const getConversationSync = async (data) => {
+  return await request.post({
+    url: '/app-api/im/conversation/sync',
     data
   })
 }

+ 11 - 2
src/api/recruit/enterprise/personnel/index.js

@@ -8,9 +8,10 @@ export const personJobCvLook = async (id) => {
 }
 
 // 招聘端-牛人管理-加入不合适
-export const joinEliminate = async (ids) => {
+export const joinEliminate = async (data) => {
   return await request.post({
-    url: `/app-admin-api/menduner/system/person-cv/eliminate?ids=${ids}`
+    url: '/app-admin-api/menduner/system/person-cv/unfit/eliminate',
+    data
   })
 }
 
@@ -19,4 +20,12 @@ export const personEntryByEnterprise = async (ids) => {
   return await request.post({
     url: `/app-admin-api/menduner/system/person-cv/entry?ids=${ids}`
   })
+}
+
+// 招聘端-牛人管理-不合适
+export const personCvUnfitPage = async (params) => {
+  return await request.get({
+    url: '/app-admin-api/menduner/system/person-cv/unfitPage',
+    params
+  })
 }

+ 140 - 0
src/hooks/web/useIM.js

@@ -0,0 +1,140 @@
+
+
+
+import { ref, onMounted, onUnmounted } from 'vue';
+import { getConversationSync } from '@/api/common'
+
+// 配置悟空IM
+import {
+  MessageText,
+  Channel,
+  WKSDK,
+  ChannelTypePerson,
+  // ChannelTypeGroup
+} from "wukongimjssdk"
+import Snackbar from '@/plugins/snackbar'
+
+import { useUserStore } from '@/store/user'
+
+const userStore = useUserStore()
+
+const ConnectStatus = {
+  Disconnect: 0, // 断开连接
+  Connected: 1, // 连接成功
+  Connecting: 2, // 连接中
+  ConnectFail: 3, // 连接错误
+  ConnectKick: 4, // 连接被踢,服务器要求客户端断开(一般是账号在其他地方登录,被踢)
+}
+
+
+initDataSource()
+// api 接入
+function initDataSource () {
+  // 最近会话数据源
+  WKSDK.shared().config.provider.syncConversationsCallback  = async () => {
+    const res = await getConversationSync({ msg_count: 100 })
+    return res
+  }
+}
+export function initKey () {
+  return new Promise((resolve) => { 
+    userStore.getChatKey().then(() => {
+      resolve()
+    }).catch((error) => {
+      // 报错重连
+      console.log(error.msg)
+      setTimeout(() => {
+        initKey()
+      }, 3000)
+    })
+  })
+}
+
+export function initConnect () {
+  const connected = ref(false)
+  const conversationList = ref([])
+  // 单机模式可以直接设置地址
+  WKSDK.shared().config.addr = 'ws://' + userStore.IMConfig.wsUrl // 默认端口为5200
+  // 认证信息
+  WKSDK.shared().config.uid = userStore.IMConfig.uid // 用户uid(需要在悟空通讯端注册过)
+  WKSDK.shared().config.token = userStore.IMConfig.token // 用户token (需要在悟空通讯端注册过)
+  // 连接
+  onMounted(() => {
+    // 连接状态监听
+    WKSDK.shared().connectManager.addConnectStatusListener(
+      async (status, reasonCode) => {
+        if (status === ConnectStatus.Connected) {
+          console.log('连接成功')
+          connected.value = true
+          // 获取列表
+          const res = await syncConversation()
+          conversationList.value = [...res]
+        } else {
+          console.log('reasonCode', reasonCode)
+          connected.value = false
+          conversationList.value = []
+        }
+      }
+    )
+
+    // 消息发送状态监听
+    WKSDK.shared().chatManager.addMessageStatusListener(statusListen)
+    // 常规消息监听
+    WKSDK.shared().chatManager.addMessageListener(messageListen)
+    
+    connect()
+  })
+  onUnmounted(() => {
+    // 连接状态监听移除
+    WKSDK.shared().connectManager.disconnect()
+    // 消息发送状态监听移除
+    WKSDK.shared().chatManager.removeMessageStatusListener(statusListen)
+    // 常规消息监听移除
+    WKSDK.shared().chatManager.removeMessageListener(messageListen)
+  })
+  return { connected, conversationList }
+}
+
+// 消息发送状态监听
+function statusListen (packet) {
+  if (packet.reasonCode === 1) {
+    // 发送成功
+  } else {
+    // 发送失败
+  }
+}
+
+// 常规消息监听
+function messageListen (message) {
+  console.log('收到消息', message)
+  // message.content // 消息内容
+  // message.channel // 消息频道
+  // message.fromUID // 消息发送者
+}
+
+
+export function connect () {
+  WKSDK.shared().connectManager.connect()
+}
+
+export function send (user, text) {
+  // 例如发送文本消息hello给用户u10001
+  const _text = new MessageText(text) // 文本消息
+  WKSDK.shared().chatManager.send(_text, new Channel(user, ChannelTypePerson))
+
+  // 例如发送文本消息hello给群频道g10001
+  // WKSDK.shared().chatManager.send(_text, new Channel(user,ChannelTypeGroup))
+}
+
+// 同步最近会话
+export async function syncConversation () {
+  return new Promise((resolve) => {
+    WKSDK.shared().conversationManager.sync().then(res => {
+      resolve(res)
+    }).catch(error => {
+      Snackbar.error(error.msg)
+    })
+  })
+}
+
+

+ 7 - 5
src/layout/company/navBar.vue

@@ -12,12 +12,14 @@
           </div>
         </div>
         
-        <div class="d-flex user-nav">
+        <div class="d-flex user-nav align-center">
           <div class="d-flex align-center cursor-pointer">
             <v-img @click="enterpriseClick(2)" rounded width="40" height="40" :src="baseInfo?.logoUrl || 'https://minio.citupro.com/dev/menduner/7.png'" ></v-img>
             <span @click="enterpriseClick(1)" class="ml-3">{{ baseInfo?.enterpriseAnotherName || $t('sys.tourist') }}</span>
           </div>
           <div class="line"></div>
+          <div class="ml-3 cursor-pointer" @click="handleLogout">我要求职</div>
+          <div class="line"></div>
           <div class="d-flex align-center ml-6">
             <div class="cursor-pointer" @click="router.push({ path: '/recruit/enterprise/memberCenter/myPoints' })">{{ $t('enterprise.account.accountBalances') }}:{{ enterpriseUserAccount?.balance || 0 }}元</div>
             <div class="ml-5 cursor-pointer" @click="router.push({ path: '/recruit/enterprise/memberCenter/myPoints' })">{{ $t('enterprise.account.remainingPoints') }}:{{ enterpriseUserAccount?.point || 0 }}点</div>
@@ -48,7 +50,7 @@
           </div>
 
           <!-- 语言切换 -->
-          <v-menu>
+          <!-- <v-menu>
             <template v-slot:activator="{ props }">
               <v-btn
                 class="ml-3"
@@ -70,7 +72,7 @@
                 <v-list-item-title>{{ item.name }}</v-list-item-title>
               </v-list-item>
             </v-list>
-          </v-menu>
+          </v-menu> -->
 
           <v-btn size="small" icon="mdi-bell-outline"></v-btn>
         </div>
@@ -124,7 +126,7 @@ const menuList = ref([
   { title: t('enterprise.personalInformationSettings'), icon: 'mdi-account-cog', change: () => router.push({ path: '/recruit/enterprise/informationSettings' }) },
   { title: t('setting.switchToOtherCompany'), icon: 'mdi-home-switch', hidden: enterpriseList.value?.length < 2, change: () => handleSwitchToAnotherEnterprise },
   { title: t('enterprise.registeringNewEnterprise'), icon: 'mdi-home-plus-outline', change: () => handleRegisteringNewEnterprise },
-  { title: t('setting.switchToJobSeeker'), icon: 'mdi-swap-horizontal', change: handleLogout },
+  // { title: t('setting.switchToJobSeeker'), icon: 'mdi-swap-horizontal', change: handleLogout },
   { title: t('setting.logOut'), icon: 'mdi-logout', change: handleLogout }
 ])
 const items = computed(() => {
@@ -231,7 +233,7 @@ getEnterpriseListData()
 }
 .line {
   width: 1px;
-  height: 25px;
+  height: 20px;
   background-color: #fff;
   margin: 0 10px;
   margin: 8px 0 0 29px;

+ 11 - 9
src/layout/personal/navBar.vue

@@ -33,17 +33,18 @@
           
           <!-- 头像用户名 -->
           <div class="d-flex align-center" v-if="getToken()">
-            <span class="cursor-pointer" @click="router.push({ path: '/recruit/personal/myWallet' })">{{t('resume.accountWithdrawal')}}:{{ userAccount?.balance || 0 }}{{t('unit.rmb')}}</span>
-            <span class="mr-5 ml-5 cursor-pointer" @click="router.push({ path: '/recruit/personal/myWallet' })">{{t('resume.goldCoins')}}:{{ userAccount?.point || 0 }}{{t('unit.ge')}}</span>
+            <span class="cursor-pointer mr-5" @click="changeLoginType">我要招聘</span>
+            <span class="cursor-pointer" @click="router.push({ path: '/recruit/personal/myWallet' })">{{t('resume.accountWithdrawal')}}:{{ userAccount?.balance || 0 }}</span>
+            <span class="mr-3 ml-3 cursor-pointer" @click="router.push({ path: '/recruit/personal/myWallet' })">{{t('resume.goldCoins')}}:{{ userAccount?.point || 0 }}</span>
 
             
             <v-menu open-on-hover>
               <template v-slot:activator="{ props }">
-                <div class="d-flex ml-5 pl-2 align-center cursor-pointer" v-bind="props" @click="handleToPersonalCenter">
+                <div class="d-flex ml-3 align-center cursor-pointer" v-bind="props" @click="handleToPersonalCenter">
                   <v-avatar>
                     <v-img alt="John" :src="baseInfo?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
                   </v-avatar>
-                  <div class="ml-2">{{ baseInfo?.name || $t('sys.tourist') }}</div>
+                  <div class="ml-3">{{ baseInfo?.name || $t('sys.tourist') }}</div>
                 </div>
               </template>
 
@@ -59,7 +60,7 @@
           </div>
 
           <!-- 语言切换 -->
-          <v-menu>
+          <!-- <v-menu>
             <template v-slot:activator="{ props }">
               <v-btn
                 class="ml-3"
@@ -81,10 +82,11 @@
                 <v-list-item-title>{{ item.name }}</v-list-item-title>
               </v-list-item>
             </v-list>
-          </v-menu>
+          </v-menu> -->
           <div class="d-flex align-center" v-if="getToken()">
-            <span class="cursor-pointer mx-5" @click="router.push({ path: '/recruit/personal/TaskCenter' })">{{ $t('sys.signIn') }}</span>
-            <span class="cursor-pointer" @click="router.push({ path: '/recruit/personal/message' })">{{ $t('sys.news') }}</span>
+            <!-- <span class="cursor-pointer mx-5" @click="router.push({ path: '/recruit/personal/TaskCenter' })">{{ $t('sys.signIn') }}</span> -->
+            <!-- <span class="cursor-pointer" @click="router.push({ path: '/recruit/personal/message' })">{{ $t('sys.news') }}</span> -->
+            <v-btn class="ml-1" size="small" icon="mdi-bell-outline" @click="router.push({ path: '/recruit/personal/message' })"></v-btn>
           </div>
         </div>
         
@@ -178,7 +180,7 @@ const items = ref([
   { title: t('vipPackage.purchasePackage'), icon: 'mdi-gift-outline', change: () => router.push({ path: '/recruit/personal/purchasePackage' }) },
   { title: t('resume.onlineResume'), icon: 'mdi-list-box-outline', change: () => router.push({ path: '/recruit/personal/resume' }) },
   { title: t('setting.accountSettings'), icon: 'mdi-cog-outline', change: () => router.push({ path: '/recruit/personal/accountSettings/accountBinding' }) },
-  { title: t('setting.switchToRecruit'), icon: 'mdi-swap-horizontal', change: changeLoginType },
+  // { title: t('setting.switchToRecruit'), icon: 'mdi-swap-horizontal', change: changeLoginType },
   { title: t('publicRecruitment.bountyRewards'), icon: 'mdi-google-circles-extended', change: () => router.push({ path: '/publicRecruitment' }) },
   { title: t('points.wallet'), icon: ' mdi-database-outline', change: () => router.push({ path: '/recruit/personal/myWallet' }) },
   { title: t('taskCenter.taskCenter'), icon: 'mdi-calendar-check-outline', change: () => router.push({ path: '/recruit/personal/TaskCenter' }) },

+ 33 - 5
src/store/user.js

@@ -1,12 +1,24 @@
 import { defineStore } from 'pinia'
 import { setToken, removeToken, setRefreshToken, getToken } from '@/utils/auth'
-import { smsLogin, passwordLogin, getBaseInfo, passwordLoginOfEnterprise, smsLoginOfEnterprise, switchLoginOfEnterprise, getEnterprisingUserInfo, logoutToken, logout } from '@/api/common'
+import {
+  smsLogin,
+  passwordLogin, 
+  getBaseInfo, 
+  passwordLoginOfEnterprise, 
+  smsLoginOfEnterprise, 
+  switchLoginOfEnterprise, 
+  getEnterprisingUserInfo, 
+  logoutToken, 
+  logout 
+} from '@/api/common'
 import { getUserInfo } from '@/api/personal/user'
 import { getEnterpriseUserAccount, getUserAccount } from '@/api/common'
 import Snackbar from '@/plugins/snackbar'
 import { timesTampChange } from '@/utils/date'
 import { updateEventList } from '@/utils/eventList'
 import { getBaseInfoDictOfName } from '@/utils/getText'
+import { getChatKey } from '@/api/common'
+
 
 
 export const useUserStore = defineStore('user',
@@ -14,14 +26,15 @@ export const useUserStore = defineStore('user',
     state: () => ({
       // loginType: null, // 登录类型 // 330为企业登录
       accountInfo: {}, // 登录返回的信息
-      userInfo: {}, // 当前登录账号信息
+      userInfo: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {}, // 当前登录账号信息
       baseInfo: {}, // 人才信息
       userAccount: {}, // 用户账户信息
       enterpriseUserAccount: {}, // 企业账户信息
+      IMConfig: localStorage.getItem('IMConfig') ? JSON.parse(localStorage.getItem('IMConfig')) : {} // IM聊天密钥
     }),
     actions: {
       // 短信登录
-      async handleSmsLogin (data) {
+      handleSmsLogin (data) {
         return new Promise((resolve, reject) => {
           const loginApi = data.loginType === 330 ? smsLoginOfEnterprise : smsLogin
           loginApi(data).then(res => {
@@ -38,7 +51,20 @@ export const useUserStore = defineStore('user',
           }).catch(err => { reject(err) })
         })
       },
-      // // 密码登录
+      // 获取聊天密钥
+      getChatKey () {
+        return new Promise((resolve, reject) => {
+          getChatKey({ userId: this.userInfo.id })
+          .then(res => {
+            this.IMConfig = res
+            localStorage.setItem('IMConfig', JSON.stringify(res))
+            resolve()
+          }).catch(error => {
+            reject(error)
+          })
+        })
+      },
+      // 密码登录
       async handlePasswordLogin(data) {
         return new Promise((resolve, reject) => {
           const loginApi = data.loginType === 330 ? passwordLoginOfEnterprise : passwordLogin
@@ -62,6 +88,8 @@ export const useUserStore = defineStore('user',
           const data = await api({ id: this.accountInfo.userId })
           this.userInfo = data
           localStorage.setItem('userInfo', JSON.stringify(data))
+          // 获取密钥
+          this.getChatKey()
           updateEventList(true) // 获取规则配置跟踪列表
           this.getUserAccountInfo()
         } catch (error) {
@@ -150,7 +178,7 @@ export const useUserStore = defineStore('user',
         localStorage.setItem('userAccount', JSON.stringify(data))
       }
     }
-   },
+  },
   {
     persist: true,
     devtools: true

+ 52 - 42
src/views/recruit/enterprise/interview/index.vue

@@ -59,7 +59,7 @@
       <div style="flex: 1;overflow: hidden;">
         <div v-if="items.length">
           <div
-            class="listItem d-flex align-center justify-space-between pa-3 mb-3"
+            class="listItem d-flex align-center pa-3 mb-3"
             v-for="(item, index) in items" :key="'item_' + index"
           >
             <div class="d-flex align-center">
@@ -70,47 +70,51 @@
                 <span class="ellipsis" style="color: var(--color-999);">{{ item?.job?.name }}</span>
               </div>
             </div>
-            <span style="min-width: 80px;text-align: center;">
-              <v-icon v-if="item?.phone" class="mx-1" size="20" color="primary">mdi-phone-outline</v-icon>
-              <span>{{ item?.phone || '-' }}</span>
-            </span>
-            <!-- 面试类型: 线下面试 -->
-            <span v-if="item.type === '1'">
-              <v-icon class="mx-3" size="20" color="primary">mdi-account-multiple-outline</v-icon>
-              <span>{{ $t('interview.offlineInterview') }}</span>
-            </span>
-            <!-- 面试类型: 线上面试 -->
-            <span class="d-flex" v-else>
-              <v-icon class="mx-3 mt-2" size="20" color="primary">mdi mdi-video-account</v-icon>
-              <span class="d-flex flex-column">
-                <span>{{ $t('interview.onlineInterview') }}</span>
-                <span style="color: var(--color-999);">腾讯会议</span>
-              </span>
-            </span>
-            <!-- 面试状态: '待接受'/'已取消' -->
-            <span :style="{ 'color': item.status !== '99' ? 'orange' :'var(--color-999)'}">
-              <v-icon size="30">mdi mdi-circle-small</v-icon>
-              <span>{{ statusList.find(e => e.value === item.status)?.label }}</span>
-            </span>
-            <span>
-              <span v-if="editStatus.indexOf(item.status)" class="font-size-15 color-primary" @click="handleActionClick(2, item)">修改面试</span>
-              <v-menu>
-                <template v-slot:activator="{ props }">
-                  <v-icon v-bind="props" class="mx-3" size="20" color="primary">mdi-dots-horizontal</v-icon>
-                </template>
-                <v-list>
-                  <v-list-item
-                    v-for="(k, index) in actionItems"
-                    :key="index"
-                    :value="index"
-                    color="primary"
-                    @click="handleActionClick(k.value, item)"
-                  >
-                    <v-list-item-title>{{ k.title }}</v-list-item-title>
-                  </v-list-item>
-                </v-list>
-              </v-menu>
-            </span>
+            <div class="d-flex align-center right-item">
+              <div style="min-width: 80px;text-align: center;">
+                <v-icon v-if="item?.phone" class="mx-1" size="20" color="primary">mdi-phone-outline</v-icon>
+                <span>{{ item?.phone || '-' }}</span>
+              </div>
+              <div>
+                <!-- 面试类型: 线下面试 -->
+                <span v-if="item.type === '1'">
+                  <v-icon class="mx-3" size="20" color="primary">mdi-account-multiple-outline</v-icon>
+                  <span>{{ $t('interview.offlineInterview') }}</span>
+                </span>
+                <!-- 面试类型: 线上面试 -->
+                <span v-else class="d-flex">
+                  <v-icon class="mx-3 mt-2" size="20" color="primary">mdi mdi-video-account</v-icon>
+                  <span class="d-flex flex-column">
+                    <span>{{ $t('interview.onlineInterview') }}</span>
+                    <span style="color: var(--color-999);">腾讯会议</span>
+                  </span>
+                </span>
+              </div>
+              <!-- 面试状态: '待接受'/'已取消' -->
+              <div :style="{ 'color': item.status !== '99' ? 'orange' :'var(--color-999)'}">
+                <v-icon size="30">mdi mdi-circle-small</v-icon>
+                <span>{{ statusList.find(e => e.value === item.status)?.label }}</span>
+              </div>
+              <div>
+                <span v-if="editStatus.indexOf(item.status)" class="font-size-15 color-primary" @click="handleActionClick(2, item)">修改面试</span>
+                <v-menu>
+                  <template v-slot:activator="{ props }">
+                    <v-icon v-bind="props" class="mx-3" size="20" color="primary">mdi-dots-horizontal</v-icon>
+                  </template>
+                  <v-list>
+                    <v-list-item
+                      v-for="(k, index) in actionItems"
+                      :key="index"
+                      :value="index"
+                      color="primary"
+                      @click="handleActionClick(k.value, item)"
+                    >
+                      <v-list-item-title>{{ k.title }}</v-list-item-title>
+                    </v-list-item>
+                  </v-list>
+                </v-menu>
+              </div>
+            </div>
           </div>
           <CtPagination
             v-if="total > 0"
@@ -305,5 +309,11 @@ const handleCancelSubmit = async () => {
   &:hover {
     background-color: var(--color-f8);
   }
+  .right-item {
+    width: 100%;
+    div {
+      width: 25%;
+    }
+  }
 }
 </style>

+ 1 - 1
src/views/recruit/enterprise/personnelManagement/components/screen.vue

@@ -28,7 +28,7 @@ import CommonStyle from './commonStyle.vue'
 
 const emit = defineEmits(['search', 'reset'])
 const props = defineProps({
-  tab: String
+  tab: Number
 })
 
 const list = ref([

+ 21 - 10
src/views/recruit/enterprise/personnelManagement/components/table.vue

@@ -1,8 +1,7 @@
 <template>
   <div>
     <div class="text-end">
-      <v-btn v-if="tab === '0'" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 0)">不合适</v-btn>
-      <v-btn v-if="tab === '1'" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 1)">入职</v-btn>
+      <v-btn v-if="tab === 1" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 0)">入职</v-btn>
     </div>
     <v-data-table
       class="mt-3"
@@ -29,13 +28,13 @@
         </div>
       </template>
       <template v-slot:item.actions="{ item }">
-        <div v-if="tab === '0'">
+        <div v-if="tab === 0">
           <v-btn color="primary" variant="text" @click="handlePreviewResume(item)">查看简历</v-btn>
-          <v-btn color="primary" variant="text" @click="handleAction('', 0, item)">不合适</v-btn>
         </div>
-        <div v-if="tab === '1'">
+        <v-btn v-if="tab === 0 || tab === 1" color="primary" variant="text" @click="handleEliminate(item)">不合适</v-btn>
+        <div v-if="tab === 1">
           <v-btn color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn>
-          <v-btn color="primary" variant="text" @click="handleAction('', 1, item)">入职</v-btn>
+          <v-btn color="primary" variant="text" @click="handleAction('', 0, item)">入职</v-btn>
         </div>
       </template>
     </v-data-table>
@@ -52,8 +51,8 @@ import Snackbar from '@/plugins/snackbar'
 
 const { t } = useI18n()
 const emit = defineEmits(['refresh'])
-defineProps({
-  tab: String,
+const props = defineProps({
+  tab: Number,
   items: Array
 })
 const badgeColor = computed(() => (item) => {
@@ -82,11 +81,10 @@ const handleToPersonDetail = ({ userId, id }) => {
 }
 
 const apiList = [
-  joinEliminate, // 不合适
   personEntryByEnterprise // 入职
 ]
 
-// 不合适、入职
+// 入职
 const handleAction = async (type, index, item) => {
   const ids = type ? selected.value : [item?.id]
   if (!ids) return
@@ -95,6 +93,19 @@ const handleAction = async (type, index, item) => {
   emit('refresh')
 }
 
+// 不合适
+const handleEliminate = async (item) => {
+  if (!item.id || !item?.job?.id) return
+  const query = {
+    bizId: item.id,
+    jobId: item.job.id,
+    type: props.tab === 0 ? '0' : '1' // 投递简历0 已邀约1
+  }
+  await joinEliminate(query)
+  Snackbar.success(t('common.operationSuccessful'))
+  emit('refresh')
+}
+
 // 查看简历
 const handlePreviewResume = async ({ url, id }) => {
   if (!url || !id) return

+ 15 - 15
src/views/recruit/enterprise/personnelManagement/index.vue

@@ -1,6 +1,6 @@
 <!-- 精英管理 -->
 <template>
-  <v-card class="pa-5 card-box">
+  <v-card class="pa-3 card-box">
     <div class="d-flex justify-space-between">
       <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
         <v-tab v-for="k in tabList" :value="k.value" :key="k.value">{{ k.label }}</v-tab>
@@ -28,8 +28,9 @@
 defineOptions({ name: 'enterprise-personnelManagement-management'})
 import { ref } from 'vue'
 import { getPersonCvPage } from '@/api/enterprise'
-import { getDict } from '@/hooks/web/useDictionaries'
+import { personCvUnfitPage } from '@/api/recruit/enterprise/personnel'
 import { dealDictObjData } from '@/utils/position'
+import { getInterviewInvitePage } from '@/api/recruit/enterprise/interview'
 import TablePage from './components/table.vue'
 import Screen from './components/screen.vue'
 
@@ -37,11 +38,17 @@ const total = ref(0)
 const query = ref({
   pageNo: 1,
   pageSize: 10,
-  status: 0,
+  status: null,
   type: 0
 })
-const tab = ref('0')
-const tabList = ref([])
+const tab = ref(0)
+const tabList = ref([
+  { label: '投递简历', value: 0, api: getPersonCvPage, status: null },
+  { label: '已邀约', value: 1, api: getInterviewInvitePage, status: '0' },
+  { label: '已发offer', value: 2, api: getInterviewInvitePage, status: '1' },
+  { label: '已入职', value: 3, api: getInterviewInvitePage, status: '2' },
+  { label: '不合适', value: 4, api: personCvUnfitPage },
+])
 const textItems = ref({
   type: 'text',
   value: '',
@@ -51,19 +58,12 @@ const textItems = ref({
   appendInnerIcon: 'mdi-magnify'
 })
 
-// 获取tab列表项
-const getTabData = () => {
-  getDict('menduner_job_cv_status').then(({ data }) => {
-    data = data?.length && data || []
-    tabList.value = data
-  })
-}
-getTabData()
-
 // 获取牛人列表
 const items = ref([])
 const getList = async () => {
-  const { list, total: number } = await getPersonCvPage(query.value)
+  const api = tabList.value[tab.value].api
+  query.value.status = tab.value === 0 ? null : tabList.value[tab.value].status
+  const { list, total: number } = await api(query.value)
   if (!list.length) {
     items.value = []
     total.value = 0

+ 40 - 45
src/views/recruit/personal/message/components/chatting.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="chatting">
+  <div class="chatting d-flex flex-column">
     <div class="top-info">
       <div class="user-info d-flex align-center">
         <p class="d-flex align-center float-left">
@@ -15,8 +15,8 @@
         <span class="color-333">广州</span>
       </div>
     </div>
-    <div class="chat-record mt-3">
-      <div class="message-box" @scroll="handleScroll" ref="chatRef">
+    <div class="mt-3 message-box" @scroll="handleScroll" ref="chatRef">
+      <div>
         <div v-for="(val, i) in items" :key="i" :id="val.id">
           <div class="time-box">{{ val.time }}</div>
           <div :class="['message-view_item', val.userId === myUserId ? 'is-self' : 'is-other']">
@@ -168,56 +168,51 @@ const handleScroll = (e) => {
     }
   }
 }
-.chat-record {
-  
-  padding-bottom: 30px;
-  .message-box {
-    height: 525px;
-    padding: 0 30px 20px;
-    overflow-y: auto;
-    .time-box {
-      user-select: none;
-      position: relative;
-      top: 8px;
-      margin: 20px 0;
-      max-height: 20px;
-      text-align: center;
-      font-weight: 400;
-      font-size: 12px;
-      color: var(--color-time-divider);
-    }
-    .message-view_item {
-      display: flex;
-      flex-direction: row;
-      align-items: center;
-      margin: 8px 0;
-      position: relative;
-      .message-text {
-        background-color: #f0f2f5;
-        border-radius: 6px;
-        max-width: 85%;
-        padding: 10px;
-      }
+.message-box {
+  flex: 1;
+  padding: 0 30px 30px;
+  overflow-y: auto;
+  .time-box {
+    user-select: none;
+    position: relative;
+    top: 8px;
+    margin: 20px 0;
+    max-height: 20px;
+    text-align: center;
+    font-weight: 400;
+    font-size: 12px;
+    color: var(--color-time-divider);
+  }
+  .message-view_item {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    margin: 8px 0;
+    position: relative;
+    .message-text {
+      background-color: #f0f2f5;
+      border-radius: 6px;
+      max-width: 85%;
+      padding: 10px;
     }
-    .is-self {
-      flex-direction: row-reverse;
-      display: flex;
-      .message-text {
-        margin-right: 10px;
-      }
+  }
+  .is-self {
+    flex-direction: row-reverse;
+    display: flex;
+    .message-text {
+      margin-right: 10px;
     }
-    .is-other {
-      .message-text {
-        margin-left: 10px;
-      }
+  }
+  .is-other {
+    .message-text {
+      margin-left: 10px;
     }
   }
 }
 .bottom-info {
-  position: absolute;
-  bottom: 0;
   width: 100%;
   height: 160px;
+  background-color: #fff;
 }
 input {
   outline: none;

+ 81 - 29
src/views/recruit/personal/message/index.vue

@@ -2,11 +2,53 @@
   <div class="default-width message pa-3">
     <div class="message-left">
       <div class="message-left-search d-flex align-center justify-center">
+        <!-- {{ connected ? '连接成功': '连接失败' }} -->
         <TextInput v-model="searchInputVal" :item="textItem" @appendInnerClick="handleSearch" @enter="handleSearch"></TextInput>
       </div>
       <div class="message-chat-box mt-5">
-        <div v-if="chatList.length">
-          <div class="chat-item d-flex align-center" v-for="(val, i) in chatList" :key="i">
+        <v-overlay
+          :model-value="!connected"
+          contained
+          class="align-center justify-center"
+        >
+          <v-progress-circular
+            color="primary"
+            size="64"
+            indeterminate
+          ></v-progress-circular>
+        </v-overlay>
+        <div v-if="conversationList.length">
+          <v-list density="compact">
+            <v-list-item
+              v-for="(val, i) in conversationList"
+              :key="i"
+              :value="val"
+              color="primary"
+              :title="val.name + val.enterpriseName + val.postName"
+              :subtitle="val.tip"
+            >
+              <template v-slot:prepend>
+                <v-avatar :image="val.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+              </template>
+              <template v-slot:append>
+                <v-btn
+                  color="grey-lighten-1"
+                  icon="mdi-information"
+                  variant="text"
+                ></v-btn>
+              </template>
+
+
+              <!-- <v-list-item-title>
+                <span class="font-size-15">{{ val.name }}</span>
+                <span class="ml-3 title-box">{{ val.enterpriseName }}</span>
+                <span class="septal-line"></span>
+                <span>{{ val.postName }}</span>
+                <span>17.25</span>
+              </v-list-item-title> -->
+            </v-list-item>
+          </v-list>
+          <!-- <div class="chat-item d-flex align-center" v-for="(val, i) in chatList" :key="i">
             <v-avatar :image="val.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
             <div class="ml-3 font-size-13">
               <div class="chat-item-time color-999">17.25</div>
@@ -18,7 +60,7 @@
               </div>
               <div class="color-999 mt-1">{{ val.tip }}</div>
             </div>
-          </div>
+          </div> -->
           <div class="message-no-more-text mt-3">没有更多了</div>
         </div>
         <div v-else class="left-noData">
@@ -40,13 +82,23 @@
 defineOptions({ name: 'personal-message-index'})
 import { ref } from 'vue'
 import Chatting from './components/chatting.vue'
-// import { getChatKey } from '@/api/common'
+import { initKey, initConnect } from '@/hooks/web/useIM'
+// 实例
+
+import { useUserStore } from '@/store/user'
+
+const userStore = useUserStore()
+
+if (Object.keys(userStore.IMConfig).length === 0) {
+  await initKey()
+}
+
+const { connected, conversationList } = initConnect()
+
+console.log('connected.value', connected.value)
+
+// const IMStatus = ref(isConnected)
 
-// const getKey = async () => {
-//   const data = await getChatKey({ userId: '1' })
-//   console.log(data, 'key')
-// }
-// getKey()
 
 const showRightNoData = ref(false)
 const searchInputVal = ref()
@@ -61,26 +113,26 @@ const textItem = ref({
 })
 
 // 左侧聊天列表
-const chatList = ref([
-  {
-    id: 1,
-    name: '钟女士',
-    avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg',
-    enterpriseName: '泰康泰康泰康泰康泰康泰康',
-    postName: '人事HR',
-    updateTime: 1715337950000,
-    tip: '你好'
-  },
-  {
-    id: 2,
-    name: '林先生',
-    avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg',
-    enterpriseName: '众宝联合',
-    postName: '人力资源主管',
-    updateTime: 1715337950000,
-    tip: '你好'
-  }
-])
+// const chatList = ref([
+//   {
+//     id: 1,
+//     name: '钟女士',
+//     avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg',
+//     enterpriseName: '泰康泰康泰康泰康泰康泰康',
+//     postName: '人事HR',
+//     updateTime: 1715337950000,
+//     tip: '你好'
+//   },
+//   {
+//     id: 2,
+//     name: '林先生',
+//     avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg',
+//     enterpriseName: '众宝联合',
+//     postName: '人力资源主管',
+//     updateTime: 1715337950000,
+//     tip: '你好'
+//   }
+// ])
 
 const handleSearch = () => {
   console.log(searchInputVal.value, 'search')

+ 13 - 2
src/views/recruit/personal/position/components/details.vue

@@ -11,8 +11,10 @@
       </div>
       <div class="banner-tags mt-4">
         <span v-for="k in desc" :key="k.mdi" class="mr-10">
-          <v-icon color="var(--color-666)" size="20">{{ k.mdi }}</v-icon>
-          <span class="ml-1">{{ positionInfo[k.value] }}</span>
+          <span v-if="positionInfo[k.value]">
+            <v-icon color="var(--color-666)" size="20">{{ k.mdi }}</v-icon>
+            <span class="ml-1">{{ positionInfo[k.value] }}</span>
+          </span>
         </span>
       </div>
       <div class="banner-tools my-4">
@@ -24,6 +26,15 @@
           <v-chip v-if="info.hire && info.hirePoint && info.hirePoint > 0" label color="primary">积分:{{ commissionCalculation(info.hirePoint, 1) }}点</v-chip>
         </div>
         <div class="banner-tools-btns">
+          <v-btn
+            class="radius mr-2 button-item"
+            variant="outlined"
+            color="error"
+            prepend-icon="mdi-share-outline"
+            style="height: 36px;"
+            v-if="info?.hire && info?.hirePoint && info?.hirePoint > 0"
+            @click="handleShare"
+          >我要赏金</v-btn>
           <v-btn
             class="radius mr-2 button-item"
             variant="outlined"