19 Angajamente becf64faa1 ... 3295bbd75a

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  xgj 3295bbd75a fix 2 luni în urmă
  xgj 234feec192 fix 2 luni în urmă
  xgj 4e05013821 fix 2 luni în urmă
  xgj ce8ddff5e0 fix 2 luni în urmă
  xgj 82087e2171 fix 2 luni în urmă
  xgj 3461d57f1c fix 2 luni în urmă
  xgj 73026a62aa fix 2 luni în urmă
  xgj a180f5168c fx 2 luni în urmă
  xgj f1d08a715c fix 2 luni în urmă
  xgj 3f7a5842f3 fix 2 luni în urmă
  xgj aaf7321b38 fix 2 luni în urmă
  xgj 8d0b9ed51d fix 2 luni în urmă
  xgj 3006e713e5 fix 2 luni în urmă
  xgj 5eb7c285c4 fix 2 luni în urmă
  xgj 982cb957bb fix 2 luni în urmă
  xgj 57f876b225 fix 3 luni în urmă
  xgj ae558c038e fix 3 luni în urmă
  xgj 337be554ab fix 3 luni în urmă
  xgj a67aa2865f [CLEAR CACHE] 3 luni în urmă
37 a modificat fișierele cu 1600 adăugiri și 102 ștergeri
  1. 2 12
      .drone.yml
  2. 1 2
      .env
  3. 21 0
      .env.development
  4. 8 1
      .env.production
  5. 3 30
      .eslintrc.cjs
  6. 2 2
      build/plugins.ts
  7. 8 1
      package.json
  8. 91 0
      pnpm-lock.yaml
  9. 3 3
      src/api/index.ts
  10. 17 4
      src/assets/json/authMenuList.json
  11. 1 1
      src/layouts/LayoutClassic/index.vue
  12. 1 1
      src/layouts/LayoutColumns/index.vue
  13. 1 1
      src/layouts/LayoutTransverse/index.vue
  14. 7 3
      src/layouts/LayoutVertical/index.vue
  15. 4 4
      src/layouts/components/Header/ToolBarRight.vue
  16. 9 1
      src/layouts/components/Header/components/InfoDialog.vue
  17. 85 7
      src/layouts/components/Header/components/PasswordDialog.vue
  18. 1 1
      src/layouts/index.vue
  19. 1 1
      src/layouts/indexAsync.vue
  20. 1 1
      src/stores/modules/user.ts
  21. 16 8
      src/utils/errorHandler.ts
  22. 51 0
      src/utils/index.ts
  23. 1 1
      src/views/directives/watermarkDirect/index.vue
  24. 4 0
      src/views/login/components/LoginForm.vue
  25. 0 1
      src/views/login/index.scss
  26. 1 1
      src/views/login/index.vue
  27. 5 1
      src/views/promotion/channel.vue
  28. 31 5
      src/views/promotion/components/DialogPromotionCode.vue
  29. 81 0
      src/views/promotion/components/DialogRecordTable.vue
  30. 155 0
      src/views/promotion/components/PerviewDetail.vue
  31. 227 0
      src/views/promotion/components/PreviewPromotionalInvoiceDetailDialog.vue
  32. 52 0
      src/views/promotion/components/QRCodeShare.vue
  33. 103 0
      src/views/promotion/components/SearchArchivesSelect.vue
  34. 39 0
      src/views/promotion/constant.ts
  35. 241 0
      src/views/promotion/invoice.vue
  36. 326 1
      src/views/promotion/order.vue
  37. 0 8
      vite.config.ts

+ 2 - 12
.drone.yml

@@ -16,7 +16,7 @@ steps:
         path: /cache
 
   - name: build-dev
-    image: docker.1ms.run/node:16.14.2
+    image: docker.1ms.run/node:18.12.1
     commands:
       - node --version
       - npm install pnpm@8.15.4 -g --registry https://registry.npmmirror.com/
@@ -29,7 +29,7 @@ steps:
       branch:
         - dev
   - name: build-prod
-    image: docker.1ms.run/node:16.14.2
+    image: docker.1ms.run/node:18.12.1
     commands:
       - node --version
       - npm install pnpm@8.15.4 -g --registry https://registry.npmmirror.com/
@@ -41,16 +41,6 @@ steps:
     when:
       branch:
         - master
-  - name: build-cqmz
-    image: docker.1ms.run/node:16.14.2
-    commands:
-      - node --version
-      - npm install pnpm@8.15.4 -g --registry https://registry.npmmirror.com/
-      - pnpm --version
-      - pnpm install --no-frozen-lockfile --registry https://registry.npmmirror.com/
-      - git diff --name-only HEAD^ HEAD | grep package-lock.json && pnpm install  --registry https://registry.npmmirror.com/ --no-frozen-lockfile
-      - "[ -d node_modules ] && echo 'deps install done' || pnpm install --registry https://registry.npmmirror.com/ --no-frozen-lockfile"
-      - pnpm run build:cqmz
   - name: rebuild-cache
     image: docker.1ms.run/drillster/drone-volume-cache
     settings:

+ 1 - 2
.env

@@ -1,6 +1,5 @@
 # title
-VITE_GLOB_APP_TITLE = Geeker Admin
-
+VITE_GLOB_APP_TITLE = 合作机构
 # 本地运行端口号
 VITE_PORT = 8848
 

+ 21 - 0
.env.development

@@ -17,7 +17,28 @@ VITE_PWA = false
 # 开发环境接口地址
 VITE_API_URL = /api
 
+
 # 开发环境跨域代理,支持配置多个
 VITE_PROXY = [["/api","https://mcapi.mdfitnesscao.com"]]
 # VITE_PROXY = [["/api","https://www.fastmock.site/mock/f81e8333c1a9276214bcdbc170d9e0a0"]]
 # VITE_PROXY = [["/api-easymock","https://mock.mengxuegu.com"],["/api-fastmock","https://www.fastmock.site"]]
+
+
+# 项目本地运行端口号
+VITE_PORT = 8848
+
+# 开发环境读取配置文件路径
+VITE_PUBLIC_PATH = /
+
+# 开发环境路由历史模式
+VITE_ROUTER_HISTORY = "hash"
+
+ENV = 'development'
+
+# 开发环境请求接口
+VITE_APP_H5_DOMAIN = 'https://cps.mdfitnesscao.com'
+VITE_APP_DOMAIN = 'https://mcapi.mdfitnesscao.com'
+VITE_APP_DOMAIN_API = 'https://api.mdfitnesscao.com'
+VITE_APP_CHANNEL_API = 'https://mcapi.mdfitnesscao.com'
+VITE_APP_CHANNEL_DOMAIN = 'https://channel.dev.mdfitnesscao.com'
+VITE_APP_OSS_DOMAIN = 'https://oss.mdfitnesscao.com'

+ 8 - 1
.env.production

@@ -22,4 +22,11 @@ VITE_DROP_CONSOLE = true
 VITE_PWA = true
 
 # 线上环境接口地址
-VITE_API_URL = "https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e"
+
+# 开发环境请求接口
+VITE_APP_H5_DOMAIN = 'https://cps.hhmdtech.com'
+VITE_APP_DOMAIN = 'https://mcapi.hhmdtech.com'
+VITE_APP_DOMAIN_API = 'https://api.hhmdtech.com'
+VITE_APP_CHANNEL_API = 'https://mcapi.hhmdtech.com'
+VITE_APP_CHANNEL_DOMAIN = 'https://channel.dev.hhmdtech.com'
+VITE_APP_OSS_DOMAIN = 'https://oss.hhmdtech.com'

+ 3 - 30
.eslintrc.cjs

@@ -27,35 +27,8 @@ module.exports = {
    * "error" 或 2  ==>  规则作为一个错误(代码不能执行,界面报错)
    */
   rules: {
-    // eslint (http://eslint.cn/docs/rules)
-    "no-var": "error", // 要求使用 let 或 const 而不是 var
-    "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
-    "prefer-const": "off", // 使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
-    "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们
-
-    // typeScript (https://typescript-eslint.io/rules)
-    "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量
-    "@typescript-eslint/no-empty-function": "error", // 禁止空函数
-    "@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore
-    "@typescript-eslint/ban-ts-comment": "error", // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
-    "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长
-    "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间
-    "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
-    "@typescript-eslint/ban-types": "off", // 禁止使用特定类型
-    "@typescript-eslint/no-var-requires": "off", // 允许使用 require() 函数导入模块
-    "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!)
-
-    // vue (https://eslint.vuejs.org/rules)
-    "vue/script-setup-uses-vars": "error", // 防止<script setup>使用的变量<template>被标记为未使用,此规则仅在启用该 no-unused-vars 规则时有效
-    "vue/v-slot-style": "error", // 强制执行 v-slot 指令样式
-    "vue/no-mutating-props": "error", // 不允许改变组件 prop
-    "vue/custom-event-name-casing": "error", // 为自定义事件名称强制使用特定大小写
-    "vue/html-closing-bracket-newline": "error", // 在标签的右括号之前要求或禁止换行
-    "vue/attribute-hyphenation": "error", // 对模板中的自定义组件强制执行属性命名样式:my-prop="prop"
-    "vue/attributes-order": "off", // vue api使用顺序,强制执行属性顺序
-    "vue/no-v-html": "off", // 禁止使用 v-html
-    "vue/require-default-prop": "off", // 此规则要求为每个 prop 为必填时,必须提供默认值
-    "vue/multi-word-component-names": "off", // 要求组件名称始终为 “-” 链接的单词
-    "vue/no-setup-props-destructure": "off" // 禁止解构 props 传递给 setup
+    // 将所有规则禁用
+    "no-unused-vars": "off",
+    "vue/no-unused-components": "off"
   }
 };

