Kaynağa Gözat

Merge branch 'dev' of http://39.101.143.165:8090/dong_k/tenant-Platform into dev

郭鹏飞 11 ay önce
ebeveyn
işleme
0987206b35
36 değiştirilmiş dosya ile 5162 ekleme ve 312 silme
  1. 6 2
      .env.development
  2. 325 12
      package-lock.json
  3. 1 0
      package.json
  4. 8 0
      src/api/index.ts
  5. 14 0
      src/api/modules/consoleManage.ts
  6. 28 0
      src/api/modules/sale.ts
  7. 22 0
      src/api/modules/wechatService.ts
  8. 9 0
      src/api/types/agreementTypes.ts
  9. 6 0
      src/api/types/page.ts
  10. 45 0
      src/api/types/wechatServiceTypes.ts
  11. BIN
      src/assets/images/check.png
  12. BIN
      src/assets/images/download.png
  13. BIN
      src/assets/images/message.png
  14. 2 1
      src/assets/styles/globals.css
  15. 29 5
      src/components/complexTable/index.vue
  16. 162 0
      src/layouts/components/Topbar/Toolbar/downLoad/downLoadList.vue
  17. 87 0
      src/layouts/components/Topbar/Toolbar/downLoad/index.vue
  18. 2 0
      src/layouts/components/Topbar/Toolbar/rightSide.vue
  19. 10 2
      src/router/modules/quoteConfig.ts
  20. 53 0
      src/router/modules/sale.ts
  21. 3 2
      src/router/routes.ts
  22. 9 0
      src/store/modules/message.ts
  23. 6 0
      src/utils/dayjs.ts
  24. 198 98
      src/views/consoleManage/businessDataManage/companyDataManage/companyDataManageDetail.vue
  25. 210 140
      src/views/consoleManage/businessDataManage/companyDataManage/index.vue
  26. 86 0
      src/views/consoleManage/components/button.vue
  27. 376 0
      src/views/consoleManage/components/filterDialog.vue
  28. 397 0
      src/views/consoleManage/components/queryForm.vue
  29. 451 5
      src/views/personnel/holidaySchedule.vue
  30. 264 45
      src/views/personnel/wechatService.vue
  31. 10 0
      src/views/quoteConfig/dispatch/dispatch.vue
  32. 296 0
      src/views/sale/commission.vue
  33. 173 0
      src/views/sale/components/drawer.vue
  34. 535 0
      src/views/sale/list.vue
  35. 724 0
      src/views/sale/review.vue
  36. 615 0
      src/views/sale/settings.vue

+ 6 - 2
.env.development

@@ -3,11 +3,15 @@ VITE_APP_SETTING = true
 # 页面标题
 VITE_APP_TITLE = 租户平台
 # 接口请求地址,会设置到 axios 的 baseURL 参数上
-# VITE_APP_API_BASEURL = https://test.baoxianzhanggui.com/web-api #默认地址
+ VITE_APP_API_BASEURL = https://test.baoxianzhanggui.com/web-api #默认地址
 # VITE_APP_API_BASEURL = http://192.168.0.253:8001  #田智
 # VITE_APP_API_BASEURL = http://192.168.0.55:8001  #董鑫
-VITE_APP_API_BASEURL = http://192.168.0.106:8001  #屈晨
+# VITE_APP_API_BASEURL = http://192.168.0.106:8001  #屈晨
+VITE_APP_API_BASEURL = http://192.168.0.115:8001  #屈晨
 # VITE_APP_API_BASEURL = http://192.168.0.59:8001  #徐强
+# VITE_APP_API_BASEURL = http://192.168.0.59:8001  #徐强
+# VITE_APP_API_BASEURL = http://192.168.0.103:8001 #霍
+
 
 # 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
 VITE_APP_DEBUG_TOOL =

+ 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",

+ 8 - 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()
@@ -34,6 +35,9 @@ import additionalApi from '@/api/modules/additional.ts'
 import logApi from '@/api/modules/log.ts'
 
 import insurancePolicyApi from '@/api/modules/insurancePolicy.ts'
