Ver Fonte

微信客服功能

@dongkboy há 11 meses atrás
pai
commit
1e03b07714

+ 325 - 12
package-lock.json

@@ -16,14 +16,17 @@
         "@vueuse/core": "^12.0.0",
         "@vueuse/integrations": "^12.0.0",
         "axios": "^1.7.9",
+        "chinese-days": "^1.5.1",
         "dayjs": "^1.11.13",
         "defu": "^6.1.4",
         "disable-devtool": "^0.3.8",
         "element-plus": "^2.9.0",
         "eruda": "^3.4.1",
+        "file-saver": "^2.0.5",
         "floating-vue": "5.2.2",
         "hotkeys-js": "^3.13.7",
         "js-pinyin": "^0.2.7",
+        "jszip": "^3.10.1",
         "lodash-es": "^4.17.21",
         "mitt": "^3.0.1",
         "mockjs": "^1.1.0",
@@ -33,6 +36,8 @@
         "path-browserify": "^1.0.1",
         "path-to-regexp": "^8.2.0",
         "pinia": "^2.3.0",
+        "qrcode": "^1.5.4",
+        "qrcode.vue": "^3.6.0",
         "qs": "^6.13.1",
         "scule": "^1.3.0",
         "vconsole": "^3.15.1",
@@ -5529,7 +5534,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -6588,6 +6592,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/chinese-days": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/chinese-days/-/chinese-days-1.5.1.tgz",
+      "integrity": "sha512-tMwllz6674sN9z/PexawTu7Ypiba0X7X4Qs8gvuZl+CIjyYrl8jOuYZQwAtPyjPNqwTwE2GqSSPJK218crbCWg==",
+      "license": "MIT"
+    },
     "node_modules/chokidar": {
       "version": "4.0.3",
       "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz",
@@ -6913,7 +6923,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "color-name": "~1.1.4"
@@ -6926,7 +6935,6 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/colord": {
@@ -7151,7 +7159,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/cors": {
@@ -7465,6 +7472,15 @@
         }
       }
     },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/decode-named-character-reference": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
@@ -7690,6 +7706,12 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/dijkstrajs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+      "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+      "license": "MIT"
+    },
     "node_modules/dir-glob": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -9358,6 +9380,12 @@
         "node": ">=16.0.0"
       }
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+      "license": "MIT"
+    },
     "node_modules/filesize": {
       "version": "10.1.6",
       "resolved": "https://registry.npmmirror.com/filesize/-/filesize-10.1.6.tgz",
@@ -9738,7 +9766,6 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true,
       "license": "ISC",
       "engines": {
         "node": "6.* || 8.* || >= 10.*"
@@ -10555,6 +10582,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+      "license": "MIT"
+    },
     "node_modules/immutable": {
       "version": "5.0.3",
       "resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.0.3.tgz",
@@ -11104,7 +11137,6 @@
       "version": "2.0.4",
       "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true,
       "license": "ISC"
     },
     "node_modules/ini": {
@@ -12080,6 +12112,48 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jszip": {
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "license": "(MIT OR GPL-3.0-or-later)",
+      "dependencies": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
+    "node_modules/jszip/node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+      "license": "MIT"
+    },
+    "node_modules/jszip/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/jszip/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
@@ -12195,6 +12269,15 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "license": "MIT",
+      "dependencies": {
+        "immediate": "~3.0.5"
+      }
+    },
     "node_modules/liftoff": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/liftoff/-/liftoff-4.0.0.tgz",
@@ -14865,7 +14948,6 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=6"
@@ -14885,6 +14967,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "license": "(MIT AND Zlib)"
+    },
     "node_modules/param-case": {
       "version": "3.0.4",
       "resolved": "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz",
@@ -15049,7 +15137,6 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
       "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -15261,6 +15348,15 @@
         "node": ">=4"
       }
     },
+    "node_modules/pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/portfinder": {
       "version": "1.0.32",
       "resolved": "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.32.tgz",
@@ -15754,7 +15850,6 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
       "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/prompts": {
@@ -15793,6 +15888,203 @@
         "node": ">=6"
       }
     },