+ 2 - 2
build/plugins.ts

@@ -6,7 +6,7 @@ import { visualizer } from "rollup-plugin-visualizer";
 import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
 import vue from "@vitejs/plugin-vue";
 import vueJsx from "@vitejs/plugin-vue-jsx";
-import eslintPlugin from "vite-plugin-eslint";
+// import eslintPlugin from "vite-plugin-eslint";
 import viteCompression from "vite-plugin-compression";
 import vueSetupExtend from "unplugin-vue-setup-extend-plus/vite";
 import NextDevTools from "vite-plugin-vue-devtools";
@@ -25,7 +25,7 @@ export const createVitePlugins = (viteEnv: ViteEnv): (PluginOption | PluginOptio
     // devTools
     VITE_DEVTOOLS && NextDevTools({ launchEditor: "code" }),
     // esLint 报错信息显示在浏览器界面上
-    eslintPlugin(),
+    // eslintPlugin(),
     // name 可以写在 script 标签上
     vueSetupExtend({}),
     // 创建打包压缩配置

+ 8 - 1
package.json

@@ -18,10 +18,16 @@
   "bugs": {
     "url": "https://github.com/HalseySpicy/Geeker-Admin/issues"
   },
+  "eslintConfig": {
+    "rules": {
+      "no-unused-vars": "off",
+      "vue/no-unused-components": "off"
+    }
+  },
   "scripts": {
     "dev": "vite",
     "serve": "vite",
-    "build:dev": "vue-tsc && vite build --mode development",
+    "build:dev": "vite build --mode development",
     "build:test": "vue-tsc && vite build --mode test",
     "build:pro": "vue-tsc && vite build --mode production",
     "type:check": "vue-tsc --noEmit --skipLibCheck",
@@ -50,6 +56,7 @@
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.1",
+    "qrcode": "^1.5.4",
     "qs": "^6.12.1",
     "screenfull": "^6.0.2",
     "sortablejs": "^1.15.2",

+ 91 - 0
pnpm-lock.yaml

@@ -53,6 +53,9 @@ importers:
       pinia-plugin-persistedstate:
         specifier: ^3.2.1
         version: 3.2.3(pinia@2.2.2(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2)))
+      qrcode:
+        specifier: ^1.5.4
+        version: 1.5.4
       qs:
         specifier: ^6.12.1
         version: 6.13.0
@@ -1944,6 +1947,9 @@ packages:
     resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
     engines: {node: '>=18'}
 
+  cliui@6.0.0:
+    resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+
   cliui@7.0.4:
     resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
 
@@ -2342,6 +2348,9 @@ packages:
   didyoumean@1.2.2:
     resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
 
+  dijkstrajs@1.0.3:
+    resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
+
   dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
     engines: {node: '>=8'}
@@ -3947,6 +3956,10 @@ packages:
     resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
     engines: {node: '>= 6'}
 
+  pngjs@5.0.0:
+    resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+    engines: {node: '>=10.13.0'}
+
   portfinder@1.0.32:
     resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
     engines: {node: '>= 0.12.0'}
@@ -4109,6 +4122,11 @@ packages:
 
       (For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
 
+  qrcode@1.5.4:
+    resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
+    engines: {node: '>=10.13.0'}
+    hasBin: true
+
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -4210,6 +4228,9 @@ packages:
     resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
     engines: {node: '>=0.10.0'}
 
+  require-main-filename@2.0.0:
+    resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+
   resolve-from@4.0.0:
     resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
     engines: {node: '>=4'}
@@ -4327,6 +4348,9 @@ packages:
   serialize-javascript@6.0.2:
     resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
 
+  set-blocking@2.0.0:
+    resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
   set-function-length@1.2.2:
     resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
     engines: {node: '>= 0.4'}
@@ -5097,6 +5121,9 @@ packages:
   which-boxed-primitive@1.0.2:
     resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
 
+  which-module@2.0.1:
+    resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+
   which-typed-array@1.1.15:
     resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
     engines: {node: '>= 0.4'}
@@ -5169,6 +5196,10 @@ packages:
   workbox-window@7.1.0:
     resolution: {integrity: sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==}
 
+  wrap-ansi@6.2.0:
+    resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+    engines: {node: '>=8'}
+
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
@@ -5196,6 +5227,9 @@ packages:
     resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
     engines: {node: '>=0.4'}
 
+  y18n@4.0.3:
+    resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -5211,6 +5245,10 @@ packages:
     engines: {node: '>= 14'}
     hasBin: true
 
+  yargs-parser@18.1.3:
+    resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+    engines: {node: '>=6'}
+
   yargs-parser@20.2.9:
     resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
     engines: {node: '>=10'}
@@ -5219,6 +5257,10 @@ packages:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
     engines: {node: '>=12'}
 
+  yargs@15.4.1:
+    resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+    engines: {node: '>=8'}
+
   yargs@16.2.0:
     resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
     engines: {node: '>=10'}
@@ -7260,6 +7302,12 @@ snapshots:
       slice-ansi: 5.0.0
       string-width: 7.2.0
 
+  cliui@6.0.0:
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 6.2.0
+
   cliui@7.0.4:
     dependencies:
       string-width: 4.2.3
@@ -7668,6 +7716,8 @@ snapshots:
 
   didyoumean@1.2.2: {}
 
+  dijkstrajs@1.0.3: {}
+
   dir-glob@3.0.1:
     dependencies:
       path-type: 4.0.0
@@ -9348,6 +9398,8 @@ snapshots:
 
   pirates@4.0.6: {}
 
+  pngjs@5.0.0: {}
+
   portfinder@1.0.32:
     dependencies:
       async: 2.6.4
@@ -9488,6 +9540,12 @@ snapshots:
 
   q@1.5.1: {}
 
+  qrcode@1.5.4:
+    dependencies:
+      dijkstrajs: 1.0.3
+      pngjs: 5.0.0
+      yargs: 15.4.1
+
   qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
@@ -9605,6 +9663,8 @@ snapshots:
 
   require-from-string@2.0.2: {}
 
+  require-main-filename@2.0.0: {}
+
   resolve-from@4.0.0: {}
 
   resolve-from@5.0.0: {}
@@ -9724,6 +9784,8 @@ snapshots:
     dependencies:
       randombytes: 2.1.0
 
+  set-blocking@2.0.0: {}
+
   set-function-length@1.2.2:
     dependencies:
       define-data-property: 1.1.4
@@ -10661,6 +10723,8 @@ snapshots:
       is-string: 1.0.7
       is-symbol: 1.0.4
 
+  which-module@2.0.1: {}
+
   which-typed-array@1.1.15:
     dependencies:
       available-typed-arrays: 1.0.7
@@ -10796,6 +10860,12 @@ snapshots:
       '@types/trusted-types': 2.0.7
       workbox-core: 7.1.0
 
+  wrap-ansi@6.2.0:
+    dependencies:
+      ansi-styles: 4.3.0
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -10825,6 +10895,8 @@ snapshots:
 
   xtend@4.0.2: {}
 
+  y18n@4.0.3: {}
+
   y18n@5.0.8: {}
 
   yallist@3.1.1: {}
@@ -10833,10 +10905,29 @@ snapshots:
 
   yaml@2.5.1: {}
 
+  yargs-parser@18.1.3:
+    dependencies:
+      camelcase: 5.3.1
+      decamelize: 1.2.0
+
   yargs-parser@20.2.9: {}
 
   yargs-parser@21.1.1: {}
 
+  yargs@15.4.1:
+    dependencies:
+      cliui: 6.0.0
+      decamelize: 1.2.0
+      find-up: 4.1.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      require-main-filename: 2.0.0
+      set-blocking: 2.0.0
+      string-width: 4.2.3
+      which-module: 2.0.1
+      y18n: 4.0.3
+      yargs-parser: 18.1.3
+
   yargs@16.2.0:
     dependencies:
       cliui: 7.0.4

+ 3 - 3
src/api/index.ts

@@ -16,11 +16,11 @@ export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
 
 const config = {
   // 默认地址请求地址,可在 .env.** 文件中修改
-  baseURL: import.meta.env.VITE_API_URL as string,
+  baseURL: import.meta.env.VITE_APP_DOMAIN as string,
   // 设置超时时间
-  timeout: ResultEnum.TIMEOUT as number,
+  timeout: ResultEnum.TIMEOUT as number
   // 跨域时候允许携带凭证
-  withCredentials: true
+  // withCredentials: true
 };
 
 const axiosCanceler = new AxiosCanceler();

+ 17 - 4
src/assets/json/authMenuList.json

@@ -11,8 +11,7 @@
         "isLink": "",
         "isHide": false,
         "isFull": false,
-        "isAffix": false,
-        "isKeepAlive": true
+        "isAffix": false
       },
       "children": [
         {
@@ -26,7 +25,21 @@
             "isHide": false,
             "isFull": false,
             "isAffix": true,
-            "isKeepAlive": true
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/promotion/invoice",
+          "name": "PromotionInvoice",
+          "component": "/promotion/invoice",
+          "meta": {
+            "icon": "Menu",
+            "title": "推广账单",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": true,
+            "isKeepAlive": false
           }
         },
 
@@ -41,7 +54,7 @@
             "isHide": false,
             "isFull": false,
             "isAffix": true,
-            "isKeepAlive": true
+            "isKeepAlive": false
           }
         }
       ]