+import manageApi from '@/api/modules/consoleManage.ts'
+
+import saleApi from '@/api/modules/sale.ts'
 
 const axiosInstance:ApiInstance = axios.create({
   baseURL: (import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true') ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
@@ -133,8 +137,12 @@ const api = {
   additionalApi: additionalApi(axiosInstance),
   logApi: logApi(axiosInstance),
   insurancePolicyApi: insurancePolicyApi(axiosInstance),
+  manageApi: manageApi(axiosInstance),
   financeApi: financeApi(axiosInstance),
   invoiceRecordApi: invoiceRecordApi(axiosInstance),
   settlementApi: settlementApi(axiosInstance),
+  wechatApi:wechatApi(axiosInstance),
+  saleApi: saleApi(axiosInstance),
+
 }
 export default api

+ 14 - 0
src/api/modules/consoleManage.ts

@@ -0,0 +1,14 @@
+import { ApiInstance } from '../type'
+
+
+const manageApi = (axiosInstance: ApiInstance) => ({
+    getSearchConfigurationList: (params: any) => axiosInstance.get('/console/searchConfiguration/getSearchConfigurationList', {params}),//查询统一筛选条件列表
+    getSearchDefaultList: (params: any) => axiosInstance.get('/console/searchConfiguration/getSearchDefaultList', {params}),//查询页面默认筛选条件列表
+    getCompanyListByCompanyId: (params: any) => axiosInstance.get('/console/searchConfiguration/getPartnerCompanyListByCompanyId',{params}),//通过保险公司id查询合作公司的三级列表
+    getTaskMessageNoReadCount: (params: any) => axiosInstance.get('/console/task/getTaskMessageNoReadCount',{params}),//1.查看用户的未读导出任务消息
+    getPageTaskList: (params: any) => axiosInstance.post('/console/task/getPageTaskList',params),//用户的导出任务分页列表
+    updateReadStatus: (params: any) => axiosInstance.get('/console/task/updateReadStatus',{params}),//更新导出未读任务为已读
+
+  })
+
+export default manageApi

+ 28 - 0
src/api/modules/sale.ts

@@ -0,0 +1,28 @@
+import { ApiInstance } from '../type'
+const saleApi = (axiosInstance: ApiInstance) => ({
+  //结构树
+  agreementdeptTree: (data: any) => axiosInstance.post('dept/getDeptTree',data),
+  //保存分销设置
+  saveSetting:(data:any)=>axiosInstance.post('sysDistributionSetting/saveSetting',data),
+  //查询分销设置
+  details:()=>axiosInstance.get(`sysDistributionSetting/getSettingBySystemCode`),
+  //修改分销设置
+  update:(data:any)=>axiosInstance.post('sysDistributionSetting/updateSetting',data),
+  deleteUser:(data:any)=>axiosInstance.post('userInfo/deleteDistribution',data),
+  getDistributionRelation:(params: any)=>axiosInstance.get('userInfo/getDistributionRelation',{params}),
+
+  /**
+   * @param 分销管理
+   */
+  list: (data: any) => axiosInstance.post('sysDistributionSetting/distributorList', data),
+
+  //佣金明细
+  commissionDetails:(data: any) => axiosInstance.post('sysDistributionSetting/commissionDetails', data),
+  export:(data: any) => axiosInstance.post('sysDistributionSetting/exportCommissionDetailsExcel', data,{responseType: 'blob'}),
+
+  //分销员审核列表
+    auditList:(data:any)=>axiosInstance.post('sysDistributionSetting/distributorAuditList',data),
+    distributorAudit:(data:any)=>axiosInstance.post('sysDistributionSetting/distributorAudit',data),
+})
+
+export default saleApi

+ 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

+ 9 - 0
src/api/types/agreementTypes.ts

@@ -212,3 +212,12 @@ export type agreementDeleteRequest = {
   [property: string]: any;
 }
 
+/**
+ * 分页
+ */
+export type PageInfo= {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}

+ 6 - 0
src/api/types/page.ts

@@ -0,0 +1,6 @@
+export interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}

+ 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;
+}

BIN
src/assets/images/check.png


BIN
src/assets/images/download.png


BIN
src/assets/images/message.png


+ 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 {

+ 29 - 5
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>
@@ -22,7 +23,7 @@
             <div>
               <el-table :data="props.row.children">
                 <el-table-column v-for="column in secondaryColumns" :key="column.prop" :prop="column.prop"
-                  :label="column.label" :sortable="column.sortable ? 'custom' : false" :width="column.width">
+                  :label="column.label" :sortable="column.sortable ?'custom' : false" :width="column.width">
                   <template #header>
                     <slot name="search" :slotData="column"></slot>
                   </template>
@@ -53,16 +54,22 @@
         </el-table-column>
         <el-table-column v-if="showIndex" type="index" label="序号" width="55" />
         <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"
-          :sortable="column.sortable ? 'custom' : false" :width="column.width"
+          :sortable="column.sortable ?'custom' : false" :width="column.width"
           :align="column.align ? column.align : 'center'">
           <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 {

+ 162 - 0
src/layouts/components/Topbar/Toolbar/downLoad/downLoadList.vue

@@ -0,0 +1,162 @@
+<script setup lang="ts">
+import useSettingsStore from "@/store/modules/settings";
+import doIcon from "@/assets/images/download.png";
+import api from "@/api";
+defineOptions({
+  name: "message",
+});
+const emits = defineEmits(["close"]);
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+onMounted(() => {
+  initDefaultList();
+});
+
+const arrColor = ["#FF4545", "#666666", "#0083FF"]; //任务状态 0.失败 1 进行中  2. 完成
+const startsList = [
+  {
+    color: "#FF4545",
+    text: "下载失败",
+  },
+  {
+    color: "#666666",
+    text: "下载中...",
+  },
+  {
+    color: "#0083FF",
+    text: "下载资料",
+  },
+];
+
+const finished = ref(false);
+const params = ref({
+  pages: 1,
+  size: 10,
+  uploadUserId: JSON.parse(localStorage.getItem("userInfo")).username,
+});
+const taskList = ref([]);
+const initDefaultList = async () => {
+  const { data } = await api.manageApi.getPageTaskList(params.value);
+  const dataList = data.records;
+  dataList.forEach((item) => {
+    taskList.value.push(item);
+  });
+  params.value.pages++;
+  if (dataList.length < params.value.size) {
+    // 数据全部加载完成
+    finished.value = true;
+  }
+};
+
+const load = () => {
+  if (!finished.value) {
+    params.value.pages++;
+    initDefaultList();
+  }
+};
+
+const handleClose = () => {
+  emits("close");
+};
+// 暴露给父组件的方法
+defineExpose({
+  initDefaultList,
+});
+</script>
+
+<template>
+  <div class='drawer-box'>
+    <el-drawer v-model="props.show" direction="rtl" :before-close="handleClose">
+      <template #header>
+        <div class='header-title'>任务中心</div>
+      </template>
+      <div class='content'>
+        <div class='content-tip'>
+          <el-icon>
+            <warning color="#FAA21E" />
+          </el-icon>
+          <div class='tit'>系统仅保留7天内的记录</div>
+        </div>
+
+        <div v-infinite-scroll="load" :infinite-scroll-immediate='false'>
+          <div class='list' v-for="(item,index) in taskList" :key="index">
+            <div>
+              <div>{{item.taskName}}</div>
+              <div class='time'>导出时间:{{item.uploadDate}}</div>
+            </div>
+            <div class='flex items'>
+              <el-icon v-if='item.taskStatus==2'>
+                <download color='#0083FF' />
+              </el-icon>
+              <el-icon v-if='item.taskStatus==0'>
+                <download color='#FF4545' />
+              </el-icon>
+              <ElImage v-if='item.taskStatus==1' :src="doIcon" :style="`width:16px;height:16px;`" fit="fill" />
+              <div class='text' :style='{color:startsList[item.taskStatus].color}'> {{startsList[item.taskStatus].text}}
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.drawer-box {
+  .header-title {
+    color: #333;
+    font-weight: 500;
+    font-size: 20px;
+    padding-bottom: 18px;
+  }
+  :deep(.el-drawer__header) {
+    border-bottom: 1px solid #e5e5e5;
+    margin-bottom: 0;
+  }
+
+  .content {
+    color: #333333;
+    font-size: 14px;
+    .content-tip {
+      background: #fffaef;
+      border-radius: 6px 6px 6px 6px;
+      border: 1px solid #faa21e;
+      padding: 16px 24px;
+      display: flex;
+      align-items: center;
+      margin-bottom: 24px;
+    }
+    .tit {
+      color: #333333;
+      font-size: 16px;
+      margin-left: 16px;
+    }
+  }
+  .list {
+    padding: 16px 24px;
+    box-sizing: border-box;
+    border-radius: 6px 6px 6px 6px;
+    border: 1px solid #eeeeee;
+    margin-bottom: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .items {
+      align-items: center;
+    }
+    .text {
+      margin-left: 5px;
+    }
+    .time {
+      color: #666666;
+      margin-top: 8px;
+    }
+  }
+}
+</style>

+ 87 - 0
src/layouts/components/Topbar/Toolbar/downLoad/index.vue

@@ -0,0 +1,87 @@
+<script setup lang="ts">
+import useSettingsStore from "@/store/modules/settings";
+import message from "@/assets/images/message.png";
+import DownLoadList from "./downLoadList.vue";
+import api from "@/api";
+import useUserStore from "@/store/modules/message";
+
+const userStore = useUserStore();
+defineOptions({
+  name: "message",
+});
+
+onMounted(() => {
+  initDefaultList();
+});
+
+//查询数据
+const number = ref();
+const phone = JSON.parse(localStorage.getItem("userInfo")).username;
+const initDefaultList = async () => {
+  const { data } = await api.manageApi.getTaskMessageNoReadCount({
+    userId: phone,
+  });
+  number.value = data;
+};
+
+const settingsStore = useSettingsStore();
+const show = ref(false);
+const download = async () => {
+  const { data } = await api.manageApi.updateReadStatus({
+    userId: phone,
+  });
+  show.value = true;
+};
+
+const downLoadRef = ref();
+const isVisible=ref(false)
+watch(
+  () => userStore.count,
+  () => {
+    isVisible.value=true
+    show.value = true;
+    downLoadRef.value?.initDefaultList();
+  }
+);
+</script>
+
+<template>
+  <span v-if="settingsStore.mode === 'pc'" id="navbarTarget" class="flex-center cursor-pointer px-2 py-1 download-box" @click="download">
+    <ElImage :src="message" :style="`width:15px;height:15px;`" fit="fill" />
+    <div v-if='number' class='message-icon'></div>
+  </span>
+  <DownLoadList :show='show' @close='show=false' ref='downLoadRef'></DownLoadList>
+</template>
+
+<style scoped lang="scss">
+.download-box {
+  position: relative;
+  .message-icon {
+    position: absolute;
+    right: 5px;
+    top: 2px;
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background: #f5222d;
+  }
+
+  /* 过渡动画样式 */
+  .slide-up-enter-from,
+  .slide-up-leave-to {
+    opacity: 0;
+    transform: translateY(100vh); /* 从视口底部开始 */
+  }
+
+  .slide-up-enter-active,
+  .slide-up-leave-active {
+    transition: all 0.8s cubic-bezier(0.25, 0.8, 0.25, 1); /* 平滑的动画曲线 */
+  }
+
+  .slide-up-enter-to,
+  .slide-up-leave-from {
+    opacity: 1;
+    transform: translateY(0); /* 移动到最终位置 */
+  }
+}
+</style>

+ 2 - 0
src/layouts/components/Topbar/Toolbar/rightSide.vue

@@ -4,6 +4,7 @@ import useUserStore from '@/store/modules/user'
 import eventBus from '@/utils/eventBus'
 import ColorScheme from './ColorScheme/index.vue'
 import Fullscreen from './Fullscreen/index.vue'
+import DownLoad from './downLoad/index.vue'
 import NavSearch from './NavSearch/index.vue'
 import PageReload from './PageReload/index.vue'
 import {useRouter} from "vue-router"
@@ -27,6 +28,7 @@ watch(() => userStore.avatar, () => {
 <template>
   <div class="flex items-center">
     <!-- <NavSearch v-if="settingsStore.settings.toolbar.navSearch" /> -->
+    <DownLoad v-if="settingsStore.settings.toolbar.fullscreen" />
     <Fullscreen v-if="settingsStore.settings.toolbar.fullscreen" />
     <PageReload v-if="settingsStore.settings.toolbar.pageReload" />
     <ColorScheme v-if="settingsStore.settings.toolbar.colorScheme" />

+ 10 - 2
src/router/modules/quoteConfig.ts

@@ -18,7 +18,7 @@ const routes: RouteRecordRaw = {
       meta: {
         title: '协议管理',
       },
-      children:[
+      children: [
         {
           path: 'agreementAdd',
           name: 'AgreementAdd',
@@ -45,7 +45,7 @@ const routes: RouteRecordRaw = {
           component: () => import('@/views/quoteConfig/agreement/agreementDetails.vue'),
           meta: {
             title: '协议详情',
-            menu:false,
+            menu: false,
             activeMenu: '/quoteConfig/agreement/agreementIndex',
           },
         },
@@ -105,6 +105,14 @@ const routes: RouteRecordRaw = {
         }
       ]
     },
+    {
+      path: 'dispatchIndex',
+      name: 'dispatchIndex',
+      component: () => import('@/views/quoteConfig/dispatch/dispatch.vue'),
+      meta: {
+        title: '协议调度',
+      },
+    },
   ],
 }
 

+ 53 - 0
src/router/modules/sale.ts

@@ -0,0 +1,53 @@
+import type { RouteRecordRaw } from 'vue-router'
+
+const Layout = () => import('@/layouts/index.vue')
+
+const routes: RouteRecordRaw = {
+  path: '/sale',  
+  component: Layout,
+  redirect: '/sale/list',  
+  name: 'sale',
+  meta: {
+    title: '分销管理',
+  },
+  children: [
+    {
+      path: 'list',  
+      name: 'saleList',
+      component: () => import('@/views/sale/list.vue'),  
+      meta: {
+        title: '分销员管理',
+      },
+      children:[]
+    },
+     {
+      path: 'commission',  
+      name: 'saleCommission',
+      component: () => import('@/views/sale/commission.vue'),  
+      meta: {
+        title: '佣金明细',
+      },
+      children:[]
+    },
+     {
+      path: 'review',  
+      name: 'saleReview',
+      component: () => import('@/views/sale/review.vue'),  
+      meta: {
+        title: '分销员审核',
+      },
+      children:[]
+    },
+    {
+      path: 'settings',
+      name: 'saleSettings',  
+      component: () => import('@/views/sale/settings.vue'),
+      meta: {
+        title: '分销设置',
+      },
+      children:[]
+    },
+  ],
+}
+
+export default routes

+ 3 - 2
src/router/routes.ts

@@ -21,6 +21,7 @@ import capital from './modules/capital.ts'
 import basics from './modules/basics.ts'
 import systemManage from './modules/system.ts'
 import consoleManage from './modules/console.ts' //数据报表
+import sale from './modules/sale.ts'//分销管理
 // import {  } from "@/store/modules/user";
 // 固定路由(默认路由
 // console.log(useUserStore)
@@ -99,7 +100,7 @@ const asyncRoutes: Route.recordMainRaw[] = [
       title: "业务管理",
       icon: "i-uim:box",
     },
-    children: [operationManagement],
+    children: [operationManagement,sale],
   },
   {
     meta: {
@@ -164,7 +165,7 @@ const asyncRoutes: Route.recordMainRaw[] = [
        icon: "i-uim:box",
      },
      children: [consoleManage],
-   }
+   },
   // {
   //   meta: {
   //     title: "租户运营",

+ 9 - 0
src/store/modules/message.ts

@@ -0,0 +1,9 @@
+
+
+const useTabbarStore = defineStore('message',()=>{
+    return{
+      count:0
+    }
+})
+
+export default useTabbarStore

+ 6 - 0
src/utils/dayjs.ts

@@ -1,6 +1,12 @@
 import dayjs from 'dayjs'
 import 'dayjs/locale/zh-cn'
+import relativeTime from 'dayjs/plugin/relativeTime' // 使用dayjs自带插件
 
 dayjs.locale('zh-cn')
+dayjs.extend(relativeTime) // 导入相对时间插件
+
+export const timestampFormat = (time: number, type?: string) => dayjs(time).format(type || 'YYYY-MM-DD') // YYYY-MM-DD HH:mm:ss
+export const formatTime = (time: any) => dayjs(time).fromNow()
+
 
 export default dayjs

+ 198 - 98
src/views/consoleManage/businessDataManage/companyDataManage/companyDataManageDetail.vue

@@ -1,117 +1,217 @@
 <template>
-  <div class="logDetail">
-    <el-descriptions :column="2">
-      <template #title>
-        <div class="title">
-          <div class="icon">
-            <el-icon><List /></el-icon>
-          </div>
-          <p>{{ detailData?.operationContent }} 保险公司</p>
-        </div>
-      </template>
-      <el-descriptions-item label="操作功能:" label-align="right" align="left" label-width="100">{{ detailData?.moduleName }}</el-descriptions-item>
-      <el-descriptions-item label="操作类型:" label-align="right" align="left" label-width="100">{{ detailData?.operatorName }}</el-descriptions-item>
-      <el-descriptions-item label="操作人:" label-align="right" align="left" label-width="100">{{ detailData?.createBy }}</el-descriptions-item>
-      <el-descriptions-item label="操作时间:" label-align="right" align="left" label-width="100">{{ detailData?.createTime }}</el-descriptions-item>
-    </el-descriptions>
-    <el-row :gutter="20" class="content">
-      <el-col :span="11">
-        <p>变更前</p>
-        <div class="list">
-          <el-row class="list-item" :gutter="20" v-for="item in detailData?.compareResultList" :key="item">
-            <el-col :span="6">{{ item.name }}</el-col>
-            <el-col :span="18" style="color: #606266">{{ item.fieldContent }}</el-col>
-          </el-row>
-        </div>
-      </el-col>
-      <el-divider direction="vertical" style="height: 100%"></el-divider>
-      <el-col :span="11">
-        <p>变更后</p>
-        <div class="list">
-          <el-row class="list-item" :gutter="20" v-for="item in detailData?.compareResultList" :key="item">
-            <el-col :span="6">{{ item.name }}</el-col>
-            <el-col :span="18" style="color: #606266">{{ item.newFieldContent }}</el-col>
-          </el-row>
-        </div>
-      </el-col>
-    </el-row>
+  <div class="log">
+    <div class='btn-box'>
+      <el-button type="primary" plain class='query-btn'
+        @click='dialogShow'>{{selectedKeys.length?`已选${selectedKeys.length}项`:'筛选条件'}}</el-button>
+      <div class='query-btn'>
+        <DowButton @endAnim='downloadFile'></DowButton>
+      </div>
+    </div>
+    <QueryForm :option="listOption" @query='query' @delSelect='delSelect' ref='queryFormRef'></QueryForm>
+
+    <!-- 表格 -->
+    <ComplexTable :tableData="tableData" :showSelect='false' :columns="listOption.columns" :showIndex="false"
+      :columnwidth="120" :pageInfo="pageInfo" @getList="getList" :showOperation="false">
+    </ComplexTable>
+
+    <FilterDialog :show='isShow' @close='isShow=false' @sure='filterSure' ref='filterDialogRef' @selectedKeys='selected'
+      routerKey='companyDataManageDetail'>
+    </FilterDialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import api from '@/api'
-import { useRoute, useRouter } from 'vue-router'
+import { useRouter } from "vue-router";
+import api from "@/api";
 import { ElMessage } from "element-plus";
+import QueryForm from "../../components/queryForm.vue";
+import FilterDialog from "../../components/filterDialog.vue";
+import DowButton from "../../components/button.vue";
+import useUserStore from "@/store/modules/message";
+import PageInfo from "@/api/types/agreementTypes";
+
+const router = useRouter();
+const userStore = useUserStore();
+const isShow = ref(false);
+
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 0,
+});
+const listOption = reactive({
+  rowKey: "companyId",
+  //查询
+  queryFormFields: [],
+  // 表格列
+  columns: [
+    {
+      prop: "date",
+      label: "保险公司",
+      width: "200",
+    },
+    {
+      prop: "name",
+      label: "合作保险公司",
+      width: "300",
+    },
+    {
+      prop: "address",
+      label: "订单",
+      width: "120",
+    },
+    {
+      prop: "tag",
+      label: "车牌号",
+      width: "180",
+    },
+    {
+      prop: "tag",
+      label: "订单类型",
+      width: "180",
+    },
+    {
+      prop: "tag",
+      label: "险种",
+      width: "180",
+    },
+    {
+      prop: "tag",
+      label: "保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "录单时间",
+      width: "180",
+    },
+    {
+      prop: "tag",
+      label: "签单时间",
+      width: "180",
+    },
+  ],
+});
+const tableData = ref([
+  {
+    date: "2016-05-02",
+    name: "1222",
+    address: "123.25",
+    tag: "3222",
+  },
+  {
+    date: "2016-05-02",
+    name: "3222",
+    address: "422.352",
+    tag: "2225",
+  },
+]);
+const selectedKeys = ref([]);
+const selected = function (val) {
+  selectedKeys.value = val;
+};
+
+//查询
+const query = (item) => {
+  console.log(item);
+};
+
+//查询数据
+const phone = JSON.parse(localStorage.getItem("userInfo")).username;
+
+//获取默认选择数据
+const defaultList = ref();
+const pageHiddenList = ref([]); //页面不显示的数据
+const initDefaultList = async () => {
+  const { data } = await api.manageApi.getSearchDefaultList({
+    routerKey: "companyDataManageDetail",
+    userId: phone,
+  });
+  listOption.queryFormFields = data.filter((e) => e.showFlag);
+
+  pageHiddenList.value = data.filter((e) => !e.showFlag);
+  defaultList.value = data.map((e) => e.id);
+  selectedKeys.value = defaultList.value;
+  queryFormRef.value?.initDefaultSaveForm();
+};
+
+//删除
+const filterDialogRef = ref();
+const delSelect = (id) => {
+  listOption.queryFormFields = listOption.queryFormFields.filter(
+    (item) => item.id != id
+  );
+  filterDialogRef.value.initList(
+    [...listOption.queryFormFields, ...pageHiddenList.value],
+    defaultList.value
+  );
+};
+
+//打开dialogShow
+const dialogShow = function () {
+  isShow.value = true;
+  filterDialogRef.value.initList(
+    [...listOption.queryFormFields, ...pageHiddenList.value],
+    defaultList.value
+  );
+};
 
-const myRoute = useRoute()
-const myRouter = useRouter()
-
-let detailData = ref(undefined)
-
-const initDetail = () => {
-  api.logApi.selectById(myRoute.query.id).then((res: any) => {
-    if(res.code == 200) {
-      detailData.value = res.data
-    } else {
-      ElMessage({
-        message: res.msg,
-        type: 'warning'
-      })
-    }
-  })
+//确定
+const queryFormRef = ref();
+const filterSure = (item, index) => {
+  isShow.value = false;
+  const secondGroupIds = item.list.map((item) => item.id);
+  const oldList = listOption.queryFormFields.filter((item) =>
+    secondGroupIds.includes(item.id)
+  );
+  const firstGroupIds = listOption.queryFormFields.map((item) => item.id);
+  let newList = item.list.filter((item) => !firstGroupIds.includes(item.id));
+
+  //过滤掉默认不显示的数据
+  const hiddenList = new Set(pageHiddenList.value.map((item) => item.id));
+  const filterList = newList.filter((item) => !hiddenList.has(item.id));
+
+  listOption.queryFormFields = [...oldList, ...filterList];
+  queryFormRef.value?.initDefaultSaveForm();
+  selectedKeys.value = item.list.filter((item) => item.id);
+};
+
+const isVisible = ref(false);
+const downloadFile = () => {
+  isVisible.value = true;
+  userStore.count++;
+};
+
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
 }
 
 onMounted(() => {
-  initDetail()
-})
-
+  initDefaultList();
+});
 </script>
 
 <style scoped lang="scss">