+    "node_modules/qrcode": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+      "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+      "license": "MIT",
+      "dependencies": {
+        "dijkstrajs": "^1.0.1",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      },
+      "bin": {
+        "qrcode": "bin/qrcode"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/qrcode.vue": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz",
+      "integrity": "sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/qrcode/node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/qrcode/node_modules/cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/qrcode/node_modules/find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "license": "MIT",
+      "dependencies": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "license": "MIT",
+      "dependencies": {
+        "p-locate": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "license": "MIT",
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/qrcode/node_modules/p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "license": "MIT",
+      "dependencies": {
+        "p-limit": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/wrap-ansi": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+      "license": "ISC"
+    },
+    "node_modules/qrcode/node_modules/yargs": {
+      "version": "15.4.1",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^6.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^4.1.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^4.2.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^18.1.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "license": "ISC",
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/qs": {
       "version": "6.13.1",
       "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.1.tgz",
@@ -16325,7 +16617,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.10.0"
@@ -16341,6 +16632,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "license": "ISC"
+    },
     "node_modules/requires-port": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
@@ -16609,7 +16906,6 @@
       "version": "5.1.2",
       "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/safe-push-apply": {
@@ -17102,6 +17398,12 @@
         "upper-case-first": "^2.0.2"
       }
     },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+      "license": "ISC"
+    },
     "node_modules/set-function-length": {
       "version": "1.2.2",
       "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -17165,6 +17467,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+      "license": "MIT"
+    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -17912,7 +18220,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -21780,6 +22087,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/which-module": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+      "license": "ISC"
+    },
     "node_modules/which-typed-array": {
       "version": "1.1.18",
       "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.18.tgz",

+ 1 - 0
package.json

@@ -30,6 +30,7 @@
     "@vueuse/core": "^12.0.0",
     "@vueuse/integrations": "^12.0.0",
     "axios": "^1.7.9",
+    "chinese-days": "^1.5.1",
     "dayjs": "^1.11.13",
     "defu": "^6.1.4",
     "disable-devtool": "^0.3.8",

+ 2 - 0
src/api/index.ts

@@ -24,6 +24,7 @@ import agreementApi  from "@/api/modules/agreement";
 import financeApi from '@/api/modules/finance.ts'
 import invoiceRecordApi from '@/api/modules/invoiceRecord.ts'
 import settlementApi from '@/api/modules/settlement.ts'
+import wechatApi from '@/api//modules/wechatService.ts'
 
 // import { useRouter } from 'vue-router'
 // const router = useRouter()
@@ -136,5 +137,6 @@ const api = {
   financeApi: financeApi(axiosInstance),
   invoiceRecordApi: invoiceRecordApi(axiosInstance),
   settlementApi: settlementApi(axiosInstance),
+  wechatApi:wechatApi(axiosInstance)
 }
 export default api

+ 22 - 0
src/api/modules/wechatService.ts

@@ -0,0 +1,22 @@
+import { ApiInstance } from '../type'
+import * as Types from '../types/wechatServiceTypes';
+const wechatApi = (axiosInstance: ApiInstance) => ({
+  // 分页查询
+  wechatServicePage: (data: Types.RequestPage) => axiosInstance.post('organization/wechat/customerService/manage/pageQuery', data),
+  // 添加客服
+  wechatServiceAdd: (data: Types.addRequest) => axiosInstance.post('organization/wechat/customerService/manage', data),
+  // 删除客服
+  wechatServiceDel: (customerServiceId: any) => axiosInstance.delete(`organization/wechat/customerService/manage/${customerServiceId}`),
+  // 修改
+  wechatServicePut: (customerServiceId: any,data:any) => axiosInstance.put(`organization/wechat/customerService/manage/${customerServiceId}`,data),
+  // id查询
+  wechatServiceDetail: (customerServiceId: any) => axiosInstance.get(`organization/wechat/customerService/manage/${customerServiceId}`),
+  //获取值班配置
+  getDutyConfig: (customerId: any) => axiosInstance.get(`organization/wechat/customerService/manage/getDutyConfig/${customerId}`),
+  //修改值班配置
+  dutyConfigEdit: (data: any) => axiosInstance.put(`organization/wechat/customerService/manage/dutyConfig`, data),
+  //获取企微用户列表
+  getWechatDeptUser: () => axiosInstance.get(`organization/wechat/customerService/manage/getDeptUser`),
+})
+
+export default wechatApi

+ 45 - 0
src/api/types/wechatServiceTypes.ts

@@ -0,0 +1,45 @@
+/**
+ * 添加客服
+ */
+export type addRequest = {
+  /**
+   * 客服头像
+   */
+  avatar?: string;
+  /**
+   * 昵称
+   */
+  nickname: string;
+  /**
+   * 接待人企业微信客服
+   */
+  receptionist: string;
+  /**
+   * 接待人企业微信用户id
+   */
+  receptionistId: string;
+  /**
+   * 后线人员
+   */
+  userId: string;
+  [property: string]: any;
+}
+
+/**
+ * 分页客服数据
+ */
+export type RequestPage = {
+  /**
+   * 昵称
+   */
+  nickname?: string;
+  /**
+   * 页码
+   */
+  pageNum?: number;
+  /**
+   * 每页数据量
+   */
+  pageSize?: number;
+  [property: string]: any;
+}

+ 2 - 1
src/assets/styles/globals.css

@@ -230,6 +230,7 @@ textarea {
 
 .vh100 {
   height: calc(100vh - 150px);
+  border-radius: 5px;
 }
 
 .strong {
@@ -266,7 +267,7 @@ textarea {
 }
 
 .m-right-10 {
-  margin-right: 20px;
+  margin-right: 10px;
 }
 
 .m-right-20 {

+ 27 - 3
src/components/complexTable/index.vue

@@ -7,7 +7,8 @@
       <el-table ref="multipleTableRef"
         :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }"
         :data="props.tableData" :height="tableHeight" :max-height="maxHeight" table-layout="fixed" style="width: 100%;"
-        @selection-change="selectionChange" @select-all="selectAllChange" :highlight-current-row="highlightCurrentRow"
+        @selection-change="selectionChange" @select-all="selectAllChange"  @cell-mouse-enter="handleMouseEnter"
+        @cell-mouse-leave="handleMouseLeave" :highlight-current-row="highlightCurrentRow"
         @current-change="currentChange" v-loading="loading">
         <!-- 空内容自定义 -->
         <template #empty>
@@ -58,11 +59,17 @@
           <template #header>
             <slot name="search" :slotData="column"></slot>
           </template>
-          <template #default="scope">
+          <template #default="scope" v-if="column.component === 'customSlot'">
+            <slot name="customSlot" :data="scope.row"></slot>
+          </template>
+          <template #default="scope" v-else>
             <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)"
               class="custom-link">
               {{ scope.row[column.prop] }}
             </RouterLink>
+            <div v-else-if="column.component === 'html'">
+              <span   v-html="htmlEvent(scope.row, column).html"></span>
+            </div>
             <el-tag v-else-if="column.component === 'Tag'" :type="getTagTypeAndText(scope.row, column).type">
               {{ getTagTypeAndText(scope.row, column).text }}
             </el-tag>
@@ -105,7 +112,7 @@ import type { TableInstance } from "element-plus";
 interface TableRow {
   [key: string]: any;
 }
-const emit = defineEmits(['getList', 'selectAllChange', 'selectionChange', 'currentChange']);
+const emit = defineEmits(['getList', 'selectAllChange', 'selectionChange', 'currentChange','handleMouseEnter','handleMouseLeave']);
 interface Props{
   tableData: Array<TableRow>;
   columnwidth: number,//操作栏宽度
@@ -209,6 +216,20 @@ const selectAllChange = (value: any[]) => {
   emit('selectAllChange', newSelectionList.value)
 
 }
+/**
+ * //单元格移入事件
+ * @param row 行数据
+ */
+const handleMouseEnter=(row:any)=>{
+  emit('handleMouseEnter', row)
+}
+/**
+ * //单元格移出事件
+ * @param row 行数据
+ */
+ const handleMouseLeave=(row:any)=>{
+  emit('handleMouseLeave', row)
+}
 //行点击事件
 const currentChange = (currentRow: any, _oldCurrentRow: any) => {
   emit('currentChange', currentRow)
@@ -253,6 +274,9 @@ function getTagTypeAndText(row: Row, column: any) {
 function getDisplayText(row: Row, column: any): string {
   return column.formatter ? column.formatter(row, column, row[column.prop]) : displayValue(row[column.prop]);
 }
+function htmlEvent(row: Row, column: any) {
+  return column.formatter ? column.formatter(row, column, row[column.prop]) : row[column.prop]
+}
 </script>
 <style lang="scss" scoped>
 .el-table .el-table__cell .routerlick > a {

+ 451 - 5
src/views/personnel/holidaySchedule.vue

@@ -1,17 +1,463 @@
 <!-- 模板区域 -->
 <template>
-<div>
-<PageMain>
-</PageMain>
-</div>
+  <div>
+    <PageMain style="margin-bottom: 92px;height: 100vh;">
+      <!-- 头部 -->
+      <div class="pageheader flex a-c ">
+        <div class="tag"></div>
+        <span class="m-left-10">节假日排班</span>
+      </div>
+      <!-- 表单区域 -->
+      <el-form ref="scheduleRef" class="m-top-20" style="max-width: 600px" :model="scheduleForm" :rules="scheduleRules"
+        label-width="auto" label-position="top">
+        <el-form-item label="客服账号" prop="customerId">
+          <el-select v-model="scheduleForm.customerId" placeholder="选择客服账号" filterable>
+            <el-option :label="item.nickname" :value="String(item.id)" v-for="item in fetchSupportAgentsList"
+              :key="item.id" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <!-- 日期滑动组件 -->
+      <div class="flex a-c ">
+        <div class="dateSlider flex">
+          <!-- 左键 -->
+          <div class="leftClick flex a-c j-c " @click="moveLeft">
+            <el-icon>
+              <CaretLeft />
+            </el-icon>
+          </div>
+          <!-- 右键 -->
+          <div class="rightClick flex a-c j-c" @click="moveRight">
+            <el-icon>
+              <CaretRight />
+            </el-icon>
+          </div>
+          <!-- 滑动区域 -->
+          <div class="sliderArea flex" :style="{ transform: `translateX(${translateX}px)` }">
+            <div class="item flex f-c a-c j-c" :class="{ active: activeDateIndex === item.id, holiday: item.holiday }"
+              @click="dateClick(item)" v-for="(item, index) in monthDateList" :key="index">
+              <el-badge :is-dot="true" :offset="[10, 0]">
+                <span>{{ item.dayWeek }}</span>
+              </el-badge>
+              <span>{{ item.date }}</span>
+            </div>
+          </div>
+        </div>
+        <!-- 日历选择 -->
+        <div class="iconClass flex a-c j-c " @click="openDatePicker">
+          <el-icon size="24">
+            <Calendar />
+          </el-icon>
+        </div>
+        <!-- 日期选择器 -->
+        <el-date-picker v-model="dateValue" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" ref="datePickerRef"
+          @change="datePickerChange" style=" position: recative;
+        z-index: -200;
+              transform: translate(-120px, 0px);" />
+      </div>
+      <!-- 客服名字 -->
+      <div class="m-top-20" style="font-size: 16px;color: #333;font-weight: bold;">配置 <span style="color: #0083FF;">{{
+        idName }}</span> 值班客服</div>
+      <!-- 表格区域 -->
+      <el-button type="primary" class="m-top-20 m-btn-20" @click="dutyScheduleAdd">
+        <template #icon>
+          <el-icon>
+            <Plus />
+          </el-icon>
+        </template>
+        添加值班信息
+      </el-button>
+      <el-table :data="shiftDataList" style="width: 100%" align="conter" :header-cell-style="{
+        'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6',
+        'font-size': '15px', 'color': '#333', 'padding': '10px 0'
+      }">
+        <el-table-column prop="date" label="值班时间" align="center">
+          <template #default="scope">
+            <el-time-picker v-model="scope.row.startTime" placeholder="开始时间" value-format="HH:mm:ss"
+              format="HH:mm:ss" />
+            <span class="m-left-10 m-right-10">至</span>
+            <el-time-picker v-model="scope.row.endTime" placeholder="结束时间" value-format="HH:mm:ss" format="HH:mm:ss" />
+          </template>
+        </el-table-column>
+        <el-table-column prop="dutyCustomerServiceId" label="值班客服" align="center" width="350">
+          <template #default="scope">
+            <el-select v-model="scope.row.dutyCustomerServiceId" placeholder="选择值班客服" filterable>
+              <el-option :label="item.nickname" :value="item.id" v-for="item in fetchSupportAgentsList"
+                :key="item.id" />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column prop="address" label="操作" align="center">
+          <template #default="scope">
+            <el-button link :type="scope.$index == 0 ? 'info' : 'primary'" size="small" :disabled="scope.$index == 0"
+              @click="del(scope.$index)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </PageMain>
+    <FixedActionBar>
+      <ElButton @click="router.back()">
+        取消
+      </ElButton>
+      <ElButton type="primary" @click="submitForm(scheduleRef)">
+        保存
+      </ElButton>
+    </FixedActionBar>
+  </div>
 </template>
 
 <!-- 行为区域 -->
 <script lang='ts' setup>
-import { ref,reactive } from 'vue'
+import { ref, reactive } from 'vue'
 import api from '@/api'
+import { ElMessage } from 'element-plus';
+import type { FormRules, FormInstance } from 'element-plus';
+import { useRouter } from "vue-router";
+import chineseDays from 'chinese-days'
+const router = useRouter();
+const route = useRoute();
+// 表单类型声明
+interface scheduleForm {
+  customerId: string
+}
+const id = ref(route.query.id as string);//id参数
+const idName = ref(route.query.name as string);//id参数
+const scheduleRef = ref<FormInstance>()
+//表单
+const scheduleForm = reactive<scheduleForm>({
+  customerId: "",
+})
+//验证规则
+const scheduleRules = ref<FormRules<scheduleForm>>({
+  customerId: [
+    { required: true, message: '客户昵称必填', trigger: 'blur' },
+  ],
+})
+// 值班列表数据
+const shiftDataList = ref()
+onMounted(() => {
+  fetchSupportAgents()//获取客服列表
+  scheduleForm.customerId = id.value;
+  fetchDutyConfig();
+  datePickerChange(today())//获取传入时间的值班信息
+  console.log(today())
+})
+const activeDateIndex = ref();//选中日期下标
+const dateValue = ref("");//选择日期
+const datePickerRef = ref();//日期选择器引用
+//日期选择器事件
+const datePickerChange = (value: any) => {
+  const inputDate = new Date(value); // 输入日期
+  monthDateList.value = getDatesInMonth(inputDate);//获取传入日期自然月日期集合
+  activeDateIndex.value = value;
+  slideEvent(activeDateIndex.value);//滑动到选择日期自然周
+  shiftDataList.value = monthDateList.value.find((val: any) => val.id == activeDateIndex.value).dutyConfig;
+
+}
+// 手动打开日期选择器
+const openDatePicker = () => {
+  if (datePickerRef.value) {
+    datePickerRef.value.focus();
+  }
+};
+//日期点击事件
+const dateClick = (value: any) => {
+  activeDateIndex.value = value.id;
+  shiftDataList.value = monthDateList.value.find((val: any) => val.id == activeDateIndex.value).dutyConfig;
+}
+const filteredData = () => {
+  const filteredData = monthDateList.value.filter((item: any) =>
+    item.dutyConfig.some((config: any) =>
+      config.startTime && config.endTime && config.dutyCustomerServiceId
+    )
+  );
+  return filteredData
+}
+//配置接口
+const submitForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      api.wechatApi.dutyConfigEdit({
+        customerId: scheduleForm.customerId,
+        dutyList: filteredData()//过滤已配置节假日
+      }).then((res: any) => {
+        if (res.code == 200) {
+          ElMessage({
+            type: 'success',
+            message: '配置成功',
+            plain: true,
+          })
+        }
+      })
+    } else {
+
+    }
+  })
+}
+const fetchSupportAgentsList = ref();
+const fetchSupportAgents = () => {
+  api.wechatApi.wechatServicePage({
+    pageNum: 1,
+    pageSize: 10000,
+  }).then((res: any) => {
+    if (res.code == 200) {
+      fetchSupportAgentsList.value = res.data.records;
+    }
+  })
+}
+
+//获取节假日排班配置
+const fetchDutyConfig = () => {
+  api.wechatApi.getDutyConfig(id.value).then((res: any) => {
+    if (res.code == 200) {
+      monthDateList.value.map((val: any) => {
+        res.data.map((item: any) => {
+          if (val.dutyDate == item.dutyDate) {
+            val.dutyConfig = item.dutyConfig;
+          }
+        })
+        return val;
+      })
+      shiftDataList.value = monthDateList.value.find((val: any) => val.id == activeDateIndex.value).dutyConfig;
+    }
+  })
+}
+/**
+ * 滑动事件
+ */
+const monthDateList = ref();//当月日期集合
+const currentIndex = ref(0); // 初始化为0,表示第一个滑动项
+const slideWidth = 154; // 每个滑动项的宽度
+const translateX = ref(0); // 用于控制滑动项的位移
+const moveLeft = () => {
+  if (currentIndex.value > 0) {
+    currentIndex.value -= 7; // 每次左移7天
+    if (currentIndex.value < 0) {
+      currentIndex.value = 0; // 防止越界
+    }
+    translateX.value = -currentIndex.value * slideWidth; // 更新位移
+  }
+};
+
+const moveRight = () => {
+  if (currentIndex.value < monthDateList.value.length - 7) { // 确保不超出范围
+    currentIndex.value += 7; // 每次右移7天
+    if (currentIndex.value > monthDateList.value.length - 7) {
+      currentIndex.value = monthDateList.value.length - 7; // 防止越界
+    }
+    translateX.value = -currentIndex.value * slideWidth; // 更新位移
+  }
+};
+//处理默认滑动
+const slideEvent = (time: any) => {
+  const selectedDateIndex = monthDateList.value.findIndex((dateItem: any) => dateItem.id === time);//获取日期所在数组索引
+  if (selectedDateIndex !== -1) {
+    // 计算该日期所在的自然周的起始索引
+    const weekStartIndex = Math.floor(selectedDateIndex / 7) * 7;
+    currentIndex.value = weekStartIndex; // 更新为自然周的起始索引
+    translateX.value = -currentIndex.value * slideWidth; // 更新位移
+  }
+}
+//获取传入日期自然月日期集合
+function getDatesInMonth(date: any) {
+  const year = date.getFullYear();
+  const month = date.getMonth();
+  // 获取该月的天数
+  const daysInMonth = new Date(year, month + 1, 0).getDate();
+  const datesArray = [];
+  // 生成该月的日期数组
+  for (let day = 1; day <= daysInMonth; day++) {
+    // 创建日期对象并格式化为 YYYY-MM-DD
+    const formattedDate = new Date(year, month, day + 1).toISOString().split('T')[0];
+    // 日期转换x月x日
+    const dateParts = formattedDate.split('-');
+    const displayDate = `${parseInt(dateParts[1], 10)}月${parseInt(dateParts[2], 10)}日`;
+    //获取是否节假日
+    const holiday = chineseDays.isHoliday(new Date(year, month, day));
+    //获取星期几
+    const dayOfWeek = new Date(year, month, day).toLocaleString('zh-CN', { weekday: 'long' });
+    datesArray.push({
+      id: formattedDate,
+      date: displayDate,
+      dayWeek: dayOfWeek,
+      holiday: holiday,
+      dutyDate: formattedDate,
+      dutyConfig: [
+        {
+          startTime: "",
+          endTime: "",
+          dutyCustomerServiceId: ""
+        }
+      ]
+    });
+  }
+  let activeIndex = datesArray.findIndex(val => val.id == today());//获取选中日期所在日期集合下标
+  datesArray.map((val: any, index: any) => {
+    val.badge = index > activeIndex && val.holiday;
+    return val;
+  })
+  console.log(datesArray)
+  return datesArray;
+}
+const today = () => {
+  const today = new Date();
+  const year = today.getFullYear();
+  const month = today.getMonth() + 1;
+  const date = today.getDate();
+  const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${date.toString().padStart(2, '0')}`;
+  return formattedDate
+}
+//动态添加值班信息
+const dutyScheduleAdd = () => {
+  shiftDataList.value.push({
+    startTime: '',
+    endTime: '',
+    dutyCustomerServiceId: '',
+  })
+}
+const del = (index: any) => {
+  shiftDataList.value.splice(index, 1);
+
+}
 </script>
 <!-- 样式区域 -->
 <style lang='scss' scoped>
+.pageheader {
+  padding-bottom: 12px;
+  box-sizing: border-box;
+  border-bottom: 1px solid #eee;
+
+  span {
+    font-size: 18px;
+    color: #333;
+    font-weight: bold;
+  }
+
+  .tag {
+    width: 4px;
+    height: 18px;
+    background: #0083FF;
+    border-radius: 10px 10px 10px 10px;
+  }
+}
+
+.dateSlider {
+  position: relative;
+  width: 1148px;
+  border-radius: 6px 6px 6px 6px;
+  padding: 10px 44px;
+  box-sizing: border-box;
+  background: #fff;
+  border: 1px solid #EEEEEE;
+  overflow: hidden;
+
+  // 左键
+  .leftClick {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 24px;
+    z-index: 99;
+    background: #FAFAFA;
+    border-radius: 6px 0px 0px 6px;
+  }
+
+  // 右键
+  .rightClick {
+    position: absolute;
+    top: 0;
+    right: 0;
+    height: 100%;
+    z-index: 99;
+    width: 24px;
+    background: #FAFAFA;
+    border-radius: 0px 6px 6px 0px;
+  }
+
+  .leftClick:hover,
+  .rightClick:hover {
+    color: #0083FF;
+    background: #F4FAFF;
+  }
+
+  .sliderArea {
+    white-space: nowrap;
+    transition: transform 0.3s ease;
+    /* 添加平滑过渡效果 */
+  }
+
+  .item {
+    width: 134px;
+    height: 100%;
+    padding: 7px 40px;
+    box-sizing: border-box;
+    border-radius: 10px 10px 10px 10px;
+    background: #F7F8FA;
+    margin-right: 20px;
+    color: #999;
+    user-select: none;
+    /* 禁用文本选择 */
+    cursor: pointer;
+
+    span:first-child {
+      font-weight: bold;
+      font-size: 18px;
+    }
+
+    span:last-child {
+      margin-top: 6px;
+      font-size: 14px;
+    }
+  }
+
+  .item:last-child {
+    margin: 0;
+    /* 最后一个元素不需要右边距 */
+  }
+
+  // 选中样式
+  .active {
+    background: #F4FAFF !important;
+    border-top: 4px solid #0083FF;
+
+    span {
+      color: #0083FF !important;
+    }
+  }
+
+  //非工作日样式
+  .holiday {
+    background: #fff;
+
+    span:first-child {
+      font-weight: bold;
+      font-size: 18px;
+      color: #333;
+    }
+
+    span:last-child {
+      margin-top: 6px;
+      font-size: 14px;
+      color: #666;
+
+    }
+  }
+}
+
+.iconClass {
+  width: 42px;
+  height: 42px;
+  margin-left: 32px;
+  border-radius: 6px;
+  background: #FAFAFA;
+  color: #999;
+}
 
+.iconClass:hover {
+  color: #0083FF;
+  background: #F4FAFF;
+  cursor: pointer;
+}
 </style>

+ 264 - 45
src/views/personnel/wechatService.vue

@@ -5,7 +5,7 @@
         <ElRow :gutter="20">
           <ElCol :md="6">
             <ElFormItem label="客服昵称">
-              <el-input v-model="formInline.companyName" placeholder="输入客服昵称查找" clearable class="el-inputs" />
+              <el-input v-model="formInline.nickname" placeholder="输入客服昵称查找" clearable class="el-inputs" />
             </ElFormItem>
           </ElCol>
           <ElCol :md="4">
@@ -18,10 +18,18 @@
         </ElRow>
       </ElForm>
       <ElDivider border-style="dashed"></ElDivider>
-      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="true"
-        :pageInfo="pageInfo" @getList="getList">
+      <ComplexTable :tableData="tableData" :columns="columns" :showIndex="false" :columnwidth="200" :showSelect="false"
+        :pageInfo="pageInfo" @getList="getList" @handleMouseEnter="handleMouseEnter"
+        @handleMouseLeave="handleMouseLeave">
         <template v-slot:table-title-btn>
-          <el-button type="primary" @click="ServiceAddShow = true">添加客服账号</el-button>
+          <el-button type="primary" @click="ServiceAdd">
+            <template #icon>
+              <el-icon>
+                <Plus />
+              </el-icon>
+            </template>
+            添加客服账号
+          </el-button>
         </template>
         <template v-slot:operation="scope">
           <el-button type="primary" link @click="ServiceUpdate(scope.operationData)"> 编辑
@@ -29,45 +37,62 @@
           <el-button type="primary" link @click="ServiceDel(scope.operationData)"> 删除
           </el-button>
         </template>
-         <template v-slot:search="scope">
-          {{ scope.slotData.label }}
+        <template v-slot:customSlot="scope">
+          <div class="flex a-c j-c " @click="goSupportStaff(scope.data)">
+            <el-tag :type="scope.data.scheduling ? 'primary' : 'danger'">{{ scope.data.scheduling ? '已配置' : '未配置'
+              }}</el-tag>
+            <el-icon class="m-left-10" v-if="!scope.data.scheduling && isHovered[scope.data.userId]">
+              <Edit />
+            </el-icon>
+            <el-icon class="m-left-10" v-else style="opacity: 0;">
+              <Edit />
+            </el-icon>
+          </div>
         </template>
       </ComplexTable>
     </PageMain>
-    <el-dialog v-model="ServiceAddShow" title="添加客服账号" width="30%">
+    <!-- 客服弹窗 -->
+    <el-dialog v-model="ServiceAddShow" :title="dialogTitle" width="30%">
       <el-form ref="ServiceFormRef" style="max-width: 600px" :model="ServiceForm" :rules="ServiceRules"
         label-width="auto" label-position="top">
-        <el-form-item label="客户昵称" prop="name">
-          <el-input v-model="ServiceForm.name" />
+        <el-form-item label="客服昵称" prop="nickname">
+          <el-input v-model="ServiceForm.nickname" placeholder="请输入客服昵称" maxlength="50" show-word-limit />
         </el-form-item>
-        <el-form-item label="客头像" class="avatar">
+        <el-form-item label="客头像" class="avatar">
           <div class="flex f-c">
-            <el-upload class="avatar-uploader" action="#" :show-file-list="false" :on-change="beforeAvatarUpload">
+            <el-upload class="avatar-uploader" action="#" :auto-upload="false" :show-file-list="false"
+              :on-change="beforeAvatarUpload">
               <img v-if="imageUrl" :src="imageUrl" />
               <el-icon v-else class="avatar-uploader-icon">
                 <Plus />
               </el-icon>
             </el-upload>
-            <span>支持png,jpg,jpeg的照片,大小不超过5MB</span>
+            <span>支持png,jpg,jpeg的照片,大小不超过2MB</span>
           </div>
         </el-form-item>
-        <el-form-item label="关联员工账号 (该账号为员工在掌柜体系内注册的账号)" prop="name">
-          <el-select v-model="ServiceForm.region" placeholder="请输入姓名查找">
-            <el-option label="Zone one" value="shanghai" />
-            <el-option label="Zone two" value="beijing" />
+        <el-form-item prop="userId">
+          <template #label>
+            关联员工账号 <span style="font-size: 12px;color: #999;">(该账号为员工在掌柜体系内注册的账号)</span>
+          </template>
+          <el-select v-model="ServiceForm.userId" placeholder="请输入姓名查找" clearable filterable>
+            <el-option :label="`${item.name}-${item.mobile}`" :value="item.userId" v-for="item in backendStaff"
+              :key="item.id" />
           </el-select>
         </el-form-item>
-        <el-form-item label="接待人员(该人员为企业微信生态内的员工)" prop="name">
-          <el-select v-model="ServiceForm.region" placeholder="请输入姓名查找">
-            <el-option label="Zone one" value="shanghai" />
-            <el-option label="Zone two" value="beijing" />
+        <el-form-item prop="receptionistId">
+          <template #label>
+            接待人员 <span style="font-size: 12px;color: #999;">(该人员为企业微信生态内的员工)</span>
+          </template>
+          <el-select v-model="ServiceForm.receptionistId" placeholder="请输入姓名查找" clearable filterable
+            @change="WeChatUserListChange">
+            <el-option :label="item.name" :value="item.userId" v-for="item in WeChatUserList" :key="item.userId" />
           </el-select>
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
           <el-button @click="ServiceAddShow = false">取消</el-button>
-          <el-button type="primary" @click="ServiceAddShow = false">
+          <el-button type="primary" @click="ServiceSave(ServiceFormRef)">
             确定
           </el-button>
         </div>
@@ -78,10 +103,13 @@
 
 <script lang="ts" setup>
 import api from '@/api';
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { Plus } from '@element-plus/icons-vue'
 import { ref, reactive } from 'vue'
+import { useRouter } from "vue-router";
+const router = useRouter();
+
 interface PageInfo {
   pageNum: number;
   pageSize: number;
@@ -89,21 +117,37 @@ interface PageInfo {
   total: number;
 }
 interface ServiceForm {
-  name: string
+  nickname: string
+  avatar: string
+  userId: string
+  receptionist: string
+  receptionistId: string
 
 }
-
+const dialogTitle = ref('添加客服账号');
+const dialogType = ref('add');
+const id=ref();
 const ServiceFormRef = ref<FormInstance>()
+//客服新增表单
 const ServiceForm = reactive<ServiceForm>({
-  name: 'Hello',
+  nickname: '',//客服昵称
+  avatar: '',//客服头像
+  userId: "",//后线id
+  receptionist: "",//企微名称
+  receptionistId: "",//企微id
 
 })
+//表单校验
 const ServiceRules = reactive<FormRules<ServiceForm>>({
-  name: [
-    { required: true, message: 'Please input Activity name', trigger: 'blur' },
-    { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
+  nickname: [
+    { required: true, message: '客户昵称必填', trigger: 'blur' },
+  ],
+  userId: [
+    { required: true, message: '关联账号必选', trigger: 'change' },
+  ],
+  receptionistId: [
+    { required: true, message: '接待人员必选', trigger: 'change' },
   ],
-
 })
 /**
  *   选择头像
@@ -113,6 +157,7 @@ const ServiceRules = reactive<FormRules<ServiceForm>>({
 const imageUrl = ref('')//图片回显
 //图片上传事件
 const beforeAvatarUpload = (file: any) => {
+
   if (file.raw.type !== 'image/jpeg') {
     ElMessage.error('只读取,png,jeg格式的图片')
     return false
@@ -120,19 +165,26 @@ const beforeAvatarUpload = (file: any) => {
     ElMessage.error('请上传小于2MB的图片')
     return false
   }
+  let formData = new FormData();
+  formData.append('file', file.raw)
+  formData.append('type', 'image')
+  api.areaApi.fileUpload(formData).then((res: any) => {
+    if (res.code == 200) {
+      ServiceForm.avatar = res.data.id;
+      imageUrl.value = res.data.downloadUrl;
+    }
+  })
 }
 const ServiceAddShow = ref(false)//添加客服弹窗
 
 //表格数据
 const tableData = ref([
   {
-    companyName:"1",
-    companyCode:"3213",
-    type:"3123",
+    avatar: "",
   }
 ]);
 //分页
-const pageInfo = ref({
+const pageInfo = reactive({
   pageNum: 1,
   pageSize: 20,
   sizes: [10, 20, 30, 40, 50],
@@ -140,41 +192,207 @@ const pageInfo = ref({
 });
 //检索条件
 const formInline = reactive({
-  companyName: '',//保险公司name
+  nickname: '',//保险公司name
 })
 onMounted(() => {
+  getbackendStaff();//获取后线人员
+  getWeChatUser();//获取企微用户
   findpage();
 
 });
 //分页查询方法
 function findpage() {
+  api.wechatApi.wechatServicePage({ ...pageInfo, ...formInline }).then((res: any) => {
+    if (res.code == 200) {
+      tableData.value = res.data.records;
+      pageInfo.pageNum = res.data.current;
+      pageInfo.pageSize = res.data.size;
+      pageInfo.total = res.data.total;
+    }
+  })
 }
 //表格字段
 const columns = [
-  { prop: "companyName", label: "客服头像", align: 'left', width: '150' },
-  { prop: "companyCode", label: "客服昵称", width: '200' },
-  { prop: "type1", label: "接待人员", width: '200' },
-  { prop: "type2", label: "员工账号", width: '200' },
-  { prop: "type3", label: "添加时间", width: '200' },
-  { prop: "type", label: "节假日排班", },
+  {
+    prop: "avatar", label: "客服信息", width: '300', component: 'html',
+    formatter: (row: any) => {
+      return {
+        html: ` <div style="display: flex;align-items: center;justify-content: center;">
+                  <img src="${row.avatar}" alt="" style="width: 24px;height: 24px;margin-right: 10px;
+                  box-shadow: inset 0px 4px 10px 0px rgba(123,148,206,0.1);border-radius: 50%;">
+                  <span>${row.nickname}</span>
+              </div>`,
+      };
+    }
+  },
+  { prop: "receptionist", label: "接待人员", width: '300' },
+  { prop: "userName", label: "员工账号", width: '200' },
+  { prop: "createTime", label: "添加时间", width: '200' },
+  { prop: "scheduling", label: "节假日排班", component: 'customSlot' },
 ];
 //分页事件
 function getList(item: PageInfo) {
-  pageInfo.value.pageNum = item.pageNum;
-  pageInfo.value.pageSize = item.pageSize;
+  pageInfo.pageNum = item.pageNum;
+  pageInfo.pageSize = item.pageSize;
   findpage();
 };
 
+// 定义 isHovered 的类型
+type HoverState = {
+  [key: string]: boolean; // 键为 string 类型,值为 boolean 类型
+};
+const isHovered = ref<HoverState>({}); //储存每一行的hover状态
+//单元格移入事件
+const handleMouseEnter = (row: any) => {
+  isHovered.value[row.userId] = true;
+}
+//单元格移出事件
+const handleMouseLeave = (row: any) => {
+  isHovered.value[row.userId] = false;
+
+}
 //编辑客服
 function ServiceUpdate(item: any) {
-
+  console.log(item)
+  dialogTitle.value = '编辑'
+  dialogType.value = 'edit'
+  imageUrl.value = item.avatar;
+  id.value=item.id;
+  api.wechatApi.wechatServiceDetail(item.id).then((res: any) => {
+    if (res.code == 200) {
+      ServiceForm.nickname = res.data.nickname;
+      ServiceForm.avatar = res.data.avatar;
+      ServiceForm.userId = res.data.userId;
+      ServiceForm.receptionistId = res.data.receptionistId;
+      ServiceForm.receptionist = res.data.receptionist;
+      ServiceAddShow.value = true
+    } else {
+      ElMessage({
+        type: 'success',
+        message: '删除成功',
+        plain: true,
+      })
+    }
+  })
 }
 //删除客服
 function ServiceDel(item: any) {
-
+  console.log(item)
+  ElMessageBox.confirm(
+    '确定要删除该客服账号吗?',
+    '删除客服',
+    {
+      confirmButtonText: '确定删除',
+      type: 'error',
+      center: true,
+      confirmButtonClass: 'el-button--danger',
+    }
+  )
+    .then(() => {
+      api.wechatApi.wechatServiceDel(item.id).then((res: any) => {
+        if (res.code == 200) {
+          ElMessage({
+            type: 'success',
+            message: '删除成功',
+            plain: true,
+          })
+          findpage();
+        }
+      })
+    })
+    .catch(() => {
+    })
+}
+//跳转节假日配置页
+const goSupportStaff = (row:any) => {
+  router.push({
+    name: 'personnelHolidaySchedule',
+    query: {
+      id: row.id,
+      name:row.nickname,
+    }
+  })
+}
+//获取后线人员
+const backendStaff = ref();
+const getbackendStaff = () => {
+  api.userApi.memberList({
+    pageNum: 1,
+    pageSize: 1000,
+    status: "6",
+    isMember: 1
+  }).then((res: any) => {
+    console.log(res)
+    if (res.code == 200) {
+      backendStaff.value = res.data.records;
+    }
+  })
 }
+//获取企微人员
+const WeChatUserList = ref();
+const getWeChatUser = () => {
+  api.wechatApi.getWechatDeptUser().then((res: any) => {
 
+    if (res.code == 200) {
+      WeChatUserList.value = res.data;
+    }
+  })
+}
+const WeChatUserListChange = (value: any) => {
+  let name = WeChatUserList.value.find((item: any) => item.userId == value).name;
+  ServiceForm.receptionist = name;
+}
+const ServiceAdd = () => {
+  dialogTitle.value = '添加客服账号'
+  dialogType.value = 'add'
+  ServiceForm.nickname = '';
+  ServiceForm.avatar = ''
+  ServiceForm.userId = ''
+  ServiceForm.receptionist = ''
+  ServiceForm.receptionistId = ''
+  imageUrl.value = "";
+  ServiceAddShow.value = true;
 
+}
+//添加客服
+const ServiceSave = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return
+  await formEl.validate((valid) => {
+    if (valid) {
+      if (dialogType.value == 'add') {
+        api.wechatApi.wechatServiceAdd(ServiceForm).then((res: any) => {
+          if (res.code == 200) {
+            ElMessage({
+              type: 'success',
+              message: '添加成功',
+              plain: true,
+            })
+            ServiceAddShow.value = false;
+            findpage();
+          }
+        })
+      } else {
+        api.wechatApi.wechatServicePut(id.value,ServiceForm).then((res: any) => {
+          if (res.code == 200) {
+            ElMessage({
+              type: 'success',
+              message: '编辑成功',
+              plain: true,
+            })
+            ServiceAddShow.value = false;
+            findpage();
+          }
+        })
+      }
+    } else {
+      ElMessage({
+        type: 'error',
+        message: '信息不完整',
+        plain: true,
+      })
+    }
+  })
+}
 </script>
 <style lang="scss" scoped>
 .avatar-uploader :deep(.el-upload) {
@@ -185,17 +403,18 @@ function ServiceDel(item: any) {
   cursor: pointer;
   position: relative;
   overflow: hidden;
-  transition: var(--el-transition-duration-fast);
 }
 
 .avatar span {
   color: #999;
   font-size: 12px;
+
 }
 
 .avatar-uploader img {
   width: 100%;
   height: 100%;
+
 }
 
 .el-icon.avatar-uploader-icon {