+ 1 - 1
src/layouts/LayoutClassic/index.vue

@@ -58,5 +58,5 @@ const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu
 </script>
 
 <style scoped lang="scss">
-@import "./index.scss";
+@import "./index";
 </style>

+ 1 - 1
src/layouts/LayoutColumns/index.vue

@@ -99,5 +99,5 @@ const changeSubMenu = (item: Menu.MenuOptions) => {
 </script>
 
 <style scoped lang="scss">
-@import "./index.scss";
+@import "./index";
 </style>

+ 1 - 1
src/layouts/LayoutTransverse/index.vue

@@ -57,5 +57,5 @@ const handleClickMenu = (subItem: Menu.MenuOptions) => {
 </script>
 
 <style scoped lang="scss">
-@import "./index.scss";
+@import "./index";
 </style>

+ 7 - 3
src/layouts/LayoutVertical/index.vue

@@ -5,7 +5,7 @@
       <div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
         <div class="logo flx-center">
           <img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />
-          <span v-show="!isCollapse" class="logo-text">{{ title }}</span>
+          <span v-show="!isCollapse" class="logo-text">{{ title }} </span>
         </div>
         <el-scrollbar>
           <el-menu
@@ -33,6 +33,7 @@
 <script setup lang="ts" name="layoutVertical">
 import { computed } from "vue";
 import { useRoute } from "vue-router";
+import { useUserStore } from "@/stores/modules/user";
 import { useAuthStore } from "@/stores/modules/auth";
 import { useGlobalStore } from "@/stores/modules/global";
 import Main from "@/layouts/components/Main/index.vue";
@@ -40,7 +41,10 @@ import ToolBarLeft from "@/layouts/components/Header/ToolBarLeft.vue";
 import ToolBarRight from "@/layouts/components/Header/ToolBarRight.vue";
 import SubMenu from "@/layouts/components/Menu/SubMenu.vue";
 
-const title = import.meta.env.VITE_GLOB_APP_TITLE;
+// const title = import.meta.env.VITE_GLOB_APP_TITLE;
+
+const userStore = useUserStore();
+const title = computed(() => userStore.userInfo.name);
 
 const route = useRoute();
 const authStore = useAuthStore();
@@ -52,5 +56,5 @@ const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu
 </script>
 
 <style scoped lang="scss">
-@import "./index.scss";
+@import "./index";
 </style>

+ 4 - 4
src/layouts/components/Header/ToolBarRight.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="tool-bar-ri">
     <div class="header-icon">
-      <AssemblySize id="assemblySize" />
-      <Language id="language" />
-      <SearchMenu id="searchMenu" />
+      <!-- <AssemblySize id="assemblySize" /> -->
+      <!-- <Language id="language" /> -->
+      <!-- <SearchMenu id="searchMenu" /> -->
       <ThemeSetting id="themeSetting" />
-      <Message id="message" />
+      <!-- <Message id="message" /> -->
       <Fullscreen id="fullscreen" />
     </div>
     <span class="username">{{ username }}</span>

+ 9 - 1
src/layouts/components/Header/components/InfoDialog.vue

@@ -1,6 +1,8 @@
 <template>
   <el-dialog v-model="dialogVisible" title="个人信息" width="500px" draggable>
-    <span>This is userInfo</span>
+    <span>
+      <h3>合作机构名称:{{ username }}</h3>
+    </span>
     <template #footer>
       <span class="dialog-footer">
         <el-button @click="dialogVisible = false">取消</el-button>
@@ -11,6 +13,12 @@
 </template>
 
 <script setup lang="ts">
+import { computed } from "vue";
+import { useUserStore } from "@/stores/modules/user";
+
+const userStore = useUserStore();
+const username = computed(() => userStore.userInfo.name);
+
 import { ref } from "vue";
 
 const dialogVisible = ref(false);

+ 85 - 7
src/layouts/components/Header/components/PasswordDialog.vue

@@ -1,22 +1,100 @@
 <template>
-  <el-dialog v-model="dialogVisible" title="修改密码" width="500px" draggable>
-    <span>This is Password</span>
-    <template #footer>
-      <span class="dialog-footer">
+  <el-dialog v-model="dialogVisible" title="修改密码" width="500px" draggable @close="onReset(ruleFormRef)">
+    <el-form ref="ruleFormRef" :model="ruleForm" size="large" label-width="120px">
+      <!-- <el-form-item
+        label="昵称"
+        :rules="[
+          {
+            required: true,
+            message: '请输入昵称',
+            trigger: 'blur'
+          }
+        ]"
+        prop="nickname"
+      >
+        <el-input clearable v-model="ruleForm.nickname" placeholder="请输入昵称" maxlength="10" show-word-limit />
+      </el-form-item> -->
+
+      <el-form-item
+        label="原密码"
+        prop="oldPassword"
+        :rules="[
+          {
+            required: true,
+            message: '请输入原密码',
+            trigger: 'blur'
+          }
+        ]"
+      >
+        <el-input clearable show-password v-model="ruleForm.oldPassword" placeholder="请输入新密码" />
+      </el-form-item>
+
+      <el-form-item
+        prop="password"
+        label="新密码"
+        :rules="[
+          {
+            required: true,
+            message: '请输入新密码',
+            trigger: 'blur'
+          }
+        ]"
+      >
+        <el-input clearable show-password v-model="ruleForm.password" placeholder="密码" />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="onSubmit(ruleFormRef)">确定</el-button>
         <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="dialogVisible = false">确认</el-button>
-      </span>
-    </template>
+      </el-form-item>
+    </el-form>
   </el-dialog>
 </template>
 
 <script setup lang="ts">
 import { ref } from "vue";
+import { useUserStore } from "@/stores/modules/user";
+import { ElMessage, type FormInstance } from "element-plus";
+import { LOGIN_URL } from "@/config";
+import { useRouter } from "vue-router";
+
+import request from "@/api";
+
+const router = useRouter();
+const userStore = useUserStore();
 
 const dialogVisible = ref(false);
 const openDialog = () => {
   dialogVisible.value = true;
 };
 
+const ruleForm = ref({
+  password: "",
+  oldPassword: ""
+});
+const ruleFormRef = ref();
+
+const onSubmit = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  await formEl.validate((valid, fields) => {
+    if (!valid) return;
+    request.post("/archivesService/agency/update/password", ruleForm.value).then(_ => {
+      ElMessage({
+        type: "success",
+        message: "修改成功"
+      });
+
+      // 2.清除 Token
+      userStore.setToken("");
+
+      // 3.重定向到登陆页
+      router.replace(LOGIN_URL);
+    });
+  });
+};
+const onReset = () => {
+  if (!ruleFormRef.value) return;
+  ruleFormRef.value.resetFields();
+};
+
 defineExpose({ openDialog });
 </script>

+ 1 - 1
src/layouts/index.vue

@@ -1,6 +1,6 @@
 <!-- 💥 这里是一次性加载 LayoutComponents -->
 <template>
-  <el-watermark id="watermark" :font="font" :content="watermark ? ['Geeker Admin', 'Happy Working'] : ''">
+  <el-watermark id="watermark" :font="font" :content="watermark ? ['第三方管理系统', 'Happy Working'] : ''">
     <component :is="LayoutComponents[layout]" />
     <ThemeDrawer />
   </el-watermark>

+ 1 - 1
src/layouts/indexAsync.vue

@@ -1,6 +1,6 @@
 <!-- 💥 这里是异步加载 LayoutComponents -->
 <template>
-  <el-watermark id="watermark" :font="font" :content="watermark ? ['Geeker Admin', 'Happy Working'] : ''">
+  <el-watermark id="watermark" :font="font" :content="watermark ? ['第三方管理系统', 'Happy Working'] : ''">
     <suspense>
       <template #default>
         <component :is="LayoutComponents[layout]" />

+ 1 - 1
src/stores/modules/user.ts