-.logDetail{
-  padding: 24px;
-  background: #FFFFFF;
+.log {
   width: 100%;
   height: calc(100vh - 120px);
-  .title{
-    display: flex;
-    align-items: center;
-    justify-content: flex-start;
-    .icon{
-      width: 30px;
-      height: 30px;
-      border-radius: 50%;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      background: #1A85FA;
-      color: #FFFFFF;
-      font-size: 20px;
-      margin-right: 10px;
-    }
-  }
-  .content{
-    height: calc(100% - 120px);
-    overflow: hidden;
-    p{
-      text-align: center;
-      background: #F0F0F0;
-      margin: 0;
-      padding: 16px 0;
-    }
-    .list{
-      height: calc(100% - 80px);
-      overflow-y: auto;
-      overflow-x: hidden;
-      .list-item{
-        margin: 14px 0;
-        padding: 0 20px;
-        display: flex;
-      }
-    }
+  background: #ffffff;
+  padding: 24px;
+  overflow-y: auto;
+  .query-btn {
+    margin-left: 20px;
   }
-  :deep(.el-descriptions) {
-    width: 50%;
+  .btn-box {
+    padding-bottom: 15px;
+    margin-bottom: 24px;
+    border-bottom: 1px solid #eeeeee;
+    display: flex;
+    justify-content: flex-end;
   }
 }
 </style>

+ 210 - 140
src/views/consoleManage/businessDataManage/companyDataManage/index.vue

@@ -1,169 +1,239 @@
 <template>
   <div class="log">
-    <el-form label-width="100" label-position="left" :model="form">
-      <el-row :gutter="20">
-        <el-col :span="8">
-          <el-form-item label="操作人">
-            <el-input v-model="form.operator" placeholder="请输入操作人姓名查找"></el-input>
-          </el-form-item>
-        </el-col>
-        <el-col :span="8">
-          <el-form-item label="操作时间">
-            <el-date-picker
-              v-model="form.operatingTime"
-              type="daterange"
-              range-separator="至"
-              start-placeholder="开始时间"
-              end-placeholder="结束时间"
-              format="YYYY-MM-DD"
-              value-format="YYYY-MM-DD"
-              @change="timeChange"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="8">
-          <el-form-item label="操作功能">
-            <el-select v-model="form.function" placeholder="请选择">
-              <el-option label="工能1" value="1" key="1"></el-option>
-            </el-select>
-          </el-form-item>
-        </el-col>
-        <el-col :span="8">
-          <el-form-item label="操作内容">
-            <el-input v-model="form.content" placeholder="请输入"></el-input>
-          </el-form-item>
-        </el-col>
-        <el-col :span="16" style="display: flex; align-items: center; justify-content: flex-end">
-          <el-button type="primary" @click="search">查询</el-button>
-          <el-button plain type="primary" @click="reset">重置</el-button>
-        </el-col>
-      </el-row>
-    </el-form>
-    <el-table
-      :data="tableData"
-      height="600"
-      style="margin-top: 24px"
-    >
-      <el-table-column label="操作人" prop="createBy" width="200"></el-table-column  >
-      <el-table-column label="操作时间" prop="createTime" width="240"></el-table-column>
-      <el-table-column label="操作功能" prop="moduleName"></el-table-column>
-      <el-table-column label="操作内容" prop="operationContent"></el-table-column>
-      <el-table-column label="类型" prop="operatorName" width="150"></el-table-column>
-      <el-table-column label="操作" width="150">
-        <template #default="scope">
-          <el-button link type="primary" @click="detail(scope.row)">查看详情</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <div class="pagination">
-      <el-pagination
-        v-model:current-page="pages"
-        v-model:page-size="size"
-        :page-sizes="[10, 20, 30, 40]"
-        layout="total, sizes, prev, pager, next, jumper"
-        :total="total"
-        @size-change="handleSizeChange"
-        @current-change="handleCurrentChange"
-      ></el-pagination>
+    <div class='btn-box'>
+      <el-button type="primary" plain class='query-btn'
+        @click='dialogShow'>{{selectedKeys.length?`已选${selectedKeys.length}项`:'筛选条件'}}</el-button>
+      <div class='query-btn'>
+        <DowButton @endAnim='downloadFile'></DowButton>
+      </div>
     </div>
+    <QueryForm :option="listOption" @query='query' @delSelect='delSelect' ref='queryFormRef'></QueryForm>
+
+    <!-- 表格 -->
+    <ComplexTable :tableData="tableData" :showSelect='false' :columns="listOption.columns" :showIndex="false"
+      :columnwidth="120" :pageInfo="pageInfo" @getList="getList">
+      <template v-slot:operation="scope">
+        <el-button type="primary" link @click='agreementEdit(scope.operationData.id)'>明细</el-button>
+      </template>
+    </ComplexTable>
+
+    <FilterDialog :show='isShow' @close='isShow=false' @sure='filterSure' ref='filterDialogRef' @selectedKeys='selected'
+      routerKey='companyDataManage'>
+    </FilterDialog>
   </div>
 </template>
 
 <script setup lang="ts">
-import { useRouter } from 'vue-router'
-import api from '@/api'
-import {ElMessage} from "element-plus";
+import { useRouter } from "vue-router";
+import api from "@/api";
+import { ElMessage } from "element-plus";
+import QueryForm from "../../components/queryForm.vue";
+import FilterDialog from "../../components/filterDialog.vue";
+import DowButton from "../../components/button.vue";
+import useUserStore from "@/store/modules/message";
+import PageInfo from "@/api/types/agreementTypes";
 
-const myRouter = useRouter()
+const router = useRouter();
+const userStore = useUserStore();
+const isShow = ref(false);
 
-let activeName = ref('first')
-const handleClick = () => {
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 20,
+  sizes: [10, 20, 30, 40, 50],
+  total: 0,
+});
+const listOption = reactive({
+  rowKey: "companyId",
+  //查询
+  queryFormFields: [],
+  // 表格列
+  columns: [
+    {
+      prop: "date",
+      label: "保险公司",
+      width: "200",
+    },
+    {
+      prop: "name",
+      label: "合作保险公司",
+      width: "300",
+    },
+    {
+      prop: "address",
+      label: "订单",
+      width: "120",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "单交保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "单商保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "单三保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "交三保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+    {
+      prop: "tag",
+      label: "交商保费",
+      width: "180",
+      align: "right",
+      sortable: "default",
+    },
+  ],
+});
+const tableData = ref([
+  {
+    date: "2016-05-02",
+    name: "1222",
+    address: "123.25",
+    tag: "3222",
+  },
+  {
+    date: "2016-05-02",
+    name: "3222",
+    address: "422.352",
+    tag: "2225",
+  },
+]);
+const selectedKeys = ref([]);
+const selected = function (val) {
+  selectedKeys.value = val;
+};
 
-}
+//查询
+const query = (item) => {
+  console.log(item);
+};
 
-let form = ref({})
-const tableProps = ref({
-  value: "id",
-  label: "name",
-  children: "children",
-  checkStrictly: true,
-});
-let option = ref([])
-const timeChange = (val) => {
+//查询数据
+const phone = JSON.parse(localStorage.getItem("userInfo")).username;
 
-}
-// 搜索
-const search = () => {
-  pages.value = 1
-  size.value = 10
-  initData()
-}
-const reset = () => {
-  form.value = {}
-  pages.value = 1
-  size.value = 10
-  initData()
-}
+//获取默认选择数据
+const defaultList = ref();
+const pageHiddenList = ref([]); //页面不显示的数据
+const initDefaultList = async () => {
+  const { data } = await api.manageApi.getSearchDefaultList({
+    routerKey: "companyDataManage",
+    userId: phone,
+  });
+  listOption.queryFormFields = data.filter((e) => e.showFlag);
 
-// 列表
-let tableData = ref([])
-const detail = (row) => {
-  myRouter.push({
-    path: '/consoleManage/businessDataManage/companyDataManage/companyDataManageDetail',
-    query: {
-      id: row.id
-    }
-  })
-}
+  pageHiddenList.value = data.filter((e) => !e.showFlag);
+  defaultList.value = data.map((e) => e.id);
+  selectedKeys.value = defaultList.value;
+  queryFormRef.value?.initDefaultSaveForm();
+};
 
-// 分页
-let pages = ref(1)
-let size = ref(10)
-let total = ref(0)
-const handleSizeChange = (size) => {
-  size.value = size
-  initData()
-}
-const handleCurrentChange = (page) => {
-  pages.value = page
-  initData()
-}
+//删除
+const filterDialogRef = ref();
+const delSelect = (id) => {
+  listOption.queryFormFields = listOption.queryFormFields.filter(
+    (item) => item.id != id
+  );
+  filterDialogRef.value.initList(
+    [...listOption.queryFormFields, ...pageHiddenList.value],
+    defaultList.value
+  );
+};
+
+//打开dialogShow
+const dialogShow = function () {
+  isShow.value = true;
+  filterDialogRef.value.initList(
+    [...listOption.queryFormFields, ...pageHiddenList.value],
+    defaultList.value
+  );
+};
+
+//确定
+const queryFormRef = ref();
+const filterSure = (item, index) => {
+  isShow.value = false;
+  const secondGroupIds = item.list.map((item) => item.id);
+  const oldList = listOption.queryFormFields.filter((item) =>
+    secondGroupIds.includes(item.id)
+  );
+  const firstGroupIds = listOption.queryFormFields.map((item) => item.id);
+  let newList = item.list.filter((item) => !firstGroupIds.includes(item.id));
+
+  //过滤掉默认不显示的数据
+  const hiddenList = new Set(pageHiddenList.value.map((item) => item.id));
+  const filterList = newList.filter((item) => !hiddenList.has(item.id));
+
+  listOption.queryFormFields = [...oldList, ...filterList];
+  queryFormRef.value?.initDefaultSaveForm();
+  selectedKeys.value = item.list.filter((item) => item.id);
+};
 
-const initData = () => {
-  api.logApi.list({
-    pages: pages.value,
-    size: size.value,
-    ...form.value
-  }).then((res: any) => {
-    if(res.code === 200) {
-      tableData.value = res.data.records
-      total.value = res.data.total
-    } else {
-      ElMessage({
-        message: res.msg,
-        type: 'warning'
-      })
-    }
-  })
+const isVisible = ref(false);
+const downloadFile = () => {
+  isVisible.value = true;
+  userStore.count++;
+};
+
+//分页事件
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
 }
 
+const agreementEdit = (id) => {
+  router.push({
+    name: "companyDataManageDetail",
+    query: {
+      id: id,
+    },
+  });
+};
 onMounted(() => {
-  initData()
-})
-
+  initDefaultList();
+});
 </script>
 
 <style scoped lang="scss">
-.log{
+.log {
   width: 100%;
   height: calc(100vh - 120px);
-  background: #FFFFFF;
+  background: #ffffff;
   padding: 24px;
   overflow-y: auto;
-  .pagination{
-    margin-top: 24px;
+  .query-btn {
+    margin-left: 20px;
+  }
+  .btn-box {
+    padding-bottom: 15px;
+    margin-bottom: 24px;
+    border-bottom: 1px solid #eeeeee;
     display: flex;
-    align-items: center;
     justify-content: flex-end;
   }
 }

+ 86 - 0
src/views/consoleManage/components/button.vue

@@ -0,0 +1,86 @@
+<template>
+  <!-- 导出按钮 -->
+  <div class='relative'>
+    <div ref='exportButton'>
+      <el-button type="primary" plain class='query-btn' @click='handleExport' :disabled="isExporting">导出</el-button>
+    </div>
+    <div ref="animationElement" class="absolute z-9999 transition-all duration-8000 ease-in-out opacity-0 "
+      style="pointer-events: none;">
+      <el-icon :size='22'>
+        <download color='#0083FF' />
+      </el-icon>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import message from "@/assets/images/message.png";
+const emits = defineEmits(["endAnim"]);
+
+const exportButton = ref(null);
+const animationElement = ref(null);
+const isExporting = ref(false);
+const iconSize = 30;
+const animationBaseStyles = computed(() => ({
+  transition: 'none',
+}));
+
+// 处理导出动画
+const handleExport = () => {
+  emits("endAnim",);
+  return false
+  if (isExporting.value) return;
+  isExporting.value = true;
+  const animEl = animationElement.value;
+  const btnEl = exportButton.value;
+  resetAnimationElement(animEl);
+
+  animEl.style.position = 'fixed';
+  const btnRect = btnEl.getBoundingClientRect();
+  const initialLeft = btnRect.left + (btnRect.width / 2) - (iconSize / 2);
+  const initialTop = btnRect.top + (btnRect.height / 2) - (iconSize / 2);
+  animEl.style.left = `${initialLeft}px`;
+  animEl.style.top = `${initialTop}px`;
+  animEl.offsetHeight;
+
+  setTimeout(() => {
+    animEl.style.transition = 'opacity 0.3s ease-out';
+    animEl.style.opacity = '1';
+  }, 50);
+
+  setTimeout(() => {
+    const targetRect = document.getElementById('navbarTarget').getBoundingClientRect();
+    const targetLeft = targetRect.left + (targetRect.width / 2) - (iconSize / 2) + 6;
+    const targetTop = targetRect.top + (targetRect.height / 2) - (iconSize / 2) + 6;
+    animEl.style.transition = 'all 1.5s cubic-bezier(0.2, 0.85, 0.4, 0.95)';
+    animEl.style.left = `${targetLeft}px`;
+    animEl.style.top = `${targetTop}px`;
+    animEl.style.transform = 'scale(0.9)';
+  }, 350);
+
+  // 动画结束后处理
+  setTimeout(() => {
+    animEl.style.transition = 'all 0.3s ease';
+    animEl.style.transform = 'scale(1) translateY(-5px)';
+    setTimeout(() => {
+      animEl.style.transition = 'opacity 0.3s ease-out';
+      animEl.style.opacity = '0';
+      isExporting.value = false;
+      emits("endAnim",);
+    }, 300);
+  }, 2000);
+};
+// 重置动画
+const resetAnimationElement = (el) => {
+  el.style.position = 'absolute';
+  el.style.transition = 'none';
+  el.style.opacity = '0';
+  el.style.transform = 'scale(1)';
+  el.style.left = '0px';
+  el.style.top = '0px';
+};
+</script>
+
+<style scoped>
+</style>

+ 376 - 0
src/views/consoleManage/components/filterDialog.vue

@@ -0,0 +1,376 @@
+<template>
+  <div class="filter-dialog" v-if='props.show'>
+    <el-dialog v-model="props.show" title="选择筛选条件" @close='handleCancel'>
+      <div class='dialog-box'>
+        <el-row :gutter="10" class='dialog-cont'>
+          <el-col :span="12" class='divider'>
+            <el-input v-model="searchName" placeholder="搜索" class='search-input'></el-input>
+            <div class="insuranceLeft">
+              <el-tree ref="treeRef" :data="treeList" :props="defaultProps" show-checkbox node-key="id"
+                @check="handleCheckChange" :check-strictly="false" :default-expand-all="true"
+                :default-checked-keys="selectedKeys" :filter-node-method="handleSearchName"></el-tree>
+            </div>
+          </el-col>
+          <el-divider direction="vertical" class='divider'></el-divider>
+          <el-col :span="11" class='divider'>
+            <div class='dialog-r-title'>
+              <span>已选: {{ selectedList.length }} 项筛选条件</span>
+              <el-button link type="primary" @click="clear">清空</el-button>
+            </div>
+            <div class="selectedList">
+              <div class='dialog-r-list' v-for="(JYX, index) in selectedList" :key="JYX.id">
+                <span>{{ JYX.label }}</span>
+                <el-icon class='del-icon' v-if='!JYX.defaultFlag && !JYX.selectFlag' @click="handleDel(JYX, index)">
+                  <Close />
+                </el-icon>
+              </div>
+            </div>
+          </el-col>
+        </el-row>
+        <div class="footer">
+          <el-button plain @click="handleCancel">取消</el-button>
+          <el-button type="primary" @click="handleConfirm">确定</el-button>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false,
+  },
+  routerKey: {
+    type: String,
+    default: "",
+  },
+});
+
+const shows = ref(true);
+
+// 查找
+import api from "@/api";
+import { ElMessage } from "element-plus";
+import type { ElTree } from "element-plus";
+
+const treeRef = ref<InstanceType<typeof ElTree>>();
+const emits = defineEmits(["close", "selectedKeys", "sure"]);
+let searchName = ref("");
+watch(searchName, (val) => {
+  treeRef.value!.filter(val);
+});
+
+const handleSearchName = (value: string, data: any) => {
+  if (!value) return true;
+  return data.label.includes(value);
+};
+
+// 默认勾选的数据
+let selectedKeys = ref([]);
+
+// 数据源
+let treeList = ref([
+  // {
+  //   label: "时间",
+  //   id: 1,
+  //   inputType: "date-picker",
+  //   key: "date2",
+  //   defaultValue: "",
+  // },
+  // {
+  //   label: "签单时间",
+  //   id: 2,
+  //   inputType: "date-picker",
+  //   key: "date2",
+  //   defaultValue: "",
+  // },
+  // {
+  //   label: "录单时间",
+  //   id: 3,
+  //   inputType: "date-picker",
+  //   key: "date3",
+  //   defaultValue: "",
+  // },
+  // {
+  //   label: "保险公司",
+  //   id: 4,
+  //   inputType: "select-single",
+  //   key: "test4",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+  // {
+  //   label: "合作保险公司",
+  //   id: 5,
+  //   inputType: "select-multiple",
+  //   key: "test5",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+  // {
+  //   label: "业务员类型",
+  //   id: 6,
+  //   inputType: "select-multiple",
+  //   key: "test6",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+  // {
+  //   label: "协议类型",
+  //   id: 7,
+  //   inputType: "select-multiple",
+  //   key: "test7",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+  // {
+  //   label: "出单机构",
+  //   id: 8,
+  //   inputType: "date-tree",
+  //   key: "test8",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   defaultProps: {
+  //     children: "children",
+  //     label: "label",
+  //   },
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+  // {
+  //   label: "订单状态",
+  //   id: 9,
+  //   inputType: "select-single",
+  //   key: "test9",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+  // {
+  //   label: "订单类型",
+  //   id: 10,
+  //   inputType: "select-multiple",
+  //   key: "test10",
+  //   listOption: [],
+  //   defaultValue: [],
+  //   selectValue: "", //用逗号分开存储的值
+  // },
+]);
+const defaultProps = {
+  children: "children",
+  label: "label",
+  disabled: function (data) {
+    return data.defaultFlag || data.selectFlag || !data.showFlag;
+  },
+};
+const handleCheckChange = (data, row) => {
+  if (row.checkedNodes.length > 0) {
+    let list1 = row.checkedNodes.filter((a) => a.parentId == "0");
+    let list2 = row.checkedNodes.filter((a) => a.parentId != "0");
+    if (list1.length > 0) {
+      let list3 = list1.filter((a) => list2.some((b) => b.parentId == a.id));
+      selectedList.value = row.checkedNodes.filter(
+        (a) => !list3.some((b) => b.id == a.id)
+      );
+    } else {
+      selectedList.value = list2;
+    }
+    selectedList.value.forEach((item) => {
+      item.drivingIntentionId = item.id;
+    });
+  }
+};
+
+// 选择驾意险
+let selectedList = ref([]);
+const handleType = (item, index) => {
+  item.checked = !item.checked;
+  selectedList.value = [];
+  treeList.value.forEach((sub) => {
+    if (sub.checked) {
+      selectedList.value.push(sub);
+    }
+  });
+};
+
+// 删除
+const handleDel = (item, index) => {
+  selectedList.value.splice(index, 1);
+  selectedKeys.value = selectedList.value.map((a) => a.drivingIntentionId);
+  treeRef.value!.setCheckedKeys(selectedKeys.value, true);
+};
+
+// 编辑
+let editIndex = ref(0);
+const initEditData = (data, index, id) => {
+  editIndex.value = index;
+  api.projectApi
+    .treeList({
+      company: id ? id : "1738811735599380291",
+    })
+    .then((res) => {
+      if (res.code == 200) {
+        if (res.data && res.data.length > 0) {
+          res.data.forEach((item) => {
+            item.checked = false;
+            item.drivingIntentionId = item.id;
+          });
+        }
+        treeList.value = res.data;
+        if (data && data.length > 0) {
+          selectedList.value = JSON.parse(JSON.stringify(data));
+          selectedKeys.value = selectedList.value.map(
+            (a) => a.drivingIntentionId
+          );
+        }
+      } else {
+        ElMessage({
+          message: res.msg,
+          type: "warning",
+        });
+      }
+    });
+};
+
+// 取消
+const handleCancel = () => {
+  emits("close");
+};
+// 保存
+const handleConfirm = () => {
+  emits("sure", {
+    list: selectedList.value,
+    index: editIndex.value,
+  });
+};
+//赋默认值
+const defaultSelectedId = ref();
+const initDefaultSelect = function (list) {
+  if (list && list.length) {
+    let ids = getMultiLabels(list, treeList.value);
+    selectedKeys.value = ids;
+    emits("selectedKeys", selectedKeys.value);
+  }
+};
+//数据过滤
+const getMultiLabels = (selectedValues, foodList) => {
+  return selectedValues.map((targetValue) => {
+    const matchedItem = foodList.find(
+      (item) => item.label == targetValue.label
+    );
+    return matchedItem ? matchedItem.id : "";
+  });
+};
+// 清空
+const clear = () => {
+  selectedList.value = selectedList.value.filter((item) =>
+    defaultSelectedId.value.includes(item.id)
+  );
+  treeRef.value!.setCheckedKeys(defaultSelectedId.value, true);
+};
+//获取全部选择数据
+const phone = JSON.parse(localStorage.getItem("userInfo")).username;
+const initList = async (list, defaultList) => {
+  defaultSelectedId.value = defaultList;
+  if (treeList.value.length) {
+    initDefaultSelect(list);
+    selectedList.value = list || [];
+  } else {
+    const { data } = await api.manageApi.getSearchConfigurationList({
+      routerKey: props.routerKey,
+      userId: phone,
+    });
+     treeList.value = data;
+    initDefaultSelect(list);
+    selectedList.value = list || [];
+  }
+};
+
+defineExpose({
+  initEditData,
+  initList,
+});
+</script>
+
+<style scoped lang="scss">
+.filter-dialog {
+  .dialog-box {
+    height: 430px;
+    .dialog-cont {
+      height: calc(100% - 30px);
+      border: 1px solid #ccc;
+      margin: 5px;
+      border-radius: 5px;
+      padding: 5px;
+      overflow: hidden;
+    }
+    .dialog-r-title {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin: 8px 0;
+    }
+    .dialog-r-list {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin: 10px;
+    }
+    .divider {
+      height: 100%;
+    }
+    .search-input {
+      margin-bottom: 10px;
+    }
+    .del-icon {
+      cursor: pointer;
+    }
+    :deep(.el-tree-node) {
+      margin-bottom: 8px;
+    }
+  }
+}
+.insuranceLeft,
+.selectedList {
+  height: calc(100% - 42px);
+  overflow-y: auto;
+}
+.insuranceLeft {
+  width: 100%;
+  .insuranceItem {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+    span {
+      margin-left: 10px;
+    }
+    .checkIcon {
+      width: 14px;
+      height: 14px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border: 1px solid #ccc;
+      border-radius: 2px;
+      font-size: 10px;
+      color: #fff;
+    }
+    .active {
+      color: #fff;
+      background: #3994fb;
+      border-color: #3994fb;
+    }
+  }
+}
+.footer {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 397 - 0
src/views/consoleManage/components/queryForm.vue

@@ -0,0 +1,397 @@
+
+<template>
+  <div class="form-filter">
+    <el-form label-position="left" :model="queryForm" class='form-content'>
+      <template v-for="(option, index) in option.queryFormFields" :key="index">
+        <!-- input -->
+        <el-form-item :label="option.label" class='form-input r-24 border position-box'
+          v-if="option.inputType == 'input'" @mouseenter="option.isShowDelIcon = true"
+          @mouseleave="option.isShowDelIcon = false">
+          <el-input class="input-deep input-w" v-model="queryForm.data[option.key]" :placeholder="`请输入${option.label}`"
+            clearable>
+          </el-input>
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+        <!-- select -->
+        <el-form-item :label="option.label" class='form-select-single  r-24 border position-box'
+          v-if="option.inputType == 'select-single' || option.selectType=='1'" @mouseenter="option.isShowDelIcon = true"
+          @mouseleave="option.isShowDelIcon = false">
+          <el-select class='min-w ' v-model="queryForm.data[option.key]" placeholder=""
+            @change='singleChange($event,option)' clearable>
+            <el-option v-for="item in option.listOption" :key="item.value" :label="item.label" :value="item.value">
+            </el-option>
+          </el-select>
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+        <!-- select 多选-->
+        <el-form-item :label="option.label" class='form-select-multiple  r-24 border position-box'
+          v-if="option.inputType == 'select-multiple' || option.selectType=='2'"
+          @mouseenter="option.isShowDelIcon = true" @mouseleave="option.isShowDelIcon = false">
+          <el-select class='min-w' multiple v-model="queryForm.data[option.key]" value-key="value" placeholder=""
+            @change='moreChange($event,option,option.listOption)' clearable>
+            <el-option v-for="item in option.listOption" :key="item.value" :label="item.label" :value="item.value">
+            </el-option>
+            <template #tag>
+              <view v-show='option.selectValue'>
+                <text v-if='option.listOption'>{{option.selectValue}}</text>
+                <text class='text-color' v-else>请选择</text>
+              </view>
+            </template>
+          </el-select>
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+        <!-- 日期 -->
+        <el-form-item :label="option.label" class='form-date-picker  r-24 border position-box'
+          v-if="option.inputType == 'date-picker'" @mouseenter="option.isShowDelIcon = true"
+          @mouseleave="option.isShowDelIcon = false">
+          <el-date-picker v-model="queryForm.data[option.key]" type="daterange" unlink-panels start-placeholder="开始日期"
+            end-placeholder="结束日期" :shortcuts="shortcuts" value-format="YYYY-MM-DD">
+            <template #range-separator>
+              <view>⇀</view>
+            </template>
+          </el-date-picker>
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+        <!-- 年份 -->
+        <el-form-item :label="option.label" class='form-date-picker  r-24 border position-box'
+          v-if="option.inputType == 'date-year'" @mouseenter="option.isShowDelIcon = true"
+          @mouseleave="option.isShowDelIcon = false">
+          <el-date-picker v-model="queryForm.data[option.key]" type="year" placeholder="选择年份" value-format="YYYY">
+            <template #range-separator>
+              <view>⇀</view>
+            </template>
+          </el-date-picker>
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+        <!-- tree  selectValue: [], //选中的Key存储的值   listOption原始数据 -->
+        <el-form-item :label="option.label" class='form-date-tree  r-24 border position-box'
+          v-if="option.inputType == 'date-tree'" @mouseenter="option.isShowDelIcon = true"
+          @mouseleave="option.isShowDelIcon = false">
+          <el-tree-select v-model="queryForm.data[option.key]" :data="option.listOption" :render-after-expand="false"
+            :props="defaultProps" node-key="id" show-checkbox multiple
+            @check="(data,checked)=>handleCheckChange(data,checked,option,queryForm.data[option.key])">
+            <template #tag>
+              <view v-show='option.selectValue'>{{option.selectValue}}</view>
+            </template>
+          </el-tree-select>
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+        <!-- 级联选择 -->
+        <el-form-item :label="option.label" class='form-date-cascader  r-24 border position-box'
+          v-if="option.inputType == 'date-cascader'" @mouseenter="option.isShowDelIcon = true"
+          @mouseleave="option.isShowDelIcon = false">
+          <el-cascader v-model="queryForm.data[option.key]" :options="option.listOption" clearable
+            :props="optionProps" />
+          <template v-if='!option.defaultFlag'>
+            <view class='icon-del' @click='del(option)' v-show='option.isShowDelIcon'>
+              <el-icon>
+                <CircleCloseFilled color='#ccc' :size='30' />
+              </el-icon>
+            </view>
+          </template>
+        </el-form-item>
+
+      </template>
+      <el-button type="primary" @click='query'>查询</el-button>
+    </el-form>
+
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import { timestampFormat } from "@/utils/dayjs";
+import api from "@/api";
+
+const emit = defineEmits(["query", "delSelect"]);
+
+const props = defineProps({
+  option: {
+    type: Object,
+    default: () => {
+      return {
+        labelWidth: {}, // label宽度,
+      };
+    },
+  },
+});
+
+const defaultProps = {
+  children: "children",
+  label: "name",
+};
+
+const optionProps = {
+  value: "areaCode",
+  label: "areaCname",
+  children: "child",
+};
+
+const showDelete = ref(false);
+let queryForm = reactive({ data: {} });
+const shortcuts = [
+  {
+    text: "今日",
+    value: () => {
+      const end = new Date();
+      const start = new Date();
+      start.setTime(start.getTime());
+      return [start, end];
+    },
+  },
+  {
+    text: "最近7天",
+    value: () => {
+      const end = new Date();
+      const start = new Date();
+      start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+      return [start, end];
+    },
+  },
+  {
+    text: "本月",
+    value: () => {
+      const year = new Date().getFullYear();
+      const month = new Date().getMonth() + 1;
+      const date = new Date(year, month, 0).getDate();
+
+      const end = new Date(year, month - 1, date);
+      const start = new Date(year, new Date().getMonth(), 1);
+      return [start, end];
+    },
+  },
+  {
+    text: "本年",
+    value: () => {
+      const end = new Date(new Date().getFullYear(), 11, 31);
+      const start = new Date(new Date().getFullYear(), 0, 1);
+      return [start, end];
+    },
+  },
+];
+//获取默认值
+const propsOption = props.option;
+const initDefaultSaveForm = () => {
+  let defaultSaveForm = {};
+  propsOption.queryFormFields.forEach((item) => {
+    let key = item.key;
+    let val = item.defaultValue;
+    if (item.inputType == "date-picker" && !val) {
+      const start = new Date();
+      defaultSaveForm[key] = [
+        timestampFormat(start.getTime()),
+        timestampFormat(start.getTime()),
+      ];
+    } else if (item.inputType == "date-year") {
+      const start = new Date();
+      defaultSaveForm[key] = [timestampFormat(start.getTime(), "YYYY")];
+    } else {
+      let mul = item.inputType == "select-multiple";
+      let sin = item.inputType == "select-single";
+      if (mul || sin) {
+        defaultSaveForm[key] = item.listOption ? val : "";
+      } else {
+        defaultSaveForm[key] = val;
+      }
+      if (mul) {
+        defaultSaveForm[key] = [val];
+      }
+      if (sin && item.isFirst) {
+        singleChange(0, item);
+      }
+    }
+  });
+  queryForm.data = Object.assign({}, defaultSaveForm);
+  console.log(queryForm.data);
+};
+
+//多选中发生变化
+const moreChange = (selectedValues: CheckboxValueType, item, foodList) => {
+  const foodLabels = getMultiLabels(selectedValues, foodList, "value");
+  item.selectValue = foodLabels.join(",");
+};
+
+//tree  change
+const handleCheckChange = (data, row, option, ids) => {
+  const foodLabels = getMultiLabels(ids, row.checkedNodes, "id");
+  option.selectValue = foodLabels.join(",");
+};
+
+//数据过滤
+const getMultiLabels = (selectedValues, foodList, key) => {
+  return selectedValues.map((targetValue) => {
+    const matchedItem = foodList.find((item) => item[key] === targetValue);
+    return matchedItem ? matchedItem.label || matchedItem.name : "";
+  });
+};
+
+//单选查联动数据
+const singleChange = async (val, option) => {
+  if (option.isFirst) {
+    //查询合作的保险公司数据
+    const { data } = await api.manageApi.getCompanyListByCompanyId({
+      companyId: val,
+    });
+    queryForm.data.partnerCompanyIds = "";
+    propsOption.queryFormFields.forEach((e) => {
+      if (e.key == "partnerCompanyIds") {
+        e.selectValue = "";
+        e.listOption = data;
+        e.listOption.unshift({
+          value: "0",
+          label: "全部",
+        });
+      }
+    });
+  }
+};
+
+//查询
+const query = () => {
+  emit("query", queryForm.data);
+};
+
+//删除
+const del = (option) => {
+  queryForm.data[option.key] = "";
+  emit("delSelect", option.id);
+};
+
+// 暴露给父组件的方法
+defineExpose({
+  initDefaultSaveForm,
+});
+</script>
+
+<style scoped lang="scss">
+.form-content {
+  display: flex;
+  flex-wrap: wrap;
+  .r-24 {
+    margin-right: 24px;
+  }
+  .border {
+    border-radius: 2px 2px 2px 2px;
+    border: 1px solid #e5e5e5;
+  }
+  :deep(.el-form-item__label) {
+    padding-left: 12px;
+  }
+  .min-w {
+    min-width: 200px;
+  }
+  .input-w {
+    width: 200px;
+  }
+  .position-box {
+    position: relative;
+    .icon-del {
+      position: absolute;
+      right: -8px;
+      top: -14px;
+    }
+    :deep(.el-icon) {
+      font-size: 20px;
+    }
+  }
+  .text-color {
+    color: #a3a8b3;
+  }
+}
+// input
+.form-input {
+  .input-deep {
+    :deep(.el-input__wrapper) {
+      box-shadow: 0 0 0 0px var(--el-input-border-color, var(--el-border-color))
+        inset;
+      cursor: default;
+      .el-input__inner {
+        cursor: default !important;
+      }
+    }
+  }
+}
+//select
+.form-select-single,
+.form-select-multiple {
+  :deep(.el-select__wrapper) {
+    border: none; /* 去除边框 */
+    box-shadow: none;
+  }
+}
+
+//picker
+.form-date-picker {
+  :deep(.el-date-editor.el-input__wrapper) {
+    border: none; /* 去除边框 */
+    box-shadow: none;
+    padding-left: 0;
+    padding-right: 0;
+    width: 240px;
+  }
+  :deep(.el-input__wrapper) {
+    border: none; /* 去除边框 */
+    box-shadow: none;
+  }
+  :deep(.el-date-editor .el-range__icon) {
+    display: none;
+  }
+}
+
+//tree
+.form-date-tree {
+  :deep(.el-select__wrapper) {
+    box-shadow: none;
+    min-width: 240px;
+  }
+}
+//el-cascader
+.form-date-cascader {
+  :deep(.el-cascader .el-input__wrapper) {
+    box-shadow: 0 0 0 0px var(--el-input-border-color, var(--el-border-color))
+      inset;
+  }
+}
+</style>

+ 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="item.badge" :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 {

+ 10 - 0
src/views/quoteConfig/dispatch/dispatch.vue

@@ -0,0 +1,10 @@
+<template>
+  <div>dispatch</div>
+</template>
+
+<script lang="ts" setup>
+</script>
+
+<style>
+
+</style>

+ 296 - 0
src/views/sale/commission.vue

@@ -0,0 +1,296 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <ElForm
+        :inline="false"
+        :model="formInline"
+        class="demo-form-inline"
+        label-width="100px"
+      >
+        <ElRow :gutter="20">
+          <ElCol :md="8">
+            <ElFormItem label="分销员姓名">
+              <el-input
+                v-model="formInline.distributor"
+                placeholder="请输入姓名查找"
+                clearable
+                class="el-inputs"
+              />
+            </ElFormItem>
+          </ElCol>
+          <ElCol :md="8">
+            <ElFormItem label="分销员手机号">
+              <el-input
+                v-model="formInline.distributorMobile"
+                placeholder="请输入手机号查找"
+                clearable
+                class="el-inputs"
+              />
+            </ElFormItem>
+          </ElCol>
+          <!-- <ElCol :md="8">
+            <ElFormItem label="推荐上级">
+              <el-input
+                v-model="formInline.inviteePerson"
+                placeholder="请输入推荐人姓名查找"
+                clearable
+                class="el-inputs"
+              />
+            </ElFormItem>
+          </ElCol> -->
+          <ElCol :md="8">
+            <ElFormItem label="签单时间">
+              <el-date-picker
+                v-model="joinTimeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                size="default"
+                format="YYYY-MM-DD"
+              />
+            </ElFormItem>
+          </ElCol>
+          <ElCol :md="6">
+            <ElFormItem>
+              <div class="flex j-end" style="width: 100%">
+                <el-button type="primary" @click="findpage">
+                  <template #icon>
+                    <SvgIcon name="i-ep:search" />
+                  </template>
+                  查询</el-button
+                >
+                <el-button type="default" @click="resetForm">重置</el-button>
+              </div>
+            </ElFormItem>
+          </ElCol>
+        </ElRow>
+      </ElForm>
+      <div>
+        <el-button type="default" @click="exportExcel">导出</el-button>
+      </div>
+      <div style="height: 559px;">
+        <el-table :data="tableData">
+          <el-table-column prop="orderNo" label="订单编号" width="180">
+            <template #default="{ row }">
+              <el-link
+                type="primary"
+                @click="handleOrderClick(row.orderNo, row)"
+              >
+                {{ row.orderNo }}
+              </el-link>
+            </template>
+          </el-table-column>
+          <el-table-column prop="premium" label="保费金额" width="180" />
+          <el-table-column prop="salesman" label="业务员" width="150" />
+          <el-table-column
+            prop="salesmanMobile"
+            label="业务员手机号"
+            width="180"
+          />
+          <el-table-column prop="signingTime" label="签单时间" width="180" />
+          <el-table-column prop="distributor" label="分销员" width="100" />
+          <el-table-column
+            prop="distributorMobile"
+            label="分销员手机号"
+            width="180"
+          />
+
+          <el-table-column prop="commission" label="分销员金额" width="180" />
+
+          <el-table-column
+            prop="distributionLevel"
+            label="分销员等级"
+            width="100"
+          >
+          <template #default="{ row }">
+             {{ row.distributionLevel==1?'一级分销':row.distributionLevel==2?'二级分销':'三级分销' }}
+            </template>
+        </el-table-column>
+          <el-table-column
+            prop="distributionRatio"
+            label="分销比例"
+            width="180"
+          />
+        </el-table>
+      </div>
+
+      <div class="pagination-wrapper">
+        <el-pagination
+          v-model:current-page="pageInfo.pageNum"
+          v-model:page-size="pageInfo.pageSize"
+          :page-sizes="[10, 20, 30, 50, 100]"
+          :total="pageInfo.total"
+          layout="total, sizes, prev, pager, next, jumper"
+          background
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </PageMain>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, watch, onBeforeMount } from "vue";
+import api from "@/api";
+import { RouterLink, useRouter } from "vue-router";
+import {
+  ElMessage,
+  ElMessageBox,
+  formatter,
+  TableColumnCtx,
+} from "element-plus";
+import useAuth from "@/utils/composables/useAuth";
+import dayjs from "dayjs";
+import { template } from "lodash-es";
+const router = useRouter();
+const { auth } = useAuth();
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+const provinceoptions = ref<string[]>([]); //省市区回显数据
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 10,
+  sizes: [10, 20, 30, 40, 50],
+  total: 100,
+});
+const formInline = reactive({
+  distributor: "",
+  distributorMobile: "",
+  inviteePerson: "",
+  signingTimeStart: "",
+  signingTimeEnd: "",
+});
+const joinTimeRange = ref([]);
+const tableData = ref([]);
+onBeforeMount(() => {
+  findpage(); //列表数据
+  if (auth("organizationlist.view")) {
+  }
+});
+function findpage() {
+  console.log(formInline);
+
+  api.saleApi
+    .commissionDetails({
+      pages: pageInfo.value.pageNum,
+      size: pageInfo.value.pageSize,
+      ...formInline,
+    })
+    .then((res: any) => {
+      if (res.code == 200) {
+        tableData.value = res.data.records;
+        pageInfo.value.pageNum = res.data.current;
+        pageInfo.value.pageSize = res.data.size;
+        pageInfo.value.total = res.data.total;
+      }
+    });
+}
+
+const resetForm = () => {
+  (formInline.distributor = ""),
+    (formInline.distributorMobile = ""),
+    (formInline.inviteePerson = ""),
+    (formInline.signingTimeStart = ""),
+    (formInline.signingTimeEnd = ""),
+    (provinceoptions.value = []);
+  findpage();
+};
+const exportExcel = () => {
+  api.saleApi.export({}).then((res) => {
+    console.log(res);
+    const blob = new Blob([res], { type: "application/vnd.ms-excel" });
+    const fileName = "分销佣金明细" + new Date().getTime() + ".xlsx";
+    if ("download" in document.createElement("a")) {
+      const elink = document.createElement("a");
+      elink.download = fileName;
+      elink.style.display = "none";
+      elink.href = URL.createObjectURL(blob);
+      document.body.appendChild(elink);
+      elink.click();
+      URL.revokeObjectURL(elink.href);
+      document.body.removeChild(elink);
+    } else {
+      navigator.msSaveBlob(blob, fileName);
+    }
+  });
+};
+// 每页显示条数改变
+const handleSizeChange = (newSize: number) => {
+  pageInfo.pageSize = newSize;
+  pageInfo.pageNum = 1; // 重置到第一页
+  findpage();
+};
+
+// 当前页改变
+const handleCurrentChange = (newPage: number) => {
+  pageInfo.pageNum = newPage;
+  findpage();
+};
+const handleOrderClick = (orderNo: string, row: any) => {
+  const resolved = router.resolve({
+    path: `/additionalOrder/order/orderDetail`,
+     query: { 
+      id: row.orderNo  // 查询参数,id 的值为 row.orderNo
+    }
+  });
+  window.open(resolved.href, '_blank');
+};
+watch(
+  joinTimeRange,
+  (newRange) => {
+    if (Array.isArray(newRange) && newRange.length === 2) {
+      formInline.signingTimeStart = dayjs(newRange[0]).format("YYYY-MM-DD");
+      formInline.signingTimeEnd = dayjs(newRange[1]).format("YYYY-MM-DD");
+    } else {
+      formInline.signingTimeStart = "";
+      formInline.signingTimeEnd = "";
+    }
+  },
+  { immediate: true }
+);
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+::v-deep.el-table td.el-table__cell div i {
+  display: none;
+}
+::v-deep.el-table .caret-wrapper {
+  display: none;
+}
+:deep(.current-style) {
+  color: #339dff;
+  position: relative;
+  padding-left: 20px;
+}
+.pagination-wrapper {
+  display: flex;
+  justify-content: flex-end; // 右对齐
+  width: 100%;
+  padding: 20px 0;
+}
+</style>