@@ -6,7 +6,7 @@ export const useUserStore = defineStore({
   id: "geeker-user",
   state: (): UserState => ({
     token: "",
-    userInfo: { name: "Geeker" }
+    userInfo: { name: "" }
   }),
   getters: {},
   actions: {

+ 16 - 8
src/utils/errorHandler.ts

@@ -2,23 +2,31 @@ import { ElNotification } from "element-plus";
 
 /**
  * @description 全局代码错误捕捉
- * */
-const errorHandler = (error: any) => {
+ */
+interface CustomError extends Error {
+  status?: number;
+}
+
+const errorHandler = (error: CustomError) => {
   // 过滤 HTTP 请求错误
+  if (error.name === "HttpError") return false;
+
   if (error.status || error.status == 0) return false;
-  let errorMap: { [key: string]: string } = {
-    InternalError: "Javascript引擎内部错误",
+
+  const errorMap: { [key: string]: string } = {
+    InternalError: "JavaScript 引擎内部错误",
     ReferenceError: "未找到对象",
     TypeError: "使用了错误的类型或对象",
     RangeError: "使用内置对象时,参数超范围",
     SyntaxError: "语法错误",
-    EvalError: "错误的使用了Eval",
-    URIError: "URI错误"
+    EvalError: "错误的使用了 Eval",
+    URIError: "URI 错误"
   };
-  let errorName = errorMap[error.name] || "未知错误";
+
+  const errorName = errorMap[error.name] || "未知错误";
   ElNotification({
     title: errorName,
-    message: error,
+    message: error.message || "无详细信息", // 确保传递的是字符串
     type: "error",
     duration: 3000
   });

+ 51 - 0
src/utils/index.ts

@@ -309,3 +309,54 @@ export function findItemNested(enumData: any, callValue: any, value: string, chi
     if (current[children]) return findItemNested(current[children], callValue, value, children);
   }, null);
 }
+
+/**
+ * 下载文件的公共函数
+ * @param {string} url - 请求的 URL
+ * @param {string} fileName - 下载后文件的名称
+ * @param {object} params - 请求参数
+ */
+export const handleDownloadFile = async (response, fileName, params = {}) => {
+  try {
+    // 创建一个 URL 对象
+    const blob = new Blob([response], {
+      type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+    });
+
+    const downloadUrl = window.URL.createObjectURL(blob);
+
+    // 创建一个链接元素
+    const link = document.createElement("a");
+    link.href = downloadUrl;
+    link.setAttribute("download", fileName); // 设置下载文件名
+
+    // 触发下载
+    document.body.appendChild(link);
+    link.click();
+
+    // 清理
+    link.parentNode.removeChild(link);
+    window.URL.revokeObjectURL(downloadUrl);
+  } catch (error) {
+    console.error("下载文件失败:", error);
+    throw error; // 可以根据需要抛出错误
+  }
+};
+
+/**
+ * 根据对象数组中的某个属性值进行去重
+ * @param array 对象数组,需要去重的数组
+ * @param key 指定根据哪个属性进行去重
+ * @returns 返回去重后的对象数组
+ */
+export const unique = (array, key) => {
+  // 使用Set实现根据属性值的唯一性
+  const _set = [...new Set(array.map(e => e[key]))];
+  const deArray = [];
+  // 根据_set中的属性值,找出原数组中对应的项
+  _set.map(item => {
+    deArray.push(array[array.findIndex(val => val[key] === item)]);
+  });
+  // 返回去重后的数组
+  return deArray;
+};

+ 1 - 1
src/views/directives/watermarkDirect/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-waterMarker="{ text: 'Geeker Admin', textColor: 'rgba(180, 180, 180, 0.6)' }" class="card content-box">
+  <div v-waterMarker="{ text: '第三方管理系统', textColor: 'rgba(180, 180, 180, 0.6)' }" class="card content-box">
     <span class="text">水印指令 🍇🍇🍇🍓🍓🍓</span>
   </div>
 </template>

+ 4 - 0
src/views/login/components/LoginForm.vue

@@ -40,6 +40,7 @@ import { useKeepAliveStore } from "@/stores/modules/keepAlive";
 import { initDynamicRouter } from "@/routers/modules/dynamicRouter";
 import { CircleClose, UserFilled } from "@element-plus/icons-vue";
 import type { ElForm } from "element-plus";
+import request from "@/api";
 
 const router = useRouter();
 const userStore = useUserStore();
@@ -70,6 +71,9 @@ const login = (formEl: FormInstance | undefined) => {
       const { data } = await loginApi({ ...loginForm, password: loginForm.password });
       userStore.setToken(data.token);
 
+      request.get("/archivesService/agency/profile").then(resp => {
+        userStore.setUserInfo({ ...resp.data.profile, name: resp.data.profile.nickname });
+      });
       // 2.添加动态路由
       await initDynamicRouter();
 

+ 0 - 1
src/views/login/index.scss

@@ -45,7 +45,6 @@
           height: 52px;
         }
         .logo-text {
-          padding: 0 0 0 25px;
           margin: 0;
           font-size: 32px;
           font-weight: bold;

+ 1 - 1
src/views/login/index.vue

@@ -7,7 +7,7 @@
       </div>
       <div class="login-form">
         <div class="login-logo">
-          <img class="login-icon" src="@/assets/images/logo.svg" alt="" />
+          <!-- <img class="login-icon" src="@/assets/images/logo.svg" alt="" /> -->
           <h4 class="logo-text">合作机构后台管理系统</h4>
         </div>
         <LoginForm />

+ 5 - 1
src/views/promotion/channel.vue

@@ -22,7 +22,11 @@
             <el-button type="primary" :icon="Grid" @click="handleViewCode(row)" link>推广码</el-button>
             <el-button type="primary" :icon="Edit" link @click="handleEdit(row)">编辑</el-button>
 
-            <el-popconfirm title="确定要删除此个推广渠道?" @confirm="handleDelete(row)">
+            <el-popconfirm
+              title="确定要删除这个推广渠道吗?删除后,该推广渠道的所有推广链接将失效,已推广订单不会受到影响"
+              @confirm="handleDelete(row)"
+              width="330px"
+            >
               <template #reference>
                 <el-button type="danger" :icon="Delete" link>删除</el-button>
               </template>

+ 31 - 5
src/views/promotion/components/DialogPromotionCode.vue

@@ -4,14 +4,38 @@
       <div>
         <h3 class="mb-4">推广者名称: {{ rowData.name }}</h3>
         <el-table :data="state.list" style="width: 100%" border>
-          <el-table-column prop="memberCardId" label="会员卡名称"> </el-table-column>
+          <el-table-column prop="memberCardName" label="会员卡名称" :formatter="v => v.memberCardName || '-'"> </el-table-column>
           <el-table-column prop="originPrice" :formatter="v => (v.originPrice / 100).toFixed(2)" label="推广原价">
           </el-table-column>
           <el-table-column prop="price" :formatter="v => (v.price / 100).toFixed(2)" label="推广售价"> </el-table-column>
           <el-table-column prop="prop" label="操作">
             <template #default="{ row }">
-              <el-button type="primary" :icon="CopyDocument" v-copy="row.id" link>复制推广链接{{ row.id }}</el-button>
-              <el-button type="primary" :icon="Download" link>下载推广二维码</el-button>
+              <el-space>
+                <el-button
+                  type="primary"
+                  :icon="CopyDocument"
+                  v-copy="`${H5_DOMAIN}/promotion/goods?promotionSiteId=${rowData.sn}&&promotionGoodsId=${row.sn}`"
+                  link
+                  >复制推广链接</el-button
+                >
+                <QRCodeShare ref="QRCodeShareRef">
+                  <el-button
+                    type="primary"
+                    :icon="Download"
+                    link
+                    @click="
+                      QRCodeShareRef.generateAndDownloadQRCode(
+                        `${H5_DOMAIN}/promotion/goods?promotionSiteId=${rowData.sn}&&promotionGoodsId=${row.sn}`,
+                        `推广者二维码--${rowData.name}--会员卡名称--${row.memberCardName}`
+                      )
+                    "
+                  >
+                    下载推广二维码</el-button
+                  >
+                </QRCodeShare>
+              </el-space>
+              <!-- <el-button type="primary" :icon="Download" link>
+                下载推广二维码</el-button> -->
             </template>
           </el-table-column>
         </el-table>
@@ -26,11 +50,11 @@
   </div>
 </template>
 <script lang="ts" setup>
-import { computed, defineProps, defineEmits, reactive, watch } from "vue";
+import { computed, defineProps, defineEmits, reactive, watch, ref } from "vue";
 import { ElMessage, type FormInstance } from "element-plus";
 import { promotionSiteUpdateOrCreate, getPromotionGoodsList } from "@/api/modules/promotion";
 import { Download, CopyDocument } from "@element-plus/icons-vue";
-
+import QRCodeShare from "./QRCodeShare.vue";
 const props = defineProps({
   show: {
     type: Boolean,
@@ -44,6 +68,8 @@ const props = defineProps({
   }
 });
 const emits = defineEmits(["update:show", "success"]);
+const QRCodeShareRef = ref();
+const H5_DOMAIN = import.meta.env.VITE_APP_H5_DOMAIN as string;
 const state = reactive({
   list: []
 });

+ 81 - 0
src/views/promotion/components/DialogRecordTable.vue

@@ -0,0 +1,81 @@
+<template>
+  <div>
+    <el-dialog title="获取账单生成历史记录(含错误信息)" v-model="dialogVisible">
+      <div>
+        <el-table :data="list" style="width: 100%">
+          <el-table-column prop="startTime" label="账单周期">
+            <template #default="{ row }">
+              <div class="flex">
+                <div>{{ row.startTime?.split(" ")[0] }}</div>
+                <div class="mx-2">至</div>
+                <div>{{ row.endTime?.split(" ")[0] }}</div>
+              </div>
+            </template>
+          </el-table-column>
+
+          <el-table-column prop="errMsgList" label="错误信息">
+            <template #default="{ row }">
+              <div v-if="row.errMsgList.length === 0">-</div>
+              <div v-else v-for="(err, index) in row.errMsgList" :key="index">
+                <div class="text-red-500">{{ err }}</div>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script lang="ts" setup>
+import request from "@/api";
+import { ref, computed, defineProps, defineEmits, watch, reactive } from "vue";
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false
+  },
+  rowData: {
+    type: Object,
+    default: () => {}
+  }
+});
+const emits = defineEmits(["update:show", "success"]);
+const dialogVisible = computed({
+  get() {
+    return props.show;
+  },
+  set(n) {
+    emits("update:show", n);
+  }
+});
+const query = {
+  page: 1,
+  pageSize: 10
+};
+const total = ref(0);
+const list = ref([]);
+watch(
+  () => dialogVisible.value,
+  n => {
+    if (n) {
+      query.page = 1;
+      getList();
+    }
+  }
+);
+const getList = () => {
+  request
+    .get("/archivesService/agency/orderBill/generateLog/paginate", {
+      params: query.value
+    })
+    .then(resp => {
+      list.value = resp.data.list.map(v => {
+        return {
+          ...v,
+          errMsgList: JSON.parse(v.errMsg)
+        };
+      });
+      total.value = resp.data.total;
+    });
+};
+</script>

+ 155 - 0
src/views/promotion/components/PerviewDetail.vue

@@ -0,0 +1,155 @@
+<template>
+  <div>
+    <div class="flex items-center">
+      <div class="w-[100px] h-[100px] text-purple-600">
+        <Tickets />
+      </div>
+      <div>
+        <div>会员卡订单</div>
+        <div class="mt-6">订单编号:{{ detail?.sn }}</div>
+      </div>
+    </div>
+    <div class="mt-4">
+      <el-tabs type="border-card" v-model="activeName">
+        <el-tab-pane name="1">
+          <template #label>
+            订单信息
+            <!-- <el-icon v-if="isvisibleIcon" class="ml-1" @click.stop="handleProcessCallback"><Warning /></el-icon> -->
+          </template>
+          <el-descriptions title="" :column="2">
+            <el-descriptions-item label="订单编号:">{{ detail?.sn }}</el-descriptions-item>
+            <el-descriptions-item label="订单类型:">会员卡订单</el-descriptions-item>
+            <el-descriptions-item label="创建时间:">{{ detail?.createdAt }}</el-descriptions-item>
+            <el-descriptions-item label="支付时间:">{{ detail?.payTime || "-" }}</el-descriptions-item>
+            <el-descriptions-item label="订单状态:">
+              <span :class="statusList[detail?.status - 1]?.color">
+                {{ statusList[detail?.status - 1]?.label }}
+              </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="支付方式:"> 微信支付 </el-descriptions-item>
+            <el-descriptions-item label="订单金额:"> ¥{{ detail?.balance / 100 }} </el-descriptions-item>
+            <el-descriptions-item label="实付金额:"> ¥{{ detail?.balance / 100 }} </el-descriptions-item>
+            <el-descriptions-item label="支付款流向:">
+              {{ detail?.paymentType == 1 ? "平台" : detail?.agency?.nickname }}
+            </el-descriptions-item>
+            <el-descriptions-item label="商户号:">
+              {{ detail?.mchId }}
+            </el-descriptions-item>
+            <el-descriptions-item label="支付单号:">
+              {{ detail?.paymentOrderSn || "-" }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="订单来源信息" name="2">
+          <el-descriptions title="" :column="2">
+            <el-descriptions-item label="订单来源:">
+              {{ detail?.agency?.nickname || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="来源ID:">
+              {{ detail?.agency?.account }}
+            </el-descriptions-item>
+            <el-descriptions-item label="渠道名称:">
+              {{ detail?.promotionSite?.name || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="渠道ID:">
+              {{ detail?.promotionSite?.sn || "-" }}
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="商品信息" name="3">
+          <el-descriptions title="" :column="1">
+            <el-descriptions-item label="商品名称:">
+              {{ detail?.promotionGoods?.memberCardName || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="商品ID:">
+              {{ detail?.promotionGoods?.memberCardId || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="购买数量:">1 </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+        <el-tab-pane label="用户信息" name="4">
+          <el-descriptions title="" :column="1">
+            <el-descriptions-item label="用户ID:">
+              {{ detail?.archives?.id || "-" }}
+            </el-descriptions-item>
+            <el-descriptions-item label="手机号:">
+              <span v-if="!detail?.archives?.accounts?.length">-</span>
+              <span v-else v-for="(item, index) in detail?.archives?.accounts" :key="index" class="space-x-4">
+                <span>{{ item?.account || "-" }}</span>
+              </span>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+<script setup>
+// import { request } from "@/utils";
+import http from "@/api";
+import { Tickets, Warning } from "@element-plus/icons-vue";
+import { ref, defineProps, computed } from "vue";
+import { ElMessage } from "element-plus";
+
+import { statusList } from "../constant.ts";
+
+const activeName = ref("1");
+const props = defineProps({
+  orderSn: {
+    type: String,
+    default: ""
+  }
+});
+const emits = defineEmits(["success"]);
+const detail = ref({});
+
+const isvisibleIcon = computed(() => {
+  const payTimeDiffFiveMin =
+    detail.value?.payTime && new Date().getTime() - new Date(detail.value?.payTime).getTime() > 5 * 60 * 1000;
+  return (
+    (detail.value.status == 2 && [1, 3].includes(detail.value?.callbackStatus)) ||
+    (detail.value?.callbackStatus == 4 && payTimeDiffFiveMin)
+  );
+});
+const handleProcessCallback = () => {
+  /**
+   * 
+   * 订单状态为支付成功的时候,微信支付回调可能会支付失败。如果为待处理和失败 则重新发起
+   * 如果为4,则支付时间超过了5分钟 也要重新发起
+   * 
+   * OrderTypeCallbackStatusWait    = 1 // 待处理
+    OrderTypeCallbackStatusDone    = 2 // 已处理
+    OrderTypeCallbackStatusError   = 3 // 失败
+    OrderTypeCallbackStatusProcess = 4 // 处理中
+   */
+  http
+    .post("archivesService/mechanism/order/processCallback", {
+      orderSn: props.orderSn
+    })
+    .then(resp => {
+      ElMessage.success(resp.message);
+      emits("success");
+      getDetail();
+    });
+};
+
+const getDetail = () => {
+  if (!props.orderSn) return;
+  http
+    .get("/archivesService/agency/order/detail", {
+      orderSn: props.orderSn
+    })
+    .then(resp => {
+      detail.value = resp.data.detail;
+    });
+};
+const initData = () => {
+  activeName.value = "1";
+  getDetail();
+};
+
+defineExpose({
+  initData
+});
+</script>
+<style lang="scss"></style>

+ 227 - 0
src/views/promotion/components/PreviewPromotionalInvoiceDetailDialog.vue

@@ -0,0 +1,227 @@
+<template>
+  <div>
+    <el-dialog title="账单详情" v-model="dialogVisible" width="70%">
+      <div>
+        <el-table :data="[detail]" style="width: 100%" border>
+          <el-table-column prop="startTime" label="账单周期">
+            <template #default="{ row }">
+              <div class="flex">
+                <div>{{ row.startTime?.split(" ")[0] }}</div>
+                <div class="mx-2">至</div>
+                <div>{{ row.endTime?.split(" ")[0] }}</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column prop="archiveId" label="账单所属第三方名称">
+            <template #default="{ row }">
+              <div class="flex">
+                <div>{{ row.agency?.nickname }}【{{ row.agency?.id }}】</div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column
+            prop="agencyReceiveBalance"
+            label="累计支付到第三方总金额"
+            :formatter="v => (v.agencyReceiveBalance / 100).toFixed(2)"
+          />
+          <el-table-column
+            prop="toMechanismBalance"
+            label="结算后平台应收收款金额"
+            :formatter="v => (v.toMechanismBalance / 100).toFixed(2)"
+          />
+          <el-table-column
+            prop="mechanismReceiveBalance"
+            label="累计支付到平台总金额"
+            :formatter="v => (v.mechanismReceiveBalance / 100).toFixed(2)"
+          />
+          <el-table-column
+            prop="toAgencyBalance"
+            label="结算后第三方应收款金额"
+            :formatter="v => (v.toAgencyBalance / 100).toFixed(2)"
+          />
+          <el-table-column prop="serviceName" label="结算状态">
+            <template #default="{ row }">
+              <div :class="settlementStatusList[row.status - 1]?.color">
+                {{ settlementStatusList[row.status - 1]?.label }}
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-button type="primary" class="my-3" v-if="detail.status == 2" @click="handleSettle">设为已结算</el-button>
+        <el-divider />
+        <div>
+          <div class="flex justify-between">
+            <div class="flex">
+              <div>{{ detail.startTime?.split(" ")[0] }}</div>
+              <div class="mx-2">至</div>
+              <div class="mr-3">{{ detail.endTime?.split(" ")[0] }}</div>
+              订单详情(系统只显示这期间内支付已完成订单)
+            </div>
+            <el-button v-if="state.recordList.length" :icon="Document" type="primary" link @click="handleExport"
+              >导出详情</el-button
+            >
+          </div>
+
+          <el-table :data="state.recordList" style="width: 100%" border class="my-4">
+            <el-table-column prop="sn" label="订单信息">
+              <template #default="{ row }">
+                <div>
+                  <span>{{ row.order.sn }}</span>
+                  <el-tag>【会员卡订单】</el-tag>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="promotionSite" label="订单来源信息">
+              <template #default="{ row }">
+                <div>
+                  <div>{{ row?.order?.promotionSite?.name }}</div>
+                  <div style="color: #a8abb2">[ {{ row.order?.promotionSite?.sn }} ]</div>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column
+              prop="promotionGoods"
+              :formatter="v => v.order.promotionGoods?.memberCardName"
+              label="订单商品名称"
+            />
+            <el-table-column prop="promotionGoods" label="实付金额">
+              <template #default="{ row }">
+                <div>
+                  {{ row.order.balance / 100 }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="status" :formatter="v => v.order.promotionGoods?.memberCardName" label="订单状态">
+              <template #default="{ row }">
+                <div :class="statusList[row.order.status - 1]?.color">
+                  {{ statusList[row.order.status - 1]?.label }}
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="createdAt" label="支付时间" />
+            <el-table-column prop="payType" label="支付方式" :formatter="() => '微信支付'" />
+            <el-table-column prop="paymentType" label="支付款流向">
+              <template #default="{ row }">
+                <div>
+                  {{ row.order.paymentType === 1 ? "平台" : row?.order.agency?.nickname }}
+                </div>
+                <div style="color: #a8abb2">
+                  [ 商户号: {{ row.order.mchId }} ]
+                  <br />
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column
+              prop="toAgencyBalance"
+              label="应收款方"
+              :formatter="v => (v.type == 2 ? '平台' : v?.order.agency?.nickname)"
+            />
+            <el-table-column prop="toMechanismBalance" label="应收结算金额" :formatter="v => (v.balance / 100).toFixed(2)" />
+          </el-table>
+          <el-pagination
+            class="mt-4 justify-end"
+            background
+            layout="prev, pager, next"
+            :total="state.total"
+            :page-size="state.query.pageSize"
+            @current-change="changePage"
+          />
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import { handleDownloadFile } from "@/utils";
+import { ref, computed, defineProps, defineEmits, reactive } from "vue";
+import { statusList, settlementStatusList } from "../constant.ts";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { Document } from "@element-plus/icons-vue";
+import request from "@/api";
+
+const props = defineProps({
+  show: {
+    type: Boolean,
+    default: false
+  },
+  rowData: {
+    type: Object,
+    default: () => {}
+  }
+});
+const emits = defineEmits(["update:show", "success"]);
+const detail = ref({});
+const state = reactive({
+  query: {
+    page: 1,
+    pageSize: 10
+  },
+
+  total: 0,
+  recordList: []
+});
+const dialogVisible = computed({
+  get() {
+    return props.show;
+  },
+  set(n) {
+    emits("update:show", n);
+  }
+});
+const handleSettle = () => {
+  ElMessageBox.confirm("确定该笔账单已经进行了实际的资金结算吗?确定后该状态不可撤回", "提示", {
+    confirmButtonText: "确定",
+    cancelButtonText: "取消",
+    type: "warning"
+  }).then(() => {
+    request.post(`archivesService/agency/orderBill/settle?id=${detail.value.id}`).then(resp => {
+      ElMessage.success(resp.message);
+      emits("success");
+      initData(detail.value.id);
+    });
+  });
+};
+const initData = id => {
+  request.get(`archivesService/agency/orderBill/detail?id=${id}`).then(resp => {
+    detail.value = resp.data.detail;
+  });
+  getOrderBillRecord(id);
+};
+const getOrderBillRecord = id => {
+  request
+    .get("archivesService/agency/orderBill/record/paginate", {
+      ...state.query,
+      billId: id
+    })
+    .then(resp => {
+      state.recordList = resp.data.list;
+      state.total = resp.data.total;
+    });
+};
+const handleExport = () => {
+  request
+    .post(
+      `archivesService/agency/orderBill/export`,
+      {
+        id: detail.value.id
+      },
+      {
+        responseType: "blob"
+      }
+    )
+    .then(resp => {
+      const fileName = `${detail.value.startTime?.split(" ")[0]} 至 ${
+        detail.value.endTime?.split(" ")[0]
+      } 订单详情(系统只显示这期间内支付已完成订单)`;
+
+      handleDownloadFile(resp, fileName);
+    });
+};
+const changePage = page => {
+  state.query.page = page;
+  getOrderBillRecord(detail.value.id);
+};
+defineExpose({
+  initData
+});
+</script>

+ 52 - 0
src/views/promotion/components/QRCodeShare.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="">
+    <!-- <button @click="generateAndDownloadQRCode" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
+      下载分享二维码
+    </button> -->
+    <slot></slot>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+import QRCode from "qrcode";
+
+// const SHARE_URL = "https://www.baidu.com";
+
+const generateAndDownloadQRCode = async (SHARE_URL, filename, params = {}) => {
+  try {
+    // 生成二维码的 DataURL
+    const qrCodeDataUrl = await QRCode.toDataURL(SHARE_URL, {
+      errorCorrectionLevel: "H",
+      width: 300,
+      margin: 2,
+      ...params
+    });
+
+    // 创建一个临时链接元素
+    const downloadLink = document.createElement("a");
+    downloadLink.href = qrCodeDataUrl;
+    downloadLink.download = `${filename}.png`;
+
+    // 触发下载
+    document.body.appendChild(downloadLink);
+    downloadLink.click();
+    document.body.removeChild(downloadLink);
+  } catch (error) {
+    console.error("生成二维码失败:", error);
+    alert("生成二维码失败,请重试");
+  }
+};
+defineExpose({
+  generateAndDownloadQRCode
+});
+</script>
+
+<style scoped>
+.qr-code-share {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20px;
+}
+</style>

+ 103 - 0
src/views/promotion/components/SearchArchivesSelect.vue

@@ -0,0 +1,103 @@
+<template>
+  <el-select
+    v-model="selected"
+    filterable
+    clearable
+    :placeholder="props.placeholder"
+    remote
+    remote-show-suffix
+    :remote-method="searchArchives"
+    @change="change"
+  >
+    <el-option v-for="(item, idx) in options" :key="item.id" :value="item.id" :label="item.name">
+      <div class="flex items-center justify-between">
+        <span>{{ item.name }}</span>
+        <el-tag type="info" class="ml-4">{{ item.id }}</el-tag>
+      </div>
+    </el-option>
+  </el-select>
+</template>
+
+<script setup>
+import { computed, nextTick, onMounted, ref } from "vue";
+import { unique } from "@/utils";
+import request from "@/api";
+const props = defineProps({
+  value: {
+    type: String,
+    default: ""
+  },
+  placeholder: {
+    type: String,
+    default: undefined
+  },
+  list: {
+    type: Array,
+    default: () => []
+  },
+  channelIds: {
+    type: String,
+    default: ""
+  }
+});
+const emit = defineEmits(["input", "change"]);
+const selected = computed({
+  get() {
+    return props.value;
+  },
+  set(val) {
+    emit("input", val);
+  }
+});
+const change = e => {
+  emit(
+    "change",
+    e,
+    options.value.find(v => v.id == e)
+  );
+  console.log(e);
+};
+
+const options = ref([]);
+const searchArchives = async (q, init = false) => {
+  if (!q && !init) return;
+  options.value = [];
+  const { data } = await request.get(`/archivesService/agency/archives/paginate`, {
+    page: 1,
+    pageSize: 100,
+    keyword: q.trim(),
+    channelIds: props.channelIds,
+    needSubArchives: 1
+  });
+  const tmp = data.list
+    .map(v => {
+      v.familyIds = [v.id, ...(v.associatArchives || []).map(v3 => v3.id)];
+      return v.associatArchives?.length
+        ? [
+            v,
+            ...v.associatArchives.map(v2 => {
+              v2.familyIds = [v.id, ...v.associatArchives.map(v3 => v3.id)];
+              return v2;
+            })
+          ]
+        : [v];
+    })
+    .flat();
+  nextTick(() => {
+    options.value = unique(tmp, "id");
+    if (options.value[0]?.id && !init) {
+      selected.value = options.value[0].id;
+    }
+  });
+};
+
+onMounted(() => {
+  if (props.list && props.list.length) {
+    options.value = props.list;
+  } else {
+    searchArchives("", true);
+  }
+});
+</script>
+
+<style lang="scss" scoped></style>

+ 39 - 0
src/views/promotion/constant.ts

@@ -0,0 +1,39 @@
+export const statusList = [
+  {
+    label: "待支付",
+    value: 1,
+    color: "text-red-500"
+  },
+  {
+    label: "已完成",
+    value: 2,
+    color: "text-green-500"
+  },
+  {
+    label: "已关闭",
+    value: 3,
+    color: "text-purple-500"
+  }
+];
+export const accountTypes = {
+  1: "手机号",
+  2: "身份证号",
+  3: "特殊编号"
+};
+export const settlementStatusList = [
+  {
+    label: "无需结算",
+    value: 1,
+    color: "text-blue-500"
+  },
+  {
+    label: "待结算",
+    value: 2,
+    color: "text-red-500"
+  },
+  {
+    label: "已结算",
+    value: 3,
+    color: "text-green-500"
+  }
+];

+ 241 - 0
src/views/promotion/invoice.vue

@@ -0,0 +1,241 @@
+<template>
+  <div>
+    <div>
+      <el-card class="mb-4" shadow="never">
+        <div>
+          <el-row>
+            <el-col :span="18">
+              <el-space wrap>
+                <el-date-picker
+                  v-model="state.times"
+                  type="monthrange"
+                  clearable
+                  range-separator="至"
+                  value-format="YYYY/MM/DD"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                />
+                <el-select
+                  v-model="state.query.status"
+                  placeholder="所有结算状态"
+                  filterable
+                  multiple
+                  collapse-tags
+                  clearable
+                  class="w-[180px]"
+                >
+                  <el-option v-for="item in settlementStatusList" :label="item.label" :value="item.value" :key="item.value" />
+                </el-select>
+                <el-button class="ml-2" type="primary" @click="onSearch">筛选</el-button>
+                <el-button type="primary" plain @click="onReset">重置</el-button>
+              </el-space>
+            </el-col>
+            <el-col :span="6">
+              <div class="flex justify-end text-right">
+                <el-space wrap>
+                  <!-- <el-button type="primary" plain @click="state.visibleRecordOfError = true" link class="mr-2"
+                    >获取账单生成历史记录</el-button
+                  > -->
+                  <el-input v-model="state.query.key" placeholder="关键词,可以搜索备注" style="width: 225px" />
+                  <el-button :icon="Search" @click="onSearch">查询</el-button>
+                </el-space>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-card>
+      <el-table :data="state.list" style="width: 100%" border>
+        <el-table-column prop="startTime" label="账单周期">
+          <template #default="{ row }">
+            <div class="flex">
+              <div>{{ row.startTime?.split(" ")[0] }}</div>
+              <div class="mx-2">至</div>
+              <div>{{ row.endTime?.split(" ")[0] }}</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="archiveId" label="账单所属第三方名称">
+          <template #default="{ row }">
+            <div class="flex">
+              <div>{{ row.agency?.nickname }}【{{ row.agency?.id }}】</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          prop="agencyReceiveBalance"
+          label="累计支付到第三方总金额"
+          :formatter="v => (v.agencyReceiveBalance / 100).toFixed(2)"
+        />
+        <el-table-column
+          prop="toMechanismBalance"
+          label="结算后平台应收款金额"
+          :formatter="v => (v.toMechanismBalance / 100).toFixed(2)"
+        />
+        <el-table-column
+          prop="mechanismReceiveBalance"
+          label="累计支付到平台总金额"
+          :formatter="v => (v.mechanismReceiveBalance / 100).toFixed(2)"
+        />
+        <el-table-column
+          prop="toAgencyBalance"
+          label="结算后第三方应收款金额"
+          :formatter="v => (v.toAgencyBalance / 100).toFixed(2)"
+        />
+        <el-table-column prop="serviceName" label="结算状态">
+          <template #default="{ row }">
+            <div :class="settlementStatusList[row.status - 1].color">
+              {{ settlementStatusList[row.status - 1]?.label }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="agencyRemark" label="备注">
+          <template #default="{ row }">
+            <div v-if="!row.agencyRemark" class="text-gray-400">未填写</div>
+            <div v-else>已填写</div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="serviceName" label="操作">
+          <template #default="{ row }">
+            <el-button type="primary" link @click="handlePreviwDetail(row.id)">账单详情</el-button>
+            <el-button
+              type="primary"
+              link
+              @click="
+                state.rowData = { ...row };
+                state.visibleEeditRemark = true;
+              "
+              >备注信息</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        class="mt-4 justify-end"
+        background
+        layout="prev, pager, next"
+        :total="state.total"
+        :page-size="state.pageSize"
+        @current-change="changePage"
+      />
+      <PreviewPromotionalInvoiceDetailDialog
+        v-model:show="visiblePreview"
+        ref="previewPromotionalInvoiceDetailDialogRef"
+        @success="getList"
+      />
+    </div>
+    <el-dialog title="账单备注" v-model="state.visibleEeditRemark">
+      <div>
+        <Editor placeholder="请输入账单备注" v-model:value="state.rowData.agencyRemark" />
+      </div>
+      <template #footer>
+        <el-button @click="state.visibleEeditRemark = false">取 消</el-button>
+        <el-button type="primary" @click="handleSaveRemark">确 定</el-button>
+      </template>
+    </el-dialog>
+    <DialogRecordTable v-model:show="state.visibleRecordOfError" />
+  </div>
+</template>
+<script setup>
+import { ref, reactive } from "vue";
+
+import { settlementStatusList } from "./constant.ts";
+import request from "@/api";
+
+import PreviewPromotionalInvoiceDetailDialog from "./components/PreviewPromotionalInvoiceDetailDialog.vue";
+import DialogRecordTable from "./components/DialogRecordTable.vue";
+import Editor from "@/components/WangEditor/index.vue";
+
+import { ElMessage } from "element-plus";
+import { Search } from "@element-plus/icons-vue";
+
+const state = reactive({
+  visible: false,
+  visibleRecordOfError: false,
+  rowData: {},
+  query: {
+    page: 1,
+    pageSize: 10, //
+    agencyIds: [],
+    status: [],
+    key: ""
+  },
+  times: [],
+  list: [],
+  total: 0,
+  agencyList: [],
+  visibleEeditRemark: false
+});
+const visiblePreview = ref(false);
+const previewPromotionalInvoiceDetailDialogRef = ref(null);
+const getList = () => {
+  let start = undefined;
+  let end = undefined;
+  if (state.times?.length > 0) {
+    start = state.times[0] + " 00:00:00";
+    end = state.times[1] + " 23:59:59";
+  }
+  request
+    .get("archivesService/agency/orderBill/paginate", {
+      ...state.query,
+      agencyIds: state.query.agencyIds?.join(","),
+      status: state.query?.status?.join(","),
+      start,
+      end
+    })
+    .then(resp => {
+      state.list = resp.data.list;
+      state.total = resp.data.total;
+    });
+};
+const onSearch = () => {
+  state.query.page = 1;
+  getList();
+};
+const onReset = () => {
+  state.times = [];
+  state.query.agencyIds = [];
+  state.query.times = [];
+  state.query.serviceIds = [];
+  state.query.type = 0;
+  state.query.value = "";
+  state.query.key = "";
+  onSearch();
+};
+const handleSaveRemark = () => {
+  request
+    .post("/archivesService/agency/orderBill/updateRemark", {
+      id: state.rowData.id,
+      remark: state.rowData.agencyRemark
+    })
+    .then(resp => {
+      ElMessage.success(resp.message);
+      state.visibleEeditRemark = false;
+      getList();
+    });
+};
+
+const handlePreviwDetail = id => {
+  visiblePreview.value = true;
+  previewPromotionalInvoiceDetailDialogRef.value.initData(id);
+};
+
+const changePage = page => {
+  state.query.page = page;
+  getList();
+};
+
+const initData = () => {
+  getList();
+  request.get("/archivesService/agency/profile").then(resp => {
+    console.log("resp");
+  });
+};
+
+initData();
+</script>
+<style lang="scss">
+.el-select-dropdown__item {
+  height: auto;
+  overflow: auto;
+}
+</style>

+ 326 - 1
src/views/promotion/order.vue

@@ -1,3 +1,328 @@
 <template>
-  <div>订单中心</div>
+  <div>
+    <div>
+      <el-card class="mb-4" shadow="never">
+        <div>
+          <el-row>
+            <el-col :span="18">
+              <el-space wrap>
+                <el-select
+                  v-model="state.query.type"
+                  placeholder="全部订单类型"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option label="会员卡订单" :value="1" />
+                </el-select>
+                <el-select
+                  v-model="state.query.agencyIds"
+                  placeholder="全部渠道"
+                  multiple
+                  collapse-tags
+                  class="w-[180px]"
+                  filterable
+                >
+                  <el-option v-for="item in channelList" :key="item.id" :label="item.name" :value="item.id"> </el-option>
+                </el-select>
+
+                <el-select
+                  v-model="state.query.status"
+                  placeholder="全部订单状态"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option label="微信支付" :value="1" />
+                </el-select>
+                <el-select
+                  v-model="state.query.payType"
+                  placeholder="全部支付方式"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option v-for="item in statusList" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+                <el-select
+                  v-model="state.query.paymentType"
+                  placeholder="全部支付款流向"
+                  clearable
+                  collapse-tags
+                  multiple
+                  filterable
+                  class="w-[180px]"
+                >
+                  <el-option label="平台" :value="1" />
+                  <el-option label="第三方" :value="2" />
+                </el-select>
+                <el-date-picker
+                  v-model="state.times"
+                  type="daterange"
+                  clearable
+                  range-separator="至"
+                  value-format="YYYY-MM-DD"
+                  start-placeholder="开始日期"
+                  end-placeholder="结束日期"
+                />
+                <el-button class="ml-2" type="primary" @click="onSearch">筛选</el-button>
+                <el-button type="primary" plain @click="onReset">重置</el-button>
+              </el-space>
+            </el-col>
+            <el-col :span="6">
+              <div class="flex justify-end text-right">
+                <el-select v-model="state.key" placeholder="请选择" style="width: 150px">
+                  <el-option label="订单号" value="key" />
+                  <el-option label="用户ID" value="user" />
+                  <el-option label="推广商品" value="shop" />
+                </el-select>
+                <el-input
+                  v-model="state.query.value"
+                  placeholder="请输入关联业务单号"
+                  v-if="state.key == 'key'"
+                  class="w-[180px]"
+                  clearable
+                />
+                <SearchArchivesSelect
+                  v-model="state.query.archivesId"
+                  v-else-if="state.key == 'user'"
+                  placeholder="输入姓名或者档案号"
+                  class="w-[180px]"
+                />
+
+                <el-select
+                  v-else-if="state.key == 'shop'"
+                  placeholder="请选择推广商品"
+                  v-model="state.query.promotionGoodsId"
+                  class="w-[180px]"
+                  clearable
+                >
+                  <el-option
+                    v-for="item in promotionGoodsList"
+                    :key="item.memberCard"
+                    :label="item.memberCardName"
+                    :value="item.id"
+                  />
+                </el-select>
+                <el-button :icon="Search" @click="onSearch">查询</el-button>
+              </div>
+            </el-col>
+          </el-row>
+        </div>
+      </el-card>
+      <el-table :data="state.list" style="width: 100%" border>
+        <el-table-column prop="sn" label="订单信息">
+          <template #default="{ row }">
+            <div>
+              <span>{{ row.sn }}</span>
+              <el-tag>【会员卡订单】</el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="promotionSite" label="订单来源信息">
+          <template #default="{ row }">
+            <div>
+              <div>{{ row.agency?.nickname }}</div>
+              <div style="color: #a8abb2">[ {{ row.promotionSite?.name }} ]</div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="promotionGoods" :formatter="v => v.promotionGoods.memberCardName" label="订单商品名称" />
+        <el-table-column prop="balance" label="实付金额">
+          <template #default="{ row }">
+            <div>
+              {{ row.balance / 100 }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="订单状态">
+          <template #default="{ row }">
+            <div :class="statusList[row.status - 1].color">
+              {{ statusList[row.status - 1]?.label }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="支付时间" />
+        <el-table-column prop="payType" label="支付方式" :formatter="() => '微信支付'" />
+        <el-table-column prop="paymentType" label="支付款流向">
+          <template #default="{ row }">
+            <div>
+              {{ row.paymentType === 1 ? "平台" : row?.agency?.nickname }}
+            </div>
+            <div style="color: #a8abb2">
+              [ 商户号:{{ row.mchId }}
+              ]
+              <br />
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="archives" label="用户信息">
+          <template #default="{ row }">
+            <div>
+              {{ row.archives?.name }}
+            </div>
+            <div>
+              <div v-for="(item, index) in row.archives?.accounts" :key="index" class="space-x-4">
+                <!-- <span class="inline-block whitespace-nowrap w-[4em]">{{ accountTypes[item.type] }}</span> -->
+                <span>【{{ item.account }}】</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作">
+          <template #default="{ row }">
+            <el-button
+              type="primary"
+              link
+              @click="
+                visibleViewDetail = true;
+                state.rowData = row;
+              "
+              >详情</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        class="mt-4 justify-end"
+        background
+        layout="prev, pager, next"
+        :total="state.total"
+        :page-size="state.pageSize"
+        @current-change="changePage"
+      />
+    </div>
+    <el-drawer v-model="visibleViewDetail" direction="rtl" title="订单详情" @open="handleOpenViewDetail">
+      <PerviewDetail :orderSn="state.rowData.sn" ref="perviewDetailRef" @success="getList()" />
+    </el-drawer>
+  </div>
 </template>
+<script setup>
+// import { request } from "@/utils";
+import { ref, reactive } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import PerviewDetail from "./components/PerviewDetail.vue";
+import SearchArchivesSelect from "./components/SearchArchivesSelect.vue";
+import { statusList, accountTypes } from "./constant.ts";
+import { promotionSitePaginate } from "@/api/modules/promotion";
+import http from "@/api";
+
+const perviewDetailRef = ref();
+const visibleViewDetail = ref(false);
+const state = reactive({
+  visible: false,
+  visibleSystemInterest: false,
+  rowData: {},
+  key: "user",
+  query: {
+    page: 1,
+    pageSize: 10, //
+    type: 0,
+    status: [],
+    payType: [],
+    paymentType: [],
+    agencyIds: [],
+    name: "", // 搜索项目:user: 用户;outTradeNo:第三方订单号
+    key: "",
+    archivesId: "",
+    promotionGoodsId: []
+  },
+  times: [],
+  list: [],
+  total: 0
+});
+
+const memberCardList = ref([]);
+const channelList = ref([]);
+const getList = () => {
+  let start = undefined;
+  let end = undefined;
+  if (state.times?.length > 0) {
+    start = state.times[0];
+    end = state.times[1];
+  }
+  const agencyIds =
+    state.query.agencyIds.length &&
+    state.query.agencyIds.map(v => {
+      return v[1];
+    });
+  http
+    .get("archivesService/agency/order/paginate", {
+      ...state.query,
+      agencyIds: agencyIds ? agencyIds?.join(",") : undefined,
+      status: state.query.status?.join(","),
+      payType: state.query.payType?.join(","),
+      paymentType: state.query.paymentType?.join(","),
+      start,
+      end
+    })
+    .then(resp => {
+      state.list = resp.data.list;
+      state.total = resp.data.total;
+    });
+};
+
+const getMemberCardList = () => {
+  http
+    .get("archivesService/agency/promotionGoods/list", {
+      status: 1
+    })
+    .then(resp => {
+      memberCardList.value = resp.data;
+    });
+};
+const promotionGoodsList = ref([]);
+const getPromotionGoodsList = () => {
+  http.get("archivesService/agency/promotionGoods/list").then(resp => {
+    promotionGoodsList.value = resp.data?.list;
+  });
+};
+const handleOpenViewDetail = () => {
+  perviewDetailRef.value.initData();
+};
+const onSearch = () => {
+  state.query.page = 1;
+  getList();
+};
+const onReset = () => {
+  state.times = [];
+  state.query.cardIds = [];
+  state.query.times = [];
+  state.query.agencyIds = [];
+  state.query.type = 0;
+  state.query.value = "";
+  state.query.key = "";
+  onSearch();
+};
+const changePage = page => {
+  state.query.page = page;
+  getList();
+};
+const getChannleList = () => {
+  promotionSitePaginate({
+    page: 1,
+    pageSize: 1000
+  }).then(res => {
+    channelList.value = res.data?.list;
+  });
+};
+const initData = () => {
+  getPromotionGoodsList();
+  getMemberCardList();
+  getList();
+  getChannleList();
+};
+initData();
+</script>
+<style lang="scss">
+.el-select-dropdown__item {
+  height: auto;
+  overflow: auto;
+}
+</style>

+ 0 - 8
vite.config.ts

@@ -37,14 +37,6 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
         }
       }
     },
-    server: {
-      host: "0.0.0.0",
-      port: viteEnv.VITE_PORT,
-      open: viteEnv.VITE_OPEN,
-      cors: true,
-      // Load proxy configuration from .env.development
-      proxy: createProxy(viteEnv.VITE_PROXY)
-    },
     plugins: createVitePlugins(viteEnv),
     esbuild: {
       pure: viteEnv.VITE_DROP_CONSOLE ? ["console.log", "debugger"] : []