+ 173 - 0
src/views/sale/components/drawer.vue

@@ -0,0 +1,173 @@
+<template>
+  <div>
+    <el-drawer 
+      v-model="props.Drawer" 
+      :direction="direction" 
+      @open="DrawerOpen" 
+      @close="cancel" 
+      :close-on-click-modal="false"
+    >
+      <template #header>
+        <h4>参与机构</h4>
+      </template>
+      <template #default>
+        <div class="flex f-c">
+          <div>
+            <el-input 
+              v-model="deptName" 
+              class="m-right-20" 
+              style="width: 240px;" 
+              placeholder="输入名称"
+              :prefix-icon="Search" 
+            />
+            <el-button @click="query">查询</el-button>
+          </div>
+          <!-- 全选复选框 -->
+          <el-checkbox 
+            v-model="checkedALL" 
+            label="全选" 
+            size="large"
+            @change="handleCheckAllChange"
+          />
+          <!-- 树形组件 -->
+          <el-tree 
+            :data="props.deptlist" 
+            :show-checkbox="true" 
+            node-key="id" 
+            ref="treeRef" 
+            :props="defaultProps"
+            :default-expand-all="true" 
+            @check="handleCheck" 
+            :default-checked-keys="props.checkedKeys"
+            :check-strictly="true" 
+          />
+        </div>
+      </template>
+      <template #footer>
+        <div>
+          <el-button @click="cancel">取消</el-button>
+          <el-button type="primary" @click="save">保存</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { defineProps, defineEmits, ref, watch, nextTick } from "vue";
+import { DrawerProps } from 'element-plus';
+import { Search } from '@element-plus/icons-vue'
+import { ElTree } from 'element-plus';
+
+// 定义属性类型
+interface DeptNode {
+  id: string;
+  name: string;
+  children?: DeptNode[];
+}
+
+const props = defineProps<{
+  Drawer: boolean;
+  deptlist: DeptNode[];
+  checkedKeys: string[];
+  checkedIds: string[];
+}>();
+
+const emits = defineEmits(["cancel", 'save', 'DrawerOpen', 'query']);
+
+// 树形组件配置
+const defaultProps = {
+  children: 'children',
+  label: 'name',
+};
+
+const direction = ref<DrawerProps['direction']>('rtl');
+const deptName = ref('');
+const treeRef = ref<InstanceType<typeof ElTree>>();
+const checkedALL = ref(false);
+const ids = ref<string[]>([]);
+
+// 当抽屉打开时初始化全选状态
+const DrawerOpen = () => {
+  nextTick(() => {
+    updateCheckAllStatus();
+  });
+};
+
+// 监听属性变化,更新选中状态和全选状态
+watch(props, () => {
+  if (props.Drawer) {
+    ids.value = [...props.checkedIds];
+    nextTick(() => {
+      updateCheckAllStatus();
+    });
+  }
+}, { deep: true });
+
+// 处理全选状态变化
+const handleCheckAllChange = (checked: boolean) => {
+  if (!treeRef.value) return;
+  
+  if (checked) {
+    // 全选:获取所有节点ID并选中
+    const allIds = getAllNodeIds(props.deptlist);
+    treeRef.value.setCheckedKeys(allIds);
+    ids.value = allIds;
+  } else {
+    // 取消全选:清空选中
+    treeRef.value.setCheckedKeys([]);
+    ids.value = [];
+  }
+};
+
+// 处理树形组件选择变化
+const handleCheck = (
+  _checkedNodes: any[],
+  checkedInfo: {
+    checkedKeys: string[];
+    checkedNodes: any[];
+    halfCheckedKeys: any[];
+    halfCheckedNodes: any[];
+  }
+) => {
+  ids.value = checkedInfo.checkedKeys;
+  updateCheckAllStatus(); // 更新全选框状态
+};
+
+// 更新全选框状态
+const updateCheckAllStatus = () => {
+  if (!treeRef.value) return;
+  
+  const allIds = getAllNodeIds(props.deptlist);
+  // 当所有节点都被选中时,全选框才勾选
+  checkedALL.value = allIds.length > 0 && 
+    ids.value.length === allIds.length &&
+    allIds.every(id => ids.value.includes(id));
+};
+
+// 递归获取所有节点的ID
+const getAllNodeIds = (nodes: DeptNode[], result: string[] = []): string[] => {
+  nodes.forEach(node => {
+    result.push(node.id);
+    if (node.children && node.children.length > 0) {
+      getAllNodeIds(node.children, result);
+    }
+  });
+  return result;
+};
+
+// 取消操作
+const cancel = () => {
+  emits('cancel');
+};
+
+// 保存操作
+const save = () => {
+  emits('save', ids.value);
+};
+
+// 查询操作
+const query = () => {
+  emits('query', deptName.value);
+};
+</script>

+ 535 - 0
src/views/sale/list.vue

@@ -0,0 +1,535 @@
+<template>
+  <div>
+    <PageMain class="vh100">
+      <ElForm
+        :inline="false"
+        :model="formInline"
+        class="demo-form-inline"
+        label-width="100px"
+      >
+        <ElRow :gutter="20">
+          <ElCol :md="8">
+            <ElFormItem label="姓名">
+              <el-input
+                v-model="formInline.name"
+                placeholder="请输入姓名查找"
+                clearable
+                class="el-inputs"
+              />
+            </ElFormItem>
+          </ElCol>
+          <ElCol :md="8">
+            <ElFormItem label="手机号">
+              <el-input
+                v-model="formInline.mobile"
+                placeholder="请输入手机号查找"
+                clearable
+                class="el-inputs"
+              />
+            </ElFormItem>
+          </ElCol>
+          <ElCol :md="8">
+            <ElFormItem label="推荐上级">
+              <el-input
+                v-model="formInline.inviteePerson"
+                placeholder="请输入推荐人姓名查找"
+                clearable
+                class="el-inputs"
+              />
+            </ElFormItem>
+          </ElCol>
+          <ElCol :md="8">
+            <ElFormItem label="申请时间">
+              <el-date-picker
+                v-model="joinTimeRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                size="default"
+                format="YYYY-MM-DD"
+              />
+            </ElFormItem>
+          </ElCol>
+          <ElCol :md="6" :offset="10">
+            <ElFormItem>
+              <div class="flex j-end" style="width: 100%">
+                <el-button type="primary" @click="roleQuery">
+                  <template #icon>
+                    <SvgIcon name="i-ep:search" />
+                  </template>
+                  查询</el-button
+                >
+                <el-button type="default" @click="resetForm">重置</el-button>
+              </div>
+            </ElFormItem>
+          </ElCol>
+        </ElRow>
+      </ElForm>
+      <ComplexTable
+        :tableData="tableData"
+        :columns="columns"
+        :showIndex="false"
+        :columnwidth="200"
+        :showSelect="true"
+        :pageInfo="pageInfo"
+        @getList="getList"
+        @selectionChange="selectionChange"
+      >
+        <template v-slot:operation="scope">
+          <el-button type="text" @click="backtopEmits(scope.operationData)">
+            清退
+          </el-button>
+          <el-button type="text" @click="look(scope.operationData)">
+            关系查询
+          </el-button>
+        </template>
+      </ComplexTable>
+    </PageMain>
+    <el-dialog
+      v-model="dialogVisible"
+      title="分销员清退"
+      width="700"
+      :before-close="handleClose"
+    >
+      <p>正在选择清退分销员{{ deleteData.name }}</p>
+      <p>
+        清退后,将不在支持该分销员发展下级,也不在享受订单的分销佣金;原有的分销佣金不受影响。
+      </p>
+      <div>
+        <el-radio-group
+          v-model="deleteData.type"
+          style="display: flex; flex-direction: column; align-items: start"
+        >
+          <el-radio :value="1">不处理,分销员关系保留</el-radio>
+          <el-radio :value="2">解除分销员和下级的绑定关系</el-radio>
+          <el-radio :value="3"
+            >转移下级分销员给某一个分销员
+            <el-input
+              v-model="deleteData.mobile"
+              maxlength="11"
+              :style="{
+                width: '240px',
+                marginLeft: '20px',
+                border:
+                  deleteData.type === 3 &&
+                  (!deleteData.mobile || deleteData.mobile.trim() === '')
+                    ? '1px solid #f56c6c'
+                    : '1px solid #dcdfe6',
+              }"
+              placeholder="输入接收人手机号"
+            />
+            <span
+              v-if="
+                deleteData.type === 3 &&
+                (!deleteData.mobile || deleteData.mobile.trim() === '')
+              "
+              style="color: #f56c6c; font-size: 12px; margin-left: 20px"
+            >
+              {{ errorTitle }}
+            </span>
+          </el-radio>
+        </el-radio-group>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false">取消</el-button>
+          <el-button type="primary" @click="deleteUser"> 确定清退 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+    <!-- 关系弹窗 -->
+    <el-dialog
+      v-model="isRelation"
+      title="关系查询"
+      width="700"
+      :before-close="handleClose"
+    >
+      <el-table
+        :data="relationTreeData"
+        style="width: 100%; margin-bottom: 20px"
+        row-key="id"
+        default-expand-all
+        :indent="16"
+        :tree-props="{ children: 'children' }"
+        :row-class-name="getRowClassName"
+      >
+        <el-table-column prop="name" label="姓名" sortable> </el-table-column>
+        <el-table-column prop="mobile" label="手机号" sortable />
+        <el-table-column prop="distributionTime" label="加入时间" sortable />
+      </el-table>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, watch, onBeforeMount } from "vue";
+import api from "@/api";
+import { RouterLink, useRouter } from "vue-router";
+import {
+  ElMessage,
+  ElMessageBox,
+  formatter,
+  TableColumnCtx,
+} from "element-plus";
+import useAuth from "@/utils/composables/useAuth";
+import dayjs from "dayjs";
+const { auth } = useAuth();
+interface PageInfo {
+  pageNum: number;
+  pageSize: number;
+  sizes: number[];
+  total: number;
+}
+const props = {
+  expandTrigger: "hover" as const,
+  value: "areaCode",
+  label: "areaCname",
+  children: "child",
+};
+const sysUpAndDownList = ref<string[]>([]); //选中id
+const provinceoptions = ref<string[]>([]); //省市区回显数据
+const areaOptions = ref(); //省市区数据
+// const provinceChange = (value: any) => {
+//   if (value && value[0] != null && value[1] != null && value[2] != null) {
+//     formInline.provinceId = value[0];
+//     formInline.cityId = value[1];
+//     formInline.districtId = value[2];
+//   } else {
+//     formInline.provinceId = "";
+//     formInline.cityId = "";
+//     formInline.districtId = "";
+//   }
+// };
+const router = useRouter();
+const pageInfo = ref({
+  pageNum: 1,
+  pageSize: 10,
+  sizes: [10, 20, 30, 40, 50],
+  total: 0,
+});
+const sysTypelist = ref([
+  {
+    id: 0,
+    name: "保险公司",
+  },
+  {
+    id: 1,
+    name: "保险分公司",
+  },
+  {
+    id: 2,
+    name: "保险经济公司",
+  },
+  {
+    id: 3,
+    name: "汽车经销商",
+  },
+  {
+    id: 4,
+    name: "保险代理公司",
+  },
+]);
+const formInline = reactive({
+  name: "",
+  mobile: "",
+  inviteePerson: "",
+  joinTimeStart: "",
+  joinTimeEnd: "",
+});
+const joinTimeRange = ref([]);
+const tableData = ref([]);
+const dialogVisible = ref(false);
+const deleteData = ref({
+  type: 0,
+  mobile: "",
+  id: "",
+  name: "",
+});
+const relationTreeData = ref([]);
+const isRelation = ref(false);
+const currentName = ref("");
+const errorTitle = ref("请输入接收人手机号");
+onBeforeMount(() => {
+  findpage(); //列表数据
+  if (auth("organizationlist.view")) {
+  }
+});
+function findpage() {
+
+  api.saleApi
+    .list({
+      pages: pageInfo.value.pageNum,
+      size: pageInfo.value.pageSize,
+      ...formInline,
+    })
+    .then((res: any) => {
+      if (res.code == 200) {
+        tableData.value = res.data.records;
+        pageInfo.value.pageNum = res.data.current;
+        pageInfo.value.pageSize = res.data.size;
+        pageInfo.value.total = res.data.total;
+      }
+    });
+}
+const columns = [
+  { prop: "name", label: "姓名", width: "120" },
+  {
+    prop: "mobile",
+    label: "手机号",
+    width: "200",
+  },
+  {
+    prop: "typeAttr",
+    label: "类型",
+    width: "120",
+    formatter: (row: any) => {
+      if (row.typeAttr == 5) {
+        return '团队'
+      } else if(row.typeAttr ==6) {
+        return "个代"
+      }else if(row.typeAttr ==7) {
+        return "渠道"
+      }else if(row.typeAttr ==8) {
+        return "电销组"
+      }else  {
+        return "其他"
+      }
+    }
+  },
+  {
+    prop: "deptName",
+    label: "所属机构",
+    width: "120",
+  },
+  {
+    prop: "inviteePerson",
+    label: "推荐上级",
+    width: "120",
+  },
+  { prop: "totalPremium", label: "累计出单保费", width: "150" },
+  { prop: "totalFee", label: "累计佣金", width: "200" },
+  { prop: "totalInvite", label: "累计邀请", width: "150" },
+  { prop: "joinTime", label: "加入时间", width: "180" },
+];
+
+function getList(item: PageInfo) {
+  pageInfo.value.pageNum = item.pageNum;
+  pageInfo.value.pageSize = item.pageSize;
+  findpage();
+}
+function tenantUpdate(item: any) {
+  router.push({
+    name: "OrganizationAdd",
+    query: {
+      id: item.id,
+    },
+  });
+}
+
+const selectionChange = (val: any) => {
+  sysUpAndDownList.value = val;
+};
+//禁用
+const disable = (value: string) => {
+  ElMessageBox.confirm(
+    "确定要禁用该业务员账号吗,禁用后该业务员账户不可登录App进行车险出单业务?",
+    "禁用租户",
+    {
+      confirmButtonText: "确定禁用",
+      type: "warning",
+      center: true,
+    }
+  )
+    .then(() => {
+      sysUpAndDown(0, [value]);
+    })
+    .catch(() => {});
+};
+//启用
+const enable = (value: string) => {
+  sysUpAndDown(1, [value]);
+};
+//批量删除
+const batchDel = () => {
+  ElMessageBox.confirm("机构删除后不可恢复,请谨慎操作", "确定删除机构?", {
+    confirmButtonText: "确定删除",
+    cancelButtonText: "取消",
+    type: "error",
+    center: true,
+  })
+    .then(() => {
+      api.tenantApi.sysDelete(sysUpAndDownList.value).then((res: any) => {
+        if (res.code == "200") {
+          ElMessage({
+            type: "success",
+            message: res.msg,
+            plain: true,
+          });
+          findpage();
+        }
+      });
+    })
+    .catch(() => {});
+};
+//批量禁用
+const batchdisable = () => {
+  ElMessageBox.confirm(
+    "确定要禁用该业务员账号吗,禁用后该业务员账户不可登录App进行车险出单业务?",
+    "禁用租户",
+    {
+      confirmButtonText: "确定禁用",
+      cancelButtonText: "取消",
+      type: "error",
+      center: true,
+    }
+  )
+    .then(async () => {
+      await sysUpAndDown(0, undefined);
+    })
+    .catch(() => {});
+};
+//批量启用
+const batchenable = async () => {
+  await sysUpAndDown(1, undefined);
+};
+const roleQuery = () => {
+  findpage();
+};
+const sysUpAndDown = async (type: number, list?: string[]) => {
+  await api.tenantApi
+    .sysUpAndDown({ ids: list ? list : sysUpAndDownList.value, opType: type })
+    .then((res: any) => {
+      if (res.code == "200") {
+        findpage();
+        ElMessage({
+          type: "success",
+          message: res.msg,
+          plain: true,
+        });
+      }
+    });
+};
+//添加子机构
+const subOrganizationAdd = (code: string) => {
+  router.push({
+    name: "OrganizationDeptAdd",
+    query: {
+      code: code,
+    },
+  });
+};
+const resetForm = () => {
+  (formInline.name = ""),
+    (formInline.mobile = ""),
+    (formInline.inviteePerson = ""),
+    (formInline.joinTimeStart = ""),
+    (formInline.joinTimeEnd = ""),
+    (provinceoptions.value = []);
+  findpage();
+};
+//清退
+const backtopEmits = (row: any) => {
+
+  deleteData.value.id = row.id;
+  deleteData.value.name = row.name;
+  dialogVisible.value = true;
+};
+//清退确认
+const deleteUser = () => {
+  if (deleteData.value.type == 0) {
+    ElMessage({
+      message: "请选择清退方式",
+      type: "error",
+    });
+    return;
+  }else{
+     api.saleApi
+    .deleteUser({
+      ids: deleteData.value.id.split(","),
+      // ...deleteData.value
+      type: deleteData.value.type,
+      mobile: deleteData.value.mobile,
+    })
+    .then((res: any) => {
+      if (res.code == 200) {
+        ElMessage({
+          message: "清退成功",
+          type: "success",
+        });
+          dialogVisible.value = false;
+        findpage();
+      }else{
+        errorTitle.value = res.msg
+      }
+    });
+  }
+ 
+
+};
+
+//关系查询
+const look = (row: any) => {
+  currentName.value = row.name;
+
+  api.saleApi.getDistributionRelation({ id: row.id }).then((res) => {
+    relationTreeData.value = res.data;
+    isRelation.value = true;
+  });
+};
+
+const getRowClassName = ({ row }: { row: any }) => {
+  if (row.name == currentName.value) {
+
+    return "current-style";
+  } else {
+    return "";
+  }
+};
+watch(
+  joinTimeRange,
+  (newRange) => {
+    if (Array.isArray(newRange) && newRange.length === 2) {
+      formInline.joinTimeStart = dayjs(newRange[0]).format("YYYY-MM-DD");
+      formInline.joinTimeEnd = dayjs(newRange[1]).format("YYYY-MM-DD");
+    } else {
+      formInline.joinTimeStart = "";
+      formInline.joinTimeEnd = "";
+    }
+  },
+  { immediate: true }
+);
+</script>
+
+<style lang="scss" scoped>
+.demo-form-inline .el-input {
+  --el-input-width: 220px;
+}
+
+.demo-form-inline .el-select {
+  --el-select-width: 220px;
+}
+
+:deep(.el-dropdown-link) {
+  display: flex;
+  align-items: center;
+  margin-left: 15px;
+  color: var(--el-color-primary);
+  cursor: pointer;
+}
+
+.el-inputs {
+  width: 100% !important;
+}
+::v-deep.el-table td.el-table__cell div i {
+  display: none;
+}
+::v-deep.el-table .caret-wrapper {
+  display: none;
+}
+:deep(.current-style) {
+  color: #339dff;
+  position: relative;
+  padding-left: 20px;
+}
+</style>

+ 724 - 0
src/views/sale/review.vue

@@ -0,0 +1,724 @@
+<template>
+  <div class="order">
+    <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+      <el-tab-pane name="first">
+        <template #label> 待审核({{ total1 }}) </template>
+        <el-form label-width="100" label-position="left" :model="form">
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="姓名">
+                <el-input
+                  v-model="form.name"
+                  placeholder="请输入姓名查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="手机号">
+                <el-input
+                  v-model="form.mobile"
+                  placeholder="请输入手机号查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="推荐上级">
+                <el-input v-model="form.inviteePerson"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="申请时间">
+                <el-date-picker
+                  v-model="joinTimeRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="timeChange"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24" style="display: flex; justify-content: flex-end">
+              <el-button type="primary" @click="search">查询</el-button>
+              <el-button plain type="primary" @click="reset">重置</el-button>
+            </el-col>
+          </el-row>
+        </el-form>
+        <el-table :data="tableData1" style="margin-top: 24px" hight="500">
+          <el-table-column label="姓名" prop="name" width="200">
+          </el-table-column>
+          <el-table-column label="手机号" prop="mobile"></el-table-column>
+          <el-table-column label="类型" prop="typeAttr">
+            <template #default="scope">
+              {{
+                scope.row.typeAttr == "5"
+                  ? "团队"
+                  : scope.row.typeAttr == "6"
+                  ? "个代"
+                  : scope.row.typeAttr == "7"
+                  ? "渠道"
+                  : scope.row.typeAttr== "8"
+                  ? "电销组"
+                  : "--"
+              }}
+            </template>
+          </el-table-column>
+          <el-table-column label="所属机构" prop="deptName"></el-table-column>
+          <el-table-column
+            label="推荐上级"
+            prop="referrerName"
+          ></el-table-column>
+          <el-table-column label="申请时间" prop="applyTime"></el-table-column>
+          <el-table-column label="操作" fixed="right" width="200">
+            <template #default="scope">
+              <!-- <el-button link type="primary">审核通过</el-button> -->
+              <el-popconfirm
+                title="确定审核通过?"
+                :hide-icon="true"
+                @confirm="review(scope.row)"
+              >
+                <template #reference>
+                  <el-button type="text">审核通过</el-button>
+                </template>
+              </el-popconfirm>
+              <!-- <el-button link type="primary" @click="handleEdit(scope.row)"
+                >驳回</el-button
+              > -->
+              <el-popconfirm
+                width="220"
+                title="审核驳回"
+                @confirm="reviewReject(scope.row)"
+              >
+                <template #reference>
+                  <el-button type="text">驳回</el-button>
+                </template>
+                <template #default="{}"> </template>
+                <template #actions="{ confirm, cancel }">
+                  <el-input
+                    v-model="remark"
+                    style="width: 100%; margin: 10px 0"
+                    :rows="2"
+                    type="textarea"
+                    placeholder="请输入驳回原因"
+                  />
+                  <el-button size="small" @click="cancel">取消</el-button>
+                  <el-button type="danger" size="small" @click="confirm">
+                    确认驳回
+                  </el-button>
+                </template>
+              </el-popconfirm>
+            </template>
+          </el-table-column>
+        </el-table>
+        <div class="pagination">
+          <el-pagination
+            v-model:current-page="pages"
+            v-model:page-size="size"
+            :page-sizes="[10, 20, 30, 40]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total1"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          ></el-pagination>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane name="second">
+        <template #label> 审核通过({{ total2 }}) </template>
+        <el-form label-width="100" label-position="left" :model="form">
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="姓名">
+                <el-input
+                  v-model="form.name"
+                  placeholder="请输入姓名查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="手机号">
+                <el-input
+                  v-model="form.mobile"
+                  placeholder="请输入手机号查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="推荐上级">
+                <el-input v-model="form.inviteePerson"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="申请时间">
+                <el-date-picker
+                  v-model="joinTimeRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="timeChange"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="审核人">
+                <el-input v-model="form.auditPerson"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="审核时间">
+                <el-date-picker
+                  v-model="reviewTimeRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="timeChange"
+                />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="24" style="display: flex; justify-content: flex-end">
+              <el-button type="primary" @click="search">查询</el-button>
+              <el-button plain type="primary" @click="reset">重置</el-button>
+            </el-col>
+          </el-row>
+        </el-form>
+        <el-table :data="tableData2" style="margin-top: 24px" hight="500">
+          <el-table-column label="审核人" prop="auditPerson" width="200">
+          </el-table-column>
+          <el-table-column label="审核时间" prop="applyTime"></el-table-column>
+          <el-table-column label="姓名" prop="name" width="200">
+          </el-table-column>
+          <el-table-column label="手机号" prop="mobile"></el-table-column>
+          <el-table-column label="类型" prop="typeAttr">
+            <template #default="scope">
+              {{
+                scope.row.typeAttr == "5"
+                  ? "团队"
+                  : scope.row.typeAttr == "6"
+                  ? "个代"
+                  : scope.row.typeAttr == "7"
+                  ? "渠道"
+                  : scope.row.typeAttr== "8"
+                  ? "电销组"
+                  : "--"
+              }}
+            </template>
+          </el-table-column>
+          <el-table-column label="所属机构" prop="deptName"></el-table-column>
+          <el-table-column
+            label="推荐上级"
+            prop="referrerName"
+          ></el-table-column>
+          <el-table-column label="申请时间" prop="applyTime"></el-table-column>
+        </el-table>
+        <div class="pagination">
+          <el-pagination
+            v-model:current-page="pages"
+            v-model:page-size="size"
+            :page-sizes="[10, 20, 30, 40]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total2"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          ></el-pagination>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane name="third">
+        <template #label> 审核不通过({{ total3 }}) </template>
+        <el-form label-width="100" label-position="left" :model="form">
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="姓名">
+                <el-input
+                  v-model="form.name"
+                  placeholder="请输入姓名查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="手机号">
+                <el-input
+                  v-model="form.mobile"
+                  placeholder="请输入手机号查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="推荐上级">
+                <el-input v-model="form.inviteePerson"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="申请时间">
+                <el-date-picker
+                  v-model="joinTimeRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="timeChange"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="审核人">
+                <el-input v-model="form.auditPerson"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="审核时间">
+                <el-date-picker
+                  v-model="form.recordingTime"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="timeChange"
+                />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="24" style="display: flex; justify-content: flex-end">
+              <el-button type="primary" @click="search">查询</el-button>
+              <el-button plain type="primary" @click="reset">重置</el-button>
+            </el-col>
+          </el-row>
+        </el-form>
+        <el-table :data="tableData3" style="margin-top: 24px" height="500">
+          <el-table-column
+            label="审核人"
+            prop="auditPerson"
+            width="120"
+          ></el-table-column>
+          <el-table-column
+            label="审核时间"
+            prop="auditTime"
+            width="220"
+          ></el-table-column>
+          <el-table-column
+            label="驳回原因"
+            prop="remark"
+            width=""
+            show-overflow-tooltip
+          ></el-table-column>
+          <el-table-column
+            label="姓名"
+            prop="name"
+            width="120"
+          ></el-table-column>
+          <el-table-column
+            label="手机号"
+            prop="mobile"
+            width="120"
+          ></el-table-column>
+          <el-table-column
+            label="类型"
+            prop="typeAttr"
+            width="120"
+          >
+        <template #default="scope">
+              {{
+                scope.row.typeAttr == "5"
+                  ? "团队"
+                  : scope.row.typeAttr == "6"
+                  ? "个代"
+                  : scope.row.typeAttr == "7"
+                  ? "渠道"
+                  : scope.row.typeAttr== "8"
+                  ? "电销组"
+                  : "--"
+              }}
+            </template>
+          </el-table-column>
+          <el-table-column
+            label="所属机构"
+            prop="deptName"
+            width="120"
+          ></el-table-column>
+          <el-table-column
+            label="推荐上级"
+            prop="referrerName"
+            width="120"
+          ></el-table-column>
+          <el-table-column
+            label="申请时间"
+            prop="applyTime"
+            width="220"
+          ></el-table-column>
+        </el-table>
+        <div class="pagination">
+          <el-pagination
+            v-model:current-page="pages"
+            v-model:page-size="size"
+            :page-sizes="[10, 20, 30, 40]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total3"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          ></el-pagination>
+        </div>
+      </el-tab-pane>
+      <el-tab-pane name="fourthly">
+        <template #label> 自动审核{{ total4 }}) </template>
+        <el-form label-width="100" label-position="left" :model="form">
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="姓名">
+                <el-input
+                  v-model="form.name"
+                  placeholder="请输入姓名查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="手机号">
+                <el-input
+                  v-model="form.mobile"
+                  placeholder="请输入手机号查找"
+                ></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="推荐上级">
+                <el-input v-model="form.inviteePerson"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="申请时间">
+                <el-date-picker
+                  v-model="joinTimeRange"
+                  type="daterange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                  format="YYYY-MM-DD"
+                  value-format="YYYY-MM-DD"
+                  @change="timeChange"
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="24" style="display: flex; justify-content: flex-end">
+              <el-button type="primary" @click="search">查询</el-button>
+              <el-button plain type="primary" @click="reset">重置</el-button>
+            </el-col>
+          </el-row>
+        </el-form>
+        <el-table :data="tableData4" style="margin-top: 24px" height="500">
+          <el-table-column label="审核人" prop="auditPerson" width="200">
+          </el-table-column>
+          <el-table-column label="审核时间" prop="auditTime"></el-table-column>
+          <el-table-column label="姓名" prop="name" width="200">
+          </el-table-column>
+          <el-table-column label="手机号" prop="mobile"></el-table-column>
+          <el-table-column label="类型" prop="typeAttr">
+            <template #default="scope">
+              {{
+                scope.row.typeAttr == "5"
+                  ? "团队"
+                  : scope.row.typeAttr == "6"
+                  ? "个代"
+                  : scope.row.typeAttr == "7"
+                  ? "渠道"
+                  : scope.row.typeAttr== "8"
+                  ? "电销组"
+                  : "--"
+              }}
+            </template>
+          </el-table-column>
+          <el-table-column label="所属机构" prop="deptName"></el-table-column>
+          <el-table-column
+            label="推荐上级"
+            prop="referrerName"
+          ></el-table-column>
+          <el-table-column label="申请时间" prop="applyTime"></el-table-column>
+        </el-table>
+        <div class="pagination">
+          <el-pagination
+            v-model:current-page="pages"
+            v-model:page-size="size"
+            :page-sizes="[10, 20, 30, 40]"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="total4"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          ></el-pagination>
+        </div>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from "vue-router";
+import { ref, reactive, watch, toRaw, computed, onMounted } from "vue";
+import api from "@/api";
+import { ElMessage, ElMessageBox } from "element-plus";
+import dayjs from "dayjs";
+const joinTimeRange = ref([]);
+const reviewTimeRange = ref([]);
+const myRouter = useRouter();
+const remark = ref("");
+let activeName = ref("first");
+let type = ref("first");
+const handleClick = (e) => {
+  pages.value = 1;
+  size.value = 10;
+  form.value = {};
+  type.value = e.props.name;
+  switch (e.props.name) {
+    case "first":
+      initData(0);
+      break;
+    case "second":
+      initData(1);
+      break;
+    case "third":
+      initData(2);
+      break;
+    case "fourthly":
+      initData(3);
+      break;
+  }
+};
+
+let form = ref({});
+const tableProps = ref({
+  value: "id",
+  label: "name",
+  children: "children",
+  checkStrictly: true,
+});
+let option = ref([]);
+const timeChange = (val) => {};
+// 搜索
+const search = () => {
+  switch (type.value) {
+    case "first":
+      initData(0);
+      break;
+    case "second":
+      initData(1);
+      break;
+    case "third":
+      initData(2);
+      break;
+    case "fourthly":
+      initData(3);
+      break;
+  }
+};
+const reset = () => {
+  form.value = {};
+  initData(type.value);
+};
+
+// 列表
+let tableData1 = ref([]);
+let tableData2 = ref([]);
+let tableData3 = ref([]);
+let tableData4 = ref([]);
+const handleEdit = (row) => {
+  myRouter.push({
+    path: "/additionalOrder/additionalOrderIndex",
+    query: {
+      id: row.id,
+    },
+  });
+};
+const handleDel = (row) => {
+  ElMessageBox({
+    title: "提示",
+    message: "确认要删除当前数据吗?",
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning",
+    center: true,
+  }).then((action) => {
+    if (action === "confirm") {
+      api.additionalApi.deleteById(row.id).then((res: any) => {
+        if (res.code == 200) {
+          ElMessage({
+            message: res.msg,
+            type: "success",
+          });
+          switch (type.value) {
+            case "first":
+              initData(0);
+              break;
+            case "second":
+              initData(1);
+              break;
+            case "third":
+              initData(2);
+              break;
+            case "fourthly":
+              initData(3);
+              break;
+          }
+        } else {
+          ElMessage({
+            message: res.msg,
+            type: "warning",
+          });
+        }
+      });
+    }
+  });
+};
+const review = (row: any) => {
+  console.log(row);
+  api.saleApi.distributorAudit({ id: row.id, auditStatus: 1 }).then((res) => {
+    reset();
+    initData(0);
+    initData(1);
+    initData(2);
+    initData(3);
+  });
+};
+
+const reviewReject = (row: any) => {
+  console.log(row);
+  if (!remark.value) {
+    ElMessage({
+      message: "请填写审核拒绝原因",
+      type: "warning",
+    });
+  } else {
+    api.saleApi
+      .distributorAudit({ id: row.id, auditStatus: 2, remark: remark.value })
+      .then((res) => {
+        reset();
+        initData(0);
+        initData(1);
+        initData(2);
+        initData(3);
+      });
+  }
+};
+
+// 分页
+let pages = ref(1);
+let size = ref(10);
+let total1 = ref(0);
+let total2 = ref(0);
+let total3 = ref(0);
+let total4 = ref(0);
+const handleSizeChange = (size) => {
+  size.value = size;
+  initData(type.value);
+};
+const handleCurrentChange = (page) => {
+  pages.value = page;
+  initData(type.value);
+};
+
+const initData = (type) => {
+  api.saleApi
+    .auditList({
+      pages: pages.value,
+      size: size.value,
+      isTemporarily: 0,
+      auditStatus: type,
+      orderNo: form.value.orderNo,
+      salesman: form.value.salesman,
+      salesmanCompany: form.value.salesmanCompany,
+      companyName: form.value.companyName,
+      licenseNo: form.value.licenseNo,
+      recordingTime: form.value.recordingTime,
+    })
+    .then((res: any) => {
+      if (res.code == 200) {
+        switch (type) {
+          case 0:
+            tableData1.value = res.data.records;
+            total1.value = res.data.total;
+            break;
+          case 1:
+            tableData2.value = res.data.records;
+            total2.value = res.data.total;
+            break;
+          case 2:
+            tableData3.value = res.data.records;
+            total3.value = res.data.total;
+            break;
+          case 3:
+            tableData4.value = res.data.records;
+            total4.value = res.data.total;
+            break;
+        }
+      } else {
+        ElMessage({
+          message: res.msg,
+          type: "warning",
+        });
+      }
+    });
+};
+
+// 获取保险公司
+let companyList = ref([]);
+const initCompany = () => {
+  api.insuranceApi.leftList("").then((res) => {
+    if (res.code == 200) {
+      companyList.value = res.data;
+    }
+  });
+};
+
+onMounted(() => {
+  initData(0);
+  initData(1);
+  initData(2);
+  initData(3);
+  // initCompany();
+});
+watch(
+  [joinTimeRange, reviewTimeRange],
+  ([newJoinRange, newReviewRange]) => {
+    if (Array.isArray(newJoinRange) && newJoinRange.length === 2) {
+      form.applyTimeStart = dayjs(newJoinRange[0]).format("YYYY-MM-DD");
+      form.applyTimeEnd = dayjs(newJoinRange[1]).format("YYYY-MM-DD");
+    } else {
+      form.applyTimeStart = "";
+      form.applyTimeEnd = "";
+    }
+
+    // 处理 reviewTimeRange
+    if (Array.isArray(newReviewRange) && newReviewRange.length === 2) {
+      form.auditTimeStart = dayjs(newReviewRange[0]).format("YYYY-MM-DD");
+      form.auditTimeEnd = dayjs(newReviewRange[1]).format("YYYY-MM-DD");
+    } else {
+      form.auditTimeStart = "";
+      form.auditTimeEnd = "";
+    }
+  },
+  { immediate: true }
+);
+</script>
+
+<style scoped lang="scss">
+.order {
+  width: 100%;
+  height: calc(100vh - 120px);
+  background: #ffffff;
+  padding: 24px;
+  overflow-y: auto;
+  .pagination {
+    margin-top: 24px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+  }
+}
+</style>

+ 615 - 0
src/views/sale/settings.vue

@@ -0,0 +1,615 @@
+<template>
+  <div>
+    <PageMain class="vh100" style="position: relative">
+      <p><span></span>分销设置</p>
+
+      <ElForm
+        ref="formRef"
+        :inline="false"
+        :model="form"
+        label-width="150px"
+        class="demo-form-inline"
+        :rules="rules"
+      >
+        <!-- 分销模式选择 -->
+        <ElFormItem
+          label="分销模式"
+          prop="distributionMode"
+          style="margin-bottom: 24px"
+        >
+          <div
+            v-for="item in type"
+            :key="item.name"
+            class="type-box"
+            :class="currentIndex == item.distributionMode ? 'active ' : ''"
+            @click="typeClick(item)"
+          >
+            <img
+              src="@/assets/images/check.png"
+              alt="选中状态"
+              v-if="currentIndex == item.distributionMode"
+            />
+            <h2 style="margin: 0">{{ item.name }}</h2>
+            {{ item.describe }}
+          </div>
+        </ElFormItem>
+        
+        <!-- 佣金比例设置 - 拆分为独立的FormItem以显示各自的校验提示 -->
+        <ElFormItem label="佣金比例" style="margin-bottom: 0;">
+          <!-- 一级佣金比例 -->
+          <ElFormItem
+            prop="commissionRatioLevel1"
+            style="margin-right: 24px; margin-bottom: 16px;"
+          >
+            <div
+              style="
+                width: 256px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+              "
+            >
+              <span style="color: red">*</span> 一级
+              <el-input
+                v-model="form.commissionRatioLevel1"
+                style="width: 206px"
+                type="number"
+                step="0.01"
+                @input="handleCommissionInput('commissionRatioLevel1', $event)"
+              >
+                <template #append>%</template>
+              </el-input>
+            </div>
+          </ElFormItem>
+          
+          <!-- 二级佣金比例 -->
+          <ElFormItem
+            prop="commissionRatioLevel2"
+            style="margin-right: 24px; margin-bottom: 16px;"
+            v-if="currentIndex >= 2"
+          >
+            <div
+              style="
+                width: 256px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+              "
+            >
+              <span style="color: red">*</span> 二级
+              <el-input
+                v-model="form.commissionRatioLevel2"
+                style="width: 206px"
+                type="number"
+                step="0.01"
+                @input="handleCommissionInput('commissionRatioLevel2', $event)"
+              >
+                <template #append>%</template>
+              </el-input>
+            </div>
+          </ElFormItem>
+          
+          <!-- 三级佣金比例 -->
+          <ElFormItem
+            prop="commissionRatioLevel3"
+            style="margin-right: 24px; margin-bottom: 16px;"
+            v-if="currentIndex >= 3"
+          >
+            <div
+              style="
+                width: 256px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+              "
+            >
+              <span style="color: red">*</span> 三级
+              <el-input
+                v-model="form.commissionRatioLevel3"
+                style="width: 206px"
+                type="number"
+                step="0.01"
+                @input="handleCommissionInput('commissionRatioLevel3', $event)"
+              >
+                <template #append>%</template>
+              </el-input>
+            </div>
+          </ElFormItem>
+        </ElFormItem>
+
+        <!-- 分销员招募 -->
+        <ElFormItem
+          label="分销员招募"
+          prop="recruitmentMethod"
+          style="align-items: center"
+        >
+          <el-radio-group v-model="form.recruitmentMethod" class="ml-4">
+            <el-radio label="1" size="large">手动申请</el-radio>
+            <el-radio label="2" size="large">加入组织自动成为分销员</el-radio>
+          </el-radio-group>
+        </ElFormItem>
+        
+        <!-- 不参与分销的机构 -->
+        <ElFormItem
+          label="不参与分销的机构"
+          prop="excludeDeptScope"
+          style="align-items: center"
+        >
+          <el-radio-group v-model="form.excludeDeptScope" class="ml-4">
+            <el-radio label="1" size="large">全部机构</el-radio>
+            <el-radio label="2" size="large">部门机构 </el-radio>
+          </el-radio-group>
+          <div
+            style="
+              color: #0083ff;
+              margin-left: 10px;
+              font-size: 14px;
+              cursor: pointer;
+            "
+            v-if="form.excludeDeptScope == '2'"
+            @click="showDeptDrawer = true"
+          >
+            +选择机构
+          </div>
+        </ElFormItem>
+        
+        <!-- 已选机构列表 -->
+        <el-table
+          :data="selectData"
+          style="width: 611px; margin-left: 130px; margin-top: 16px;"
+          header-cell-class-name="custom-header-cell"
+          v-if="form.excludeDeptScope == '2'"
+        >
+          <el-table-column
+            prop="name"
+            label="机构"
+            width="517"
+            align="center"
+          />
+          <el-table-column label="操作" width="">
+            <template #default="scope">
+              <span
+                @click="removeDept(scope.row.id)"
+                style="
+                  font-size: 14px;
+                  color: #0083ff;
+                  cursor: pointer;
+                  outline: none;
+                "
+                >删除</span
+              >
+            </template>
+          </el-table-column>
+        </el-table>
+      </ElForm>
+
+      <!-- 保存按钮 -->
+      <div class="btn">
+        <el-button type="primary" @click="handleSubmit" v-if="form.systemCode">修改</el-button>
+        <el-button type="primary" @click="handleUpdate" v-else>保存</el-button>
+      </div>
+      
+      <!-- 机构选择抽屉 -->
+      <Drawer
+        :Drawer="showDeptDrawer"
+        :deptlist="deptTreeData"
+        :checkedKeys="defaultCheckedIds"
+        :checkedIds="defaultCheckedIds"
+        @cancel="handleDrawerCancel"
+        @save="handleDrawerSave"
+        @DrawerOpen="handleDrawerOpen"
+        @query="handleDrawerQuery"
+      />
+    </PageMain>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, onMounted } from "vue";
+import api from "@/api";
+import Drawer from "./components/drawer.vue";
+import { TreeOption, FormRules, FormInstance, ElMessage } from "element-plus";
+
+// 当前选中的分销模式索引
+const currentIndex = ref(3);
+// 表单引用
+const formRef = ref<FormInstance>();
+
+// 分销模式选项
+const type = ref([
+  {
+    name: "一级分销",
+    describe: "仅分销员直接推荐的业务员出单后,分销员可获佣金",
+    distributionMode: 1,
+  },
+  {
+    name: "二级分销",
+    describe:
+      "业务员可从直接推荐的客户(一级)及一级客户推荐的客户(二级)的保费中获得佣金",
+    distributionMode: 2,
+  },
+  {
+    name: "三级分销",
+    describe:
+      "向下延伸三级关系链,每一层的业务员可从这三层关系链的保费中获佣金",
+    distributionMode: 3,
+  },
+]);
+
+// 表单验证规则
+const rules = reactive<FormRules>({
+  distributionMode: [
+    { required: true, message: "请选择分销模式", trigger: "change" },
+  ],
+  // 一级佣金比例验证规则
+  commissionRatioLevel1: [
+    { required: true, message: "请输入一级佣金比例", trigger: "blur" },
+    { type: "number", message: "佣金比例必须为数字", trigger: "blur" },
+    {
+      validator: (rule, value, callback) => {
+        if (value < 0 || value > 100) {
+          callback(new Error("佣金比例需在 0-100 之间"));
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur",
+    },
+  ],
+  // 二级佣金比例验证规则
+  commissionRatioLevel2: [
+    {
+      required: currentIndex.value >= 2,
+      message: "请输入二级佣金比例",
+      trigger: "blur",
+    },
+    { 
+      type: "number", 
+      message: "佣金比例必须为数字", 
+      trigger: "blur",
+      required: false,
+    },
+    {
+      validator: (rule, value, callback) => {
+        if (value !== null && value !== undefined) {
+          if (value < 0 || value > 100) {
+            callback(new Error("佣金比例需在 0-100 之间"));
+          } else {
+            callback();
+          }
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur",
+    },
+  ],
+  // 三级佣金比例验证规则
+  commissionRatioLevel3: [
+    {
+      required: currentIndex.value >= 3,
+      message: "请输入三级佣金比例",
+      trigger: "blur",
+    },
+    { 
+      type: "number", 
+      message: "佣金比例必须为数字", 
+      trigger: "blur",
+      required: false,
+    },
+    {
+      validator: (rule, value, callback) => {
+        if (value !== null && value !== undefined) {
+          if (value < 0 || value > 100) {
+            callback(new Error("佣金比例需在 0-100 之间"));
+          } else {
+            callback();
+          }
+        } else {
+          callback();
+        }
+      },
+      trigger: "blur",
+    },
+  ],
+  recruitmentMethod: [
+    { required: true, message: "请选择分销员招募方式", trigger: "change" },
+  ],
+  excludeDeptScope: [
+    { required: true, message: "请选择是否排除机构", trigger: "change" },
+  ],
+});
+
+// 表单数据结构
+interface FormState {
+  distributionMode: string;
+  commissionRatioLevel1: number | null;
+  commissionRatioLevel2: number | null;
+  commissionRatioLevel3: number | null;
+  recruitmentMethod: string;
+  excludeDeptScope: string;
+  [key: string]: any;
+}
+
+// 表单数据
+const form = reactive<FormState>({
+  distributionMode: "",
+  commissionRatioLevel1: null,
+  commissionRatioLevel2: null,
+  commissionRatioLevel3: null,
+  recruitmentMethod: "",
+  excludeDeptScope: "",
+});
+
+// 机构选择抽屉相关
+const showDeptDrawer = ref(false);
+const defaultCheckedIds = ref<string[]>([]);
+const deptTreeData = ref<TreeOption[]>([]);
+const selectData = ref<any[]>([]);
+
+// 选择分销模式
+const typeClick = (item: any) => {
+  form.distributionMode = item.distributionMode;
+  currentIndex.value = item.distributionMode;
+
+  // 切换分销模式时,重置非当前层级的佣金比例
+  if (currentIndex.value < 2) {
+    form.commissionRatioLevel2 = null;
+    form.commissionRatioLevel3 = null;
+  }
+  if (currentIndex.value < 3) {
+    form.commissionRatioLevel3 = null;
+  }
+
+  // 重置表单校验状态
+  if (formRef.value) {
+    formRef.value.clearValidate(["commissionRatioLevel2", "commissionRatioLevel3"]);
+  }
+};
+
+// 页面加载时获取数据
+onMounted(() => {
+  agreementAuth();
+  getDetail()
+});
+
+// 获取协议授权机构
+const agreementAuth = () => {
+  api.saleApi.agreementdeptTree({ name: "", system: "" }).then((res: any) => {
+    if (res.code == "200") {
+      deptTreeData.value = res.data;
+    }
+  });
+};
+
+// 处理抽屉打开
+const handleDrawerOpen = async () => {
+  try {
+    // 可以在这里加载最新的机构数据
+  } catch (error) {}
+};
+
+// 取消抽屉
+const handleDrawerCancel = () => {
+  showDeptDrawer.value = false;
+};
+
+// 保存选中的机构
+const handleDrawerSave = (selectedIds: string[]) => {
+  showDeptDrawer.value = false;
+  selectData.value = [];
+  
+  // 从机构树中查找选中的节点
+  const findNodesById = (nodes: TreeOption[], ids: string[]): TreeOption[] => {
+    const result: TreeOption[] = [];
+    for (const node of nodes) {
+      if (ids.includes(node.id)) {
+        result.push(node);
+      }
+      if (node.children && node.children.length > 0) {
+        const childMatches = findNodesById(node.children, ids);
+        result.push(...childMatches);
+      }
+    }
+    return result;
+  };
+
+  selectData.value = findNodesById(deptTreeData.value, selectedIds);
+};
+
+// 搜索机构
+const handleDrawerQuery = async (searchName: string) => {
+  
+  try {
+    // const filteredData = deptTreeData.value.filter(
+    //   // (node:TreeOption) => node.name.includes(searchName)
+    //    (node: TreeOption) => 
+    // node.name.toLowerCase().includes(searchName.toLowerCase())
+    // );
+    // deptTreeData.value = filteredData;
+    searchName?deptTreeData.value = filterTree(deptTreeData.value, searchName):agreementAuth()
+    
+  } catch (error) {
+    console.error("筛选机构失败:", error);
+  }
+  
+};
+// 递归筛选树形结构
+const filterTree = (tree: TreeOption[], searchName: string) => {
+  return tree
+    .map(node => {
+      // 复制节点并递归筛选子节点
+      const newNode = { ...node };
+      if (node.children && node.children.length) {
+        newNode.children = filterTree(node.children, searchName);
+      }
+      return newNode;
+    })
+    .filter(node => {
+      // 节点本身包含搜索词,或者有符合条件的子节点
+      const nodeMatches = node.name.toLowerCase().includes(searchName.toLowerCase());
+      const hasMatchingChildren = node.children && node.children.length > 0;
+      return nodeMatches || hasMatchingChildren;
+    });
+};
+
+// 移除已选机构
+const removeDept = async (id: string) => {
+  selectData.value = selectData.value.filter((item) => item.id !== id);
+};
+
+// 处理佣金比例输入
+const handleCommissionInput = (field: string, value: any) => {
+  if (value === "" || value === null || value === undefined) {
+    form[field] = null;
+    return;
+  }
+
+  const numValue = Number(value);
+
+  if (isNaN(numValue)) {
+    form[field] = null;
+    return;
+  }
+
+  // 保留两位小数
+  form[field] = Math.round(numValue * 100) / 100;
+
+  // 限制在0-100之间
+  if (form[field] > 100) {
+    form[field] = 100;
+  }
+  if (form[field] < 0) {
+    form[field] = 0;
+  }
+};
+
+// 提交表单
+const handleSubmit = async () => {
+  if (!formRef.value) return;
+
+  try {
+    await formRef.value.validate();
+
+    const formData = {
+      ...form,
+      commissionRatioLevel1: form.commissionRatioLevel1 || 0,
+      commissionRatioLevel2: form.commissionRatioLevel2 || 0,
+      commissionRatioLevel3: form.commissionRatioLevel3 || 0,
+      excludeDeptIds:form.excludeDeptScope=='1'?[]: selectData.value.map((item) => item.id),
+       systemCode: localStorage.getItem('login_system')
+    };
+    await api.saleApi.saveSetting(formData);
+
+    ElMessage.success('保存成功');
+  } catch (error) {
+  }
+};
+const handleUpdate=async()=>{ 
+  if (!formRef.value) return;
+
+  try {
+    await formRef.value.validate();
+
+    const formData = {
+      ...form,
+      commissionRatioLevel1: form.commissionRatioLevel1 || 0,
+      commissionRatioLevel2: form.commissionRatioLevel2 || 0,
+      commissionRatioLevel3: form.commissionRatioLevel3 || 0,
+      excludeDeptIds:form.excludeDeptScope=='1'?[]: selectData.value.map((item) => item.id),
+       systemCode: localStorage.getItem('login_system')
+    };
+    await api.saleApi.update(formData);
+
+    ElMessage.success('保存成功');
+  } catch (error) {
+  }
+}
+
+//获取详情回显
+const getDetail = async () => { 
+  const res = await api.saleApi.details();
+  // form.value = res.data;
+  Object.assign(form, res.data);
+  currentIndex.value = form.distributionMode
+  selectData.value = form.excludeDept
+};
+</script>
+
+<style scoped lang="scss">
+p {
+  margin: 0;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid #eeeeee;
+  margin-top: -1.25rem;
+  margin-bottom: 33px;
+  
+  span {
+    display: inline-block;
+    width: 4px;
+    height: 18px;
+    background: #0083ff;
+    border-radius: 6px;
+    margin-right: 8px;
+  }
+}
+
+.type-box {
+  width: 256px;
+  height: 124px;
+  background: #ffffff;
+  border: 1px solid #e5e5e5;
+  font-size: 14px;
+  line-height: 22px;
+  color: #999999;
+  padding: 16px;
+  margin-right: 24px;
+  border-radius: 2px;
+  cursor: pointer;
+  display: inline-block;
+  vertical-align: top;
+  
+  h2 {
+    font-size: 14px;
+    font-weight: 600;
+    margin-bottom: 4px;
+    color: #333;
+  }
+}
+
+.type-box.active {
+  border: 1px solid #0083ff;
+  position: relative;
+  
+  img {
+    width: 24px;
+    height: 24px;
+    position: absolute;
+    top: 8px;
+    right: 8px;
+  }
+}
+
+.btn {
+  width: 98%;
+  height: 64px;
+  position: absolute;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-top: 1px solid #eeeeee;
+}
+
+::v-deep .custom-header-cell {
+  background-color: #f7f7f9 !important;
+}
+
+/* 调整表单验证提示的样式 */
+::v-deep .el-form-item__error {
+  padding-top: 4px;
+  font-size: 12px;
+}
+</style>