Prechádzať zdrojové kódy

Merge remote-tracking branch 'yudao/dev' into dev-to-dev

puhui999 1 rok pred
rodič
commit
869bac2501
100 zmenil súbory, kde vykonal 1728 pridanie a 724 odobranie
  1. 2 1
      .eslintrc.js
  2. 25 48
      .vscode/settings.json
  3. 6 6
      README.md
  4. 39 38
      package.json
  5. 17 0
      src/api/bpm/task/index.ts
  6. 2 3
      src/api/mall/promotion/bargain/bargainActivity.ts
  7. 14 0
      src/api/mall/promotion/bargain/bargainHelp.ts
  8. 19 0
      src/api/mall/promotion/bargain/bargainRecord.ts
  9. 5 0
      src/api/mall/promotion/seckill/seckillActivity.ts
  10. 2 2
      src/api/mall/trade/config/index.ts
  11. 17 5
      src/api/mall/trade/order/index.ts
  12. 19 0
      src/api/member/config/index.ts
  13. 0 19
      src/api/member/point/config/index.ts
  14. 5 4
      src/api/member/signin/config/index.ts
  15. 10 0
      src/api/member/user/index.ts
  16. 22 0
      src/api/pay/wallet/index.ts
  17. 72 0
      src/api/statistics/trade.ts
  18. 0 8
      src/api/system/area/index.ts
  19. 5 4
      src/components/Card/src/CardTitle.vue
  20. 11 11
      src/components/CountTo/src/CountTo.vue
  21. 2 2
      src/components/Cropper/src/CropperAvatar.vue
  22. 1 0
      src/components/Descriptions/src/DescriptionsItemLabel.vue
  23. 4 5
      src/components/Dialog/src/Dialog.vue
  24. 2 2
      src/components/Editor/src/Editor.vue
  25. 1 1
      src/components/Form/src/Form.vue
  26. 1 0
      src/components/Icon/src/Icon.vue
  27. 4 4
      src/components/Icon/src/IconSelect.vue
  28. 1 1
      src/components/Infotip/src/Infotip.vue
  29. 1 1
      src/components/InputPassword/src/InputPassword.vue
  30. 1 1
      src/components/Pagination/index.vue
  31. 2 2
      src/components/Qrcode/src/Qrcode.vue
  32. 1 1
      src/components/Table/src/Table.vue
  33. 1 1
      src/components/Tooltip/src/Tooltip.vue
  34. 64 4
      src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue
  35. 4 4
      src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue
  36. 1 1
      src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
  37. 1 1
      src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue
  38. 1 1
      src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue
  39. 26 18
      src/hooks/web/useCrudSchemas.ts
  40. 1 1
      src/layout/Layout.vue
  41. 4 3
      src/layout/components/Breadcrumb/src/Breadcrumb.vue
  42. 1 1
      src/layout/components/Footer/src/Footer.vue
  43. 1 1
      src/layout/components/Logo/src/Logo.vue
  44. 4 4
      src/layout/components/Menu/src/Menu.vue
  45. 1 1
      src/layout/components/Setting/src/Setting.vue
  46. 1 1
      src/layout/components/Setting/src/components/ColorRadioPicker.vue
  47. 17 17
      src/layout/components/Setting/src/components/InterfaceDisplay.vue
  48. 1 1
      src/layout/components/Setting/src/components/LayoutRadioPicker.vue
  49. 1 1
      src/layout/components/TabMenu/src/TabMenu.vue
  50. 8 8
      src/layout/components/TagsView/src/TagsView.vue
  51. 2 2
      src/layout/components/UserInfo/src/UserInfo.vue
  52. 7 7
      src/layout/components/useRenderLayout.tsx
  53. 13 15
      src/router/modules/remaining.ts
  54. 1 1
      src/styles/var.css
  55. 80 24
      src/utils/constants.ts
  56. 3 2
      src/utils/dict.ts
  57. 66 1
      src/utils/formatTime.ts
  58. 3 2
      src/utils/index.ts
  59. 4 3
      src/utils/tree.ts
  60. 10 10
      src/views/Home/Index.vue
  61. 4 4
      src/views/Home/Index2.vue
  62. 17 17
      src/views/Login/Login.vue
  63. 3 3
      src/views/Login/components/LoginForm.vue
  64. 1 1
      src/views/Login/components/LoginFormTitle.vue
  65. 1 1
      src/views/Login/components/QrCodeForm.vue
  66. 1 1
      src/views/Login/components/RegisterForm.vue
  67. 2 2
      src/views/Profile/Index.vue
  68. 9 0
      src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue
  69. 86 0
      src/views/bpm/processInstance/detail/TaskDelegateForm.vue
  70. 90 0
      src/views/bpm/processInstance/detail/TaskReturnDialogForm.vue
  71. 12 5
      src/views/bpm/processInstance/detail/index.vue
  72. 1 1
      src/views/infra/build/index.vue
  73. 2 2
      src/views/infra/codegen/PreviewCode.vue
  74. 3 3
      src/views/infra/webSocket/index.vue
  75. 5 5
      src/views/mall/product/category/index.vue
  76. 7 6
      src/views/mall/product/comment/CommentForm.vue
  77. 11 12
      src/views/mall/product/comment/index.vue
  78. 3 3
      src/views/mall/product/property/index.vue
  79. 2 2
      src/views/mall/product/property/value/index.vue
  80. 2 2
      src/views/mall/product/spu/components/SkuList.vue
  81. 3 2
      src/views/mall/product/spu/components/SkuTableSelect.vue
  82. 1 1
      src/views/mall/product/spu/components/SpuTableSelect.vue
  83. 16 31
      src/views/mall/product/spu/form/BasicInfoForm.vue
  84. 1 1
      src/views/mall/product/spu/form/OtherSettingsForm.vue
  85. 2 2
      src/views/mall/product/spu/form/index.vue
  86. 54 82
      src/views/mall/product/spu/index.vue
  87. 7 6
      src/views/mall/promotion/bargain/activity/BargainActivityForm.vue
  88. 2 21
      src/views/mall/promotion/bargain/activity/bargainActivity.data.ts
  89. 195 70
      src/views/mall/promotion/bargain/activity/index.vue
  90. 90 0
      src/views/mall/promotion/bargain/record/BargainRecordListDialog.vue
  91. 195 0
      src/views/mall/promotion/bargain/record/index.vue
  92. 1 0
      src/views/mall/promotion/combination/activity/CombinationActivityForm.vue
  93. 1 1
      src/views/mall/promotion/combination/activity/index.vue
  94. 1 1
      src/views/mall/promotion/components/SpuAndSkuList.vue
  95. 1 1
      src/views/mall/promotion/components/SpuSelect.vue
  96. 1 1
      src/views/mall/promotion/coupon/formatter.ts
  97. 11 5
      src/views/mall/promotion/coupon/index.vue
  98. 15 13
      src/views/mall/promotion/coupon/template/CouponTemplateForm.vue
  99. 18 21
      src/views/mall/promotion/coupon/template/index.vue
  100. 211 90
      src/views/mall/promotion/seckill/activity/index.vue

+ 2 - 1
.eslintrc.js

@@ -22,7 +22,8 @@ module.exports = defineConfig({
     'plugin:vue/vue3-recommended',
     'plugin:@typescript-eslint/recommended',
     'prettier',
-    'plugin:prettier/recommended'
+    'plugin:prettier/recommended', 
+    '@unocss'
   ],
   rules: {
     'vue/script-setup-uses-vars': 'error',

+ 25 - 48
.vscode/settings.json

@@ -1,7 +1,5 @@
 {
   "typescript.tsdk": "./node_modules/typescript/lib",
-  "volar.tsPlugin": true,
-  "volar.tsPluginStatus": false,
   "npm.packageManager": "pnpm",
   "editor.tabSize": 2,
   "prettier.printWidth": 100, // 超过最大值换行
@@ -102,57 +100,35 @@
   "i18n-ally.displayLanguage": "zh-CN",
   "i18n-ally.enabledFrameworks": ["vue", "react"],
   "cSpell.words": [
-    "xingyu",
-    "yudao",
-    "unocss",
+    "brotli",
     "browserslist",
+    "codemirror",
+    "commitlint",
+    "cropperjs",
+    "echarts",
     "esnext",
-    "unplugin",
-    "qrcode",
-    "sider",
+    "esno",
+    "iconify",
+    "INTLIFY",
+    "lintstagedrc",
+    "logicflow",
+    "nprogress",
     "pinia",
+    "pnpm",
+    "qrcode",
     "sider",
-    "nprogress",
-    "INTLIFY",
-    "stylelint",
-    "esno",
-    "vitejs",
     "sortablejs",
-    "codemirror",
-    "iconify",
-    "commitlint",
+    "stylelint",
+    "unocss",
+    "unplugin",
+    "unref",
     "videojs",
-    "echarts",
-    "wangeditor",
-    "cropperjs",
-    "logicflow",
+    "vitejs",
     "vueuse",
-    "zxcvbn",
-    "lintstagedrc",
-    "brotli",
-    "sider",
-    "pnpm"
-  ],
-  "vetur.format.scriptInitialIndent": true,
-  "vetur.format.styleInitialIndent": true,
-  "vetur.validation.script": false,
-  "MicroPython.executeButton": [
-    {
-      "text": "▶",
-      "tooltip": "运行",
-      "alignment": "left",
-      "command": "extension.executeFile",
-      "priority": 3.5
-    }
-  ],
-  "MicroPython.syncButton": [
-    {
-      "text": "$(sync)",
-      "tooltip": "同步",
-      "alignment": "left",
-      "command": "extension.execute",
-      "priority": 4
-    }
+    "wangeditor",
+    "xingyu",
+    "yudao",
+    "zxcvbn"
   ],
   // 控制相关文件嵌套展示
   "explorer.fileNesting.enabled": true,
@@ -161,7 +137,8 @@
     "*.ts": "$(capture).test.ts, $(capture).test.tsx",
     "*.tsx": "$(capture).test.ts, $(capture).test.tsx",
     "*.env": "$(capture).env.*",
-    "package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
+    "package.json": "pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore"
   },
-  "terminal.integrated.scrollback": 10000
+  "terminal.integrated.scrollback": 10000,
+  "nuxt.isNuxtApp": false
 }

+ 6 - 6
README.md

@@ -9,7 +9,7 @@
 
 ## 🐶 新手必读
 
-* nodejs > 16.0.0 && pnpm > 8.6.0 (强制使用pnpm)
+* nodejs > 16.18.0 && pnpm > 8.6.0 (强制使用pnpm)
 * 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
 * 演示地址【Vue3 + vben(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
 * 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
@@ -40,13 +40,13 @@
 |----------------------------------------------------------------------|------------------|--------|
 | [Vue](https://staging-cn.vuejs.org/)                                 | Vue 框架           | 3.3.4 |
 | [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具          | 4.4.9  |
-| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus     | 2.3.12 |
-| [TypeScript](https://www.typescriptlang.org/docs/)                   | JavaScript 的超集   | 5.1.6  |
+| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus     | 2.3.14 |
+| [TypeScript](https://www.typescriptlang.org/docs/)                   | JavaScript 的超集   | 5.2.2  |
 | [pinia](https://pinia.vuejs.org/)                                    | Vue 存储库 替代 vuex5 | 2.1.6 |
 | [vueuse](https://vueuse.org/)                                        | 常用工具集            | 10.4.1 |
-| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化              | 9.2.2  |
-| [vue-router](https://router.vuejs.org/)                              | Vue 路由           | 4.2.4  |
-| [unocss](https://uno.antfu.me/)                                      | 原子 css          | 0.55.3  |
+| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化              | 9.4.1  |
+| [vue-router](https://router.vuejs.org/)                              | Vue 路由           | 4.2.5  |
+| [unocss](https://uno.antfu.me/)                                      | 原子 css          | 0.56.1  |
 | [iconify](https://icon-sets.iconify.design/)                         | 在线图标库            | 3.1.1  |
 | [wangeditor](https://www.wangeditor.com/)                            | 富文本编辑器           | 5.1.23 |
 

+ 39 - 38
package.json

@@ -1,6 +1,6 @@
 {
   "name": "yudao-ui-admin-vue3",
-  "version": "1.8.0-snapshot",
+  "version": "1.8.2-snapshot",
   "description": "基于vue3、vite4、element-plus、typesScript",
   "author": "xingyu",
   "private": false,
@@ -22,32 +22,32 @@
     "clean:cache": "npx rimraf node_modules/.cache",
     "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
     "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
-    "lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
+    "lint:style": "stylelint --fix \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
     "lint:lint-staged": "lint-staged -c "
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
-    "@form-create/designer": "^3.1.0",
-    "@form-create/element-ui": "^3.1.17",
+    "@form-create/designer": "^3.1.3",
+    "@form-create/element-ui": "^3.1.24",
     "@iconify/iconify": "^3.1.1",
     "@videojs-player/vue": "^1.0.0",
     "@vueuse/core": "^10.4.1",
     "@wangeditor/editor": "^5.1.23",
     "@wangeditor/editor-for-vue": "^5.1.10",
-    "@zxcvbn-ts/core": "^3.0.3",
+    "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
     "axios": "^1.5.0",
     "benz-amr-recorder": "^1.1.5",
     "bpmn-js-token-simulation": "^0.10.0",
     "camunda-bpmn-moddle": "^7.0.1",
-    "cropperjs": "^1.6.0",
+    "cropperjs": "^1.6.1",
     "crypto-js": "^4.1.1",
-    "dayjs": "^1.11.9",
+    "dayjs": "^1.11.10",
     "diagram-js": "^12.3.0",
     "echarts": "^5.4.3",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "2.3.12",
-    "fast-xml-parser": "^4.2.7",
+    "element-plus": "2.3.14",
+    "fast-xml-parser": "^4.3.0",
     "highlight.js": "^11.8.0",
     "intro.js": "^7.2.0",
     "jsencrypt": "^3.3.2",
@@ -59,12 +59,12 @@
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
     "steady-xml": "^0.1.0",
-    "url": "^0.11.1",
+    "url": "^0.11.3",
     "video.js": "^7.21.5",
-    "vue": "3.3.4",
+    "vue": "^3.3.4",
     "vue-dompurify-html": "^4.1.4",
-    "vue-i18n": "9.2.2",
-    "vue-router": "^4.2.4",
+    "vue-i18n": "^9.4.1",
+    "vue-router": "^4.2.5",
     "vue-types": "^5.1.1",
     "vuedraggable": "^4.1.0",
     "web-storage-cache": "^1.1.1",
@@ -73,50 +73,51 @@
   "devDependencies": {
     "@commitlint/cli": "^17.7.1",
     "@commitlint/config-conventional": "^17.7.0",
-    "@iconify/json": "^2.2.107",
-    "@intlify/unplugin-vue-i18n": "^0.12.3",
+    "@iconify/json": "^2.2.119",
+    "@intlify/unplugin-vue-i18n": "^1.2.0",
     "@purge-icons/generated": "^0.9.0",
     "@types/intro.js": "^5.1.1",
-    "@types/lodash-es": "^4.17.8",
-    "@types/node": "^20.5.0",
+    "@types/lodash-es": "^4.17.9",
+    "@types/node": "^20.6.0",
     "@types/nprogress": "^0.2.0",
-    "@types/qrcode": "^1.5.1",
-    "@types/qs": "^6.9.7",
-    "@typescript-eslint/eslint-plugin": "^6.4.1",
-    "@typescript-eslint/parser": "^6.4.1",
-    "@unocss/transformer-variant-group": "^0.55.3",
+    "@types/qrcode": "^1.5.2",
+    "@types/qs": "^6.9.8",
+    "@typescript-eslint/eslint-plugin": "^6.7.2",
+    "@typescript-eslint/parser": "^6.7.2",
+    "@unocss/transformer-variant-group": "^0.56.1",
+    "@unocss/eslint-config": "^0.56.1",
     "@vitejs/plugin-legacy": "^4.1.1",
-    "@vitejs/plugin-vue": "^4.3.3",
+    "@vitejs/plugin-vue": "^4.3.4",
     "@vitejs/plugin-vue-jsx": "^3.0.2",
-    "@vue-macros/volar": "^0.14.2",
-    "autoprefixer": "^10.4.15",
-    "bpmn-js": "^8.9.0",
-    "bpmn-js-properties-panel": "^0.46.0",
+    "@vue-macros/volar": "^0.14.3",
+    "autoprefixer": "^10.4.16",
+    "bpmn-js": "8.9.0",
+    "bpmn-js-properties-panel": "0.46.0",
     "consola": "^3.2.3",
-    "eslint": "^8.48.0",
+    "eslint": "^8.49.0",
     "eslint-config-prettier": "^9.0.0",
     "eslint-define-config": "^1.23.0",
     "eslint-plugin-prettier": "^5.0.0",
     "eslint-plugin-vue": "^9.17.0",
     "lint-staged": "^14.0.1",
-    "postcss": "^8.4.28",
+    "postcss": "^8.4.30",
     "postcss-html": "^1.5.0",
-    "postcss-scss": "^4.0.7",
-    "prettier": "^3.0.2",
+    "postcss-scss": "^4.0.8",
+    "prettier": "^3.0.3",
     "rimraf": "^5.0.1",
-    "rollup": "^3.28.1",
-    "sass": "^1.66.1",
+    "rollup": "^3.29.2",
+    "sass": "^1.68.0",
     "stylelint": "^15.10.3",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-recommended": "^13.0.0",
     "stylelint-config-standard": "^34.0.0",
     "stylelint-order": "^6.0.3",
-    "terser": "^5.19.2",
-    "typescript": "5.1.6",
-    "unocss": "^0.55.3",
+    "terser": "^5.20.0",
+    "typescript": "5.2.2",
+    "unocss": "^0.56.1",
     "unplugin-auto-import": "^0.16.6",
     "unplugin-element-plus": "^0.8.0",
-    "unplugin-vue-components": "^0.25.1",
+    "unplugin-vue-components": "^0.25.2",
     "vite": "4.4.9",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-ejs": "^1.6.4",
@@ -126,7 +127,7 @@
     "vite-plugin-svg-icons": "^2.0.1",
     "vite-plugin-top-level-await": "^1.3.1",
     "vue-eslint-parser": "^9.3.1",
-    "vue-tsc": "^1.8.8"
+    "vue-tsc": "^1.8.13"
   },
   "license": "MIT",
   "repository": {

+ 17 - 0
src/api/bpm/task/index.ts

@@ -41,3 +41,20 @@ export const getTaskListByProcessInstanceId = async (processInstanceId) => {
 export const exportTask = async (params) => {
   return await request.download({ url: '/bpm/task/export', params })
 }
+
+// 获取所有可回退的节点
+export const getReturnList = async (params) => {
+  return await request.get({ url: '/bpm/task/get-return-list', params })
+}
+
+// 回退
+export const returnTask = async (data) => {
+  return await request.put({ url: '/bpm/task/return', data })
+}
+
+/**
+ * 委派
+ */
+export const delegateTask = async (data) => {
+  return await request.put({ url: '/bpm/task/delegate', data })
+}

+ 2 - 3
src/api/mall/promotion/bargain/bargainActivity.ts

@@ -7,17 +7,16 @@ export interface BargainActivityVO {
   startTime?: Date
   endTime?: Date
   status?: number
-  userSize?: number // 达到该人数,才能砍到低价
+  helpMaxCount?: number // 达到该人数,才能砍到低价
   bargainCount?: number // 最大帮砍次数
   totalLimitCount?: number // 最大购买次数
   spuId: number
   skuId: number
   bargainFirstPrice: number // 砍价起始价格,单位分
-  bargainPrice: number // 砍价底价
+  bargainMinPrice: number // 砍价底价
   stock: number // 活动库存
   randomMinPrice?: number // 用户每次砍价的最小金额,单位:分
   randomMaxPrice?: number // 用户每次砍价的最大金额,单位:分
-  successCount?: number // 砍价成功数量
 }
 
 // 砍价活动所需属性。选择的商品和属性的时候使用方便使用活动的通用封装

+ 14 - 0
src/api/mall/promotion/bargain/bargainHelp.ts

@@ -0,0 +1,14 @@
+import request from '@/config/axios'
+
+export interface BargainHelpVO {
+  id: number
+  record: number
+  userId: number
+  reducePrice: number
+  endTime: Date
+}
+
+// 查询砍价记录列表
+export const getBargainHelpPage = async (params) => {
+  return await request.get({ url: `/promotion/bargain-help/page`, params })
+}

+ 19 - 0
src/api/mall/promotion/bargain/bargainRecord.ts

@@ -0,0 +1,19 @@
+import request from '@/config/axios'
+
+export interface BargainRecordVO {
+  id: number
+  activityId: number
+  userId: number
+  spuId: number
+  skuId: number
+  bargainFirstPrice: number
+  bargainPrice: number
+  status: number
+  orderId: number
+  endTime: Date
+}
+
+// 查询砍价记录列表
+export const getBargainRecordPage = async (params) => {
+  return await request.get({ url: `/promotion/bargain-record/page`, params })
+}

+ 5 - 0
src/api/mall/promotion/seckill/seckillActivity.ts

@@ -57,6 +57,11 @@ export const updateSeckillActivity = async (data: SeckillActivityVO) => {
   return await request.put({ url: '/promotion/seckill-activity/update', data })
 }
 
+// 关闭秒杀活动
+export const closeSeckillActivity = async (id: number) => {
+  return await request.put({ url: '/promotion/seckill-activity/close?id=' + id })
+}
+
 // 删除秒杀活动
 export const deleteSeckillActivity = async (id: number) => {
   return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id })

+ 2 - 2
src/api/mall/trade/config/index.ts

@@ -4,13 +4,13 @@ export interface ConfigVO {
   brokerageEnabled: boolean
   brokerageEnabledCondition: number
   brokerageBindMode: number
-  brokeragePostUrls: string
+  brokeragePosterUrls: string
   brokerageFirstPercent: number
   brokerageSecondPercent: number
   brokerageWithdrawMinPrice: number
   brokerageBankNames: string
   brokerageFrozenDays: number
-  brokerageWithdrawType: string
+  brokerageWithdrawTypes: string
 }
 
 // 查询交易中心配置详情

+ 17 - 5
src/api/mall/trade/order/index.ts

@@ -41,15 +41,22 @@ export interface OrderVO {
   refundPrice?: number | null // 退款金额
   couponId?: number | null // 优惠劵编号
   couponPrice?: number | null // 优惠劵减免金额
+  vipPrice?: number | null // VIP 减免金额
   pointPrice?: number | null // 积分抵扣的金额
   receiverAreaName?: string //收件人地区名字
   items?: OrderItemRespVO[] // 订单项列表
-  // 用户信息
+  // 下单用户信息
   user?: {
     id?: number | null
     nickname?: string
     avatar?: string
   }
+  // 推广用户信息
+  brokerageUser?: {
+    id?: number | null
+    nickname?: string
+    avatar?: string
+  }
   // 订单操作日志
   logs?: OrderLogRespVO[]
 }
@@ -114,21 +121,26 @@ export interface DeliveryVO {
 }
 
 // 订单发货
-export const delivery = async (data: DeliveryVO) => {
+export const deliveryOrder = async (data: DeliveryVO) => {
   return await request.put({ url: `/trade/order/delivery`, data })
 }
 
 // 订单备注
-export const updateRemark = async (data: any) => {
+export const updateOrderRemark = async (data: any) => {
   return await request.put({ url: `/trade/order/update-remark`, data })
 }
 
 // 订单调价
-export const updatePrice = async (data: any) => {
+export const updateOrderPrice = async (data: any) => {
   return await request.put({ url: `/trade/order/update-price`, data })
 }
 
 // 修改订单地址
-export const updateAddress = async (data: any) => {
+export const updateOrderAddress = async (data: any) => {
   return await request.put({ url: `/trade/order/update-address`, data })
 }
+
+// 订单核销
+export const pickUpOrder = async (id: number) => {
+  return await request.put({ url: `/trade/order/pick-up?id=${id}` })
+}

+ 19 - 0
src/api/member/config/index.ts

@@ -0,0 +1,19 @@
+import request from '@/config/axios'
+
+export interface ConfigVO {
+  id: number
+  pointTradeDeductEnable: number
+  pointTradeDeductUnitPrice: number
+  pointTradeDeductMaxPrice: number
+  pointTradeGivePoint: number
+}
+
+// 查询积分设置详情
+export const getConfig = async () => {
+  return await request.get({ url: `/member/config/get` })
+}
+
+// 新增修改积分设置
+export const saveConfig = async (data: ConfigVO) => {
+  return await request.put({ url: `/member/config/save`, data })
+}

+ 0 - 19
src/api/member/point/config/index.ts

@@ -1,19 +0,0 @@
-import request from '@/config/axios'
-
-export interface ConfigVO {
-  id: number
-  tradeDeductEnable: number
-  tradeDeductUnitPrice: number
-  tradeDeductMaxPrice: number
-  tradeGivePoint: number
-}
-
-// 查询积分设置详情
-export const getConfig = async () => {
-  return await request.get({ url: `/member/point/config/get` })
-}
-
-// 新增修改积分设置
-export const saveConfig = async (data: ConfigVO) => {
-  return await request.put({ url: `/member/point/config/save`, data })
-}

+ 5 - 4
src/api/member/signin/config/index.ts

@@ -1,10 +1,11 @@
 import request from '@/config/axios'
 
 export interface SignInConfigVO {
-  id: number
-  day: number | null
-  point: number | null
-  enable: boolean | null
+  id?: number
+  day?: number
+  point?: number
+  experience?: number
+  status?: number
 }
 
 // 查询积分签到规则列表

+ 10 - 0
src/api/member/user/index.ts

@@ -41,3 +41,13 @@ export const updateUser = async (data: UserVO) => {
 export const updateUserLevel = async (data: any) => {
   return await request.put({ url: `/member/user/update-level`, data })
 }
+
+// 修改会员用户积分
+export const updateUserPoint = async (data: any) => {
+  return await request.put({ url: `/member/user/update-point`, data })
+}
+
+// 修改会员用户余额
+export const updateUserBalance = async (data: any) => {
+  return await request.put({ url: `/member/user/update-balance`, data })
+}

+ 22 - 0
src/api/pay/wallet/index.ts

@@ -0,0 +1,22 @@
+import request from '@/config/axios'
+
+/** 用户钱包查询参数 */
+export interface PayWalletUserReqVO {
+  userId: number
+  userType: number
+}
+/** 钱包 VO */
+export interface WalletVO {
+  id: number
+  userId: number
+  userType: number
+  balance: number
+  totalExpense: number
+  totalRecharge: number
+  freezePrice: number
+}
+
+/** 查询用户钱包详情 */
+export const getWallet = async (params: PayWalletUserReqVO) => {
+  return await request.get<WalletVO>({ url: `/pay/wallet/get`, params })
+}

+ 72 - 0
src/api/statistics/trade.ts

@@ -0,0 +1,72 @@
+import request from '@/config/axios'
+import dayjs from 'dayjs'
+import { formatDate } from '@/utils/formatTime'
+
+// todo @疯狂:挪到 mall 里哈
+
+/** 交易统计对照 Response VO */
+export interface TradeStatisticsComparisonRespVO<T> {
+  value: T
+  reference: T
+}
+
+/** 交易统计 Response VO */
+export interface TradeSummaryRespVO {
+  yesterdayOrderCount: number
+  monthOrderCount: number
+  yesterdayPayPrice: number
+  monthPayPrice: number
+}
+
+/** 交易状况 Request VO */
+export interface TradeTrendReqVO {
+  times: [dayjs.ConfigType, dayjs.ConfigType]
+}
+
+/** 交易状况统计 Response VO */
+export interface TradeTrendSummaryRespVO {
+  time: string
+  turnover: number
+  orderPayPrice: number
+  rechargePrice: number
+  expensePrice: number
+  balancePrice: number
+  brokerageSettlementPrice: number
+  orderRefundPrice: number
+}
+
+// 查询交易统计
+export const getTradeStatisticsSummary = () => {
+  return request.get<TradeStatisticsComparisonRespVO<TradeSummaryRespVO>>({
+    url: '/statistics/trade/summary'
+  })
+}
+
+// 获得交易状况统计
+export const getTradeTrendSummary = (params: TradeTrendReqVO) => {
+  return request.get<TradeStatisticsComparisonRespVO<TradeTrendSummaryRespVO>>({
+    url: '/statistics/trade/trend/summary',
+    params: formatDateParam(params)
+  })
+}
+
+// 获得交易状况明细
+export const getTradeTrendList = (params: TradeTrendReqVO) => {
+  return request.get<TradeTrendSummaryRespVO[]>({
+    url: '/statistics/trade/trend/list',
+    params: formatDateParam(params)
+  })
+}
+
+// 导出交易状况明细
+export const exportTradeTrend = (params: TradeTrendReqVO) => {
+  return request.download({
+    url: '/statistics/trade/trend/export-excel',
+    params: formatDateParam(params)
+  })
+}
+
+/** 时间参数需要格式化, 确保接口能识别 */
+const formatDateParam = (params: TradeTrendReqVO) => {
+  return { times: [formatDate(params.times[0]), formatDate(params.times[1])] } as TradeTrendReqVO
+}

+ 0 - 8
src/api/system/area/index.ts

@@ -5,14 +5,6 @@ export const getAreaTree = async () => {
   return await request.get({ url: '/system/area/tree' })
 }
 
-export const getChildrenArea = async (id: number) => {
-  return await request.get({ url: '/system/area/get-children?id=' + id })
-}
-
-export const getAreaListByIds = async (ids) => {
-  return await request.get({ url: '/system/area/get-by-ids?ids=' + ids })
-}
-
 // 获得 IP 对应的地区名
 export const getAreaByIp = async (ip: string) => {
   return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })

+ 5 - 4
src/components/Card/src/CardTitle.vue

@@ -19,17 +19,18 @@ const { title } = defineProps({
 .card-title {
   font-size: 14px;
   font-weight: 600;
+
   &::before {
-    content: '';
+    position: relative;
+    top: 8px;
+    left: -5px;
     display: inline-block;
     width: 3px;
     height: 14px;
     //background-color: #105cfb;
     background: var(--el-color-primary);
-    position: relative;
-    left: -5px;
-    top: 8px;
     border-radius: 5px;
+    content: '';
     transform: translateY(-50%);
   }
 }

+ 11 - 11
src/components/CountTo/src/CountTo.vue

@@ -11,21 +11,21 @@ const { getPrefixCls } = useDesign()
 const prefixCls = getPrefixCls('count-to')
 
 const props = defineProps({
-  startVal: propTypes.number.def(0),
-  endVal: propTypes.number.def(2021),
-  duration: propTypes.number.def(3000),
-  autoplay: propTypes.bool.def(true),
-  decimals: propTypes.number.validate((value: number) => value >= 0).def(0),
-  decimal: propTypes.string.def('.'),
-  separator: propTypes.string.def(','),
-  prefix: propTypes.string.def(''),
-  suffix: propTypes.string.def(''),
-  useEasing: propTypes.bool.def(true),
+  startVal: propTypes.number.def(0), // 开始播放值
+  endVal: propTypes.number.def(2021), // 最终值
+  duration: propTypes.number.def(3000), // 动画时长
+  autoplay: propTypes.bool.def(true), // 是否自动播放动画, 默认播放
+  decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // 显示的小数位数, 默认不显示小数
+  decimal: propTypes.string.def('.'), // 小数分隔符号, 默认为点
+  separator: propTypes.string.def(','), // 数字每三位的分隔符, 默认为逗号
+  prefix: propTypes.string.def(''), // 前缀, 数值前面显示的内容
+  suffix: propTypes.string.def(''), // 后缀, 数值后面显示的内容
+  useEasing: propTypes.bool.def(true), // 是否使用缓动效果, 默认启用
   easingFn: {
     type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
     default(t: number, b: number, c: number, d: number) {
       return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
-    }
+    } // 缓动函数
   }
 })
 

+ 2 - 2
src/components/Cropper/src/CropperAvatar.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="user-info-head" @click="open()">
-    <img v-if="sourceValue" :src="sourceValue" alt="avatar" class="img-circle img-lg" />
-    <img v-if="!sourceValue" :src="avatar" alt="avatar" class="img-circle img-lg" />
+    <el-avatar v-if="sourceValue" :src="sourceValue" alt="avatar" class="img-circle img-lg" />
+    <el-avatar v-if="!sourceValue" :src="avatar" alt="avatar" class="img-circle img-lg" />
     <el-button v-if="showBtn" :class="`${prefixCls}-upload-btn`" @click="open()">
       {{ btnText ? btnText : t('cropper.selectImage') }}
     </el-button>

+ 1 - 0
src/components/Descriptions/src/DescriptionsItemLabel.vue

@@ -22,6 +22,7 @@ const { label } = defineProps({
 .cell-item {
   display: inline;
 }
+
 .cell-item::after {
   content: ':';
 }

+ 4 - 5
src/components/Dialog/src/Dialog.vue

@@ -66,28 +66,27 @@ const dialogStyle = computed(() => {
     destroy-on-close
     lock-scroll
     draggable
-    top="0"
     class="com-dialog"
     :show-close="false"
   >
     <template #header="{ close }">
-      <div class="flex justify-between items-center h-54px pl-15px pr-15px relative">
+      <div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
         <slot name="title">
           {{ title }}
         </slot>
         <div
-          class="h-54px flex justify-between items-center absolute top-[50%] right-15px translate-y-[-50%]"
+          class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between"
         >
           <Icon
             v-if="fullscreen"
-            class="cursor-pointer is-hover mr-10px"
+            class="is-hover mr-10px cursor-pointer"
             :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
             color="var(--el-color-info)"
             hover-color="var(--el-color-primary)"
             @click="toggleFull"
           />
           <Icon
-            class="cursor-pointer is-hover"
+            class="is-hover cursor-pointer"
             icon="ep:close"
             hover-color="var(--el-color-primary)"
             color="var(--el-color-info)"

+ 2 - 2
src/components/Editor/src/Editor.vue

@@ -180,12 +180,12 @@ defineExpose({
 </script>
 
 <template>
-  <div class="border-1 border-solid border-[var(--el-border-color)] z-99">
+  <div class="z-99 border-1 border-[var(--el-border-color)] border-solid">
     <!-- 工具栏 -->
     <Toolbar
       :editor="editorRef"
       :editorId="editorId"
-      class="border-0 b-b-1 border-solid border-[var(--el-border-color)]"
+      class="border-0 b-b-1 border-[var(--el-border-color)] border-solid"
     />
     <!-- 编辑器 -->
     <Editor

+ 1 - 1
src/components/Form/src/Form.vue

@@ -203,7 +203,7 @@ export default defineComponent({
                       icon="ep:warning"
                       size={16}
                       color="var(--el-color-primary)"
-                      class="ml-2px relative top-1px"
+                      class="relative top-1px ml-2px"
                     ></Icon>
                   )
                 }}

+ 1 - 0
src/components/Icon/src/Icon.vue

@@ -32,6 +32,7 @@ const getIconifyStyle = computed(() => {
   const { color, size } = props
   return {
     fontSize: `${size}px`,
+    height: '1em',
     color
   }
 })

+ 4 - 4
src/components/Icon/src/IconSelect.vue

@@ -128,7 +128,7 @@ watch(
         >
           <template #reference>
             <div
-              class="w-40px h-32px cursor-pointer flex justify-center items-center"
+              class="h-32px w-40px flex cursor-pointer items-center justify-center"
               @click="visible = !visible"
             >
               <Icon :icon="currentActiveType + icon" />
@@ -147,13 +147,13 @@ watch(
             >
               <ElDivider border-style="dashed" class="tab-divider" />
               <ElScrollbar height="220px">
-                <ul class="flex flex-wrap px-2 ml-2">
+                <ul class="ml-2 flex flex-wrap px-2">
                   <li
                     v-for="(item, key) in pageList"
                     :key="key"
                     :style="iconItemStyle(item)"
                     :title="item"
-                    class="icon-item p-2 w-1/10 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
+                    class="icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2"
                     @click="onChangeIcon(item)"
                   >
                     <Icon :icon="currentActiveType + item" />
@@ -169,7 +169,7 @@ watch(
             :page-size="pageSize"
             :total="iconCount"
             background
-            class="flex items-center justify-center h-10"
+            class="h-10 flex items-center justify-center"
             layout="prev, pager, next"
             small
             @current-change="onCurrentChange"

+ 1 - 1
src/components/Infotip/src/Infotip.vue

@@ -40,7 +40,7 @@ const keyClick = (key: string) => {
       <span :class="[`${prefixCls}__title`, 'pl-5px text-16px font-bold']">{{ title }}</span>
     </div>
     <div :class="`${prefixCls}__content`">
-      <p v-for="(item, $index) in schema" :key="$index" class="text-14px mt-15px">
+      <p v-for="(item, $index) in schema" :key="$index" class="mt-15px text-14px">
         <Highlight
           :color="highlightColor"
           :keys="typeof item === 'string' ? [] : item.keys"

+ 1 - 1
src/components/InputPassword/src/InputPassword.vue

@@ -67,7 +67,7 @@ const getIconName = computed(() => (unref(textType) === 'password' ? 'ep:hide' :
     <div
       v-if="strength"
       :class="`${prefixCls}__bar`"
-      class="relative h-6px mt-10px mb-6px mr-auto ml-auto"
+      class="relative mb-6px ml-auto mr-auto mt-10px h-6px"
     >
       <div :class="`${prefixCls}__bar--fill`" :data-score="getPasswordStrength"></div>
     </div>

+ 1 - 1
src/components/Pagination/index.vue

@@ -9,7 +9,7 @@
     :pager-count="pagerCount"
     :total="total"
     :small="isSmall"
-    class="float-right mt-15px mb-15px"
+    class="float-right mb-15px mt-15px"
     layout="total, sizes, prev, pager, next, jumper"
     @size-change="handleSizeChange"
     @current-change="handleCurrentChange"

+ 2 - 2
src/components/Qrcode/src/Qrcode.vue

@@ -227,10 +227,10 @@ const disabledClick = () => {
     <div
       v-if="disabled"
       :class="`${prefixCls}--disabled`"
-      class="absolute top-0 left-0 flex w-full h-full items-center justify-center"
+      class="absolute left-0 top-0 h-full w-full flex items-center justify-center"
       @click="disabledClick"
     >
-      <div class="absolute top-[50%] left-[50%] font-bold">
+      <div class="absolute left-[50%] top-[50%] font-bold">
         <Icon :size="30" color="var(--el-color-primary)" icon="ep:refresh-right" />
         <div>{{ disabledText }}</div>
       </div>

+ 1 - 1
src/components/Table/src/Table.vue

@@ -289,7 +289,7 @@ export default defineComponent({
           <ElPagination
             v-model:pageSize={pageSizeRef.value}
             v-model:currentPage={currentPageRef.value}
-            class="float-right mt-15px mb-15px"
+            class="float-right mb-15px mt-15px"
             {...unref(pagination)}
           ></ElPagination>
         ) : undefined}

+ 1 - 1
src/components/Tooltip/src/Tooltip.vue

@@ -12,6 +12,6 @@ defineProps({
 <template>
   <span>{{ titel }}</span>
   <ElTooltip :content="message" placement="top">
-    <Icon :icon="icon" class="ml-1px relative top-1px" />
+    <Icon :icon="icon" class="relative top-1px ml-1px" />
   </ElTooltip>
 </template>

+ 64 - 4
src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue

@@ -11,6 +11,7 @@ import BpmnViewer from 'bpmn-js/lib/Viewer'
 import DefaultEmptyXML from './plugins/defaultEmpty'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { formatDate } from '@/utils/formatTime'
+import { isEmpty } from '@/utils/is'
 
 defineOptions({ name: 'MyProcessViewer' })
 
@@ -85,6 +86,7 @@ const createNewDiagram = async (xml) => {
     // console.error(`[Process Designer Warn]: ${e?.message || e}`);
   }
 }
+
 /* 高亮流程图 */
 // TODO 芋艿:如果多个 endActivity 的话,目前的逻辑可能有一定的问题。https://www.jdon.com/workflow/multi-events.html
 const highlightDiagram = async () => {
@@ -97,6 +99,9 @@ const highlightDiagram = async () => {
   let canvas = bpmnModeler.get('canvas')
   let todoActivity: any = activityList.find((m: any) => !m.endTime) // 找到待办的任务
   let endActivity: any = activityList[activityList.length - 1] // 获得最后一个任务
+  let findProcessTask = false //是否已经高亮了进行中的任务
+  //进行中高亮之后的任务 key 集合,用于过滤掉 taskList 进行中后面的任务,避免进行中后面的数据 Hover 还有数据
+  let removeTaskDefinitionKeyList = []
   // debugger
   bpmnModeler.getDefinitions().rootElements[0].flowElements?.forEach((n: any) => {
     let activity: any = activityList.find((m: any) => m.key === n.id) // 找到对应的活动
@@ -110,9 +115,17 @@ const highlightDiagram = async () => {
       if (!task) {
         return
       }
+      //进行中的任务已经高亮过了,则不高亮后面的任务了
+      if (findProcessTask) {
+        removeTaskDefinitionKeyList.push(n.id)
+        return
+      }
       // 高亮任务
       canvas.addMarker(n.id, getResultCss(task.result))
-
+      //标记是否高亮了进行中任务
+      if (task.result === 1) {
+        findProcessTask = true
+      }
       // 如果非通过,就不走后面的线条了
       if (task.result !== 2) {
         return
@@ -212,6 +225,11 @@ const highlightDiagram = async () => {
       }
     }
   })
+  if (!isEmpty(removeTaskDefinitionKeyList)) {
+    taskList.value = taskList.value.filter(
+      (item) => !removeTaskDefinitionKeyList.includes(item.definitionKey)
+    )
+  }
 }
 const getActivityHighlightCss = (activity) => {
   return activity.endTime ? 'highlight' : 'highlight-todo'
@@ -229,6 +247,9 @@ const getResultCss = (result) => {
   } else if (result === 4) {
     // 已取消
     return 'highlight-cancel'
+  } else if (result === 5) {
+    // 退回
+    return 'highlight-return'
   }
   return ''
 }
@@ -273,9 +294,9 @@ const elementHover = (element) => {
   console.log(element.value, 'element.value')
   const activity = activityLists.value.find((m) => m.key === element.value.id)
   console.log(activity, 'activityactivityactivityactivity')
-  // if (!activity) {
-  //   return
-  // }
+  if (!activity) {
+    return
+  }
   if (!elementOverlayIds.value[element.value.id] && element.value.type !== 'bpmn:Process') {
     let html = `<div class="element-overlays">
             <p>Elemet id: ${element.value.id}</p>
@@ -564,6 +585,45 @@ watch(
   stroke: grey !important;
 }
 
+/** 回退 */
+.highlight-return.djs-shape .djs-visual > :nth-child(1) {
+  fill: #e6a23c !important;
+  stroke: #e6a23c !important;
+  fill-opacity: 0.2 !important;
+}
+.highlight-return.djs-shape .djs-visual > :nth-child(2) {
+  fill: #e6a23c !important;
+}
+.highlight-return.djs-shape .djs-visual > path {
+  fill: #e6a23c !important;
+  fill-opacity: 0.2 !important;
+  stroke: #e6a23c !important;
+}
+.highlight-return.djs-connection > .djs-visual > path {
+  stroke: #e6a23c !important;
+}
+
+.highlight-return:not(.djs-connection) .djs-visual > :nth-child(1) {
+  fill: #e6a23c !important; /* color elements as green */
+}
+
+:deep(.highlight-return.djs-shape .djs-visual > :nth-child(1)) {
+  fill: #e6a23c !important;
+  stroke: #e6a23c !important;
+  fill-opacity: 0.2 !important;
+}
+:deep(.highlight-return.djs-shape .djs-visual > :nth-child(2)) {
+  fill: #e6a23c !important;
+}
+:deep(.highlight-return.djs-shape .djs-visual > path) {
+  fill: #e6a23c !important;
+  fill-opacity: 0.2 !important;
+  stroke: #e6a23c !important;
+}
+:deep(.highlight-return.djs-connection > .djs-visual > path) {
+  stroke: #e6a23c !important;
+}
+
 .element-overlays {
   width: 200px;
   padding: 8px;

+ 4 - 4
src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue

@@ -15,7 +15,7 @@
     <!--字段列表-->
     <div class="element-property list-property">
       <el-divider><Icon icon="ep:coin" /> 表单字段</el-divider>
-      <el-table :data="fieldList" max-height="240" border fit>
+      <el-table :data="fieldList" max-height="240" fit border>
         <el-table-column label="序号" type="index" width="50px" />
         <el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />
         <el-table-column
@@ -97,7 +97,7 @@
             >添加枚举值</el-button
           >
         </p>
-        <el-table :data="fieldEnumList" key="enum-table" max-height="240" border fit>
+        <el-table :data="fieldEnumList" key="enum-table" max-height="240" fit border>
           <el-table-column label="序号" width="50px" type="index" />
           <el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />
           <el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />
@@ -130,7 +130,7 @@
           >添加约束</el-button
         >
       </p>
-      <el-table :data="fieldConstraintsList" key="validation-table" max-height="240" border fit>
+      <el-table :data="fieldConstraintsList" key="validation-table" max-height="240" fit border>
         <el-table-column label="序号" width="50px" type="index" />
         <el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />
         <el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />
@@ -162,7 +162,7 @@
           >添加属性</el-button
         >
       </p>
-      <el-table :data="fieldPropertiesList" key="property-table" max-height="240" border fit>
+      <el-table :data="fieldPropertiesList" key="property-table" max-height="240" fit border>
         <el-table-column label="序号" width="50px" type="index" />
         <el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />
         <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />

+ 1 - 1
src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue

@@ -139,8 +139,8 @@
         :data="fieldsListOfListener"
         size="small"
         max-height="240"
-        border
         fit
+        border
         style="flex: none"
       >
         <el-table-column label="序号" width="50px" type="index" />

+ 1 - 1
src/components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue

@@ -184,8 +184,8 @@
         :data="fieldsListOfListener"
         size="small"
         max-height="240"
-        border
         fit
+        border
         style="flex: none"
       >
         <el-table-column label="序号" width="50px" type="index" />

+ 1 - 1
src/components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="panel-tab__content">
-    <el-table :data="elementPropertyList" max-height="240" border fit>
+    <el-table :data="elementPropertyList" max-height="240" fit border>
       <el-table-column label="序号" width="50px" type="index" />
       <el-table-column label="属性名" prop="name" min-width="100px" show-overflow-tooltip />
       <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />

+ 26 - 18
src/hooks/web/useCrudSchemas.ts

@@ -9,7 +9,7 @@ import { TableColumn } from '@/types/table'
 import { DescriptionsSchema } from '@/types/descriptions'
 import { ComponentOptions, ComponentProps } from '@/types/components'
 import { DictTag } from '@/components/DictTag'
-import { cloneDeep } from 'lodash-es'
+import { cloneDeep, merge } from 'lodash-es'
 
 export type CrudSchema = Omit<TableColumn, 'children'> & {
   isSearch?: boolean // 是否在查询显示
@@ -117,14 +117,18 @@ const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): F
         }
         if (!schemaItem.search?.component) component = 'Select'
       }
-      const searchSchemaItem = {
-        // 默认为 input
-        component: component,
-        componentProps: comonentProps,
-        ...schemaItem.search,
-        field: schemaItem.field,
-        label: schemaItem.search?.label || schemaItem.label
-      }
+
+      // updated by AKing: 解决了当使用默认的dict选项时,form中事件不能触发的问题
+      const searchSchemaItem = merge(
+        {
+          // 默认为 input
+          component,
+          ...schemaItem.search,
+          field: schemaItem.field,
+          label: schemaItem.search?.label || schemaItem.label
+        },
+        { componentProps: comonentProps }
+      )
       if (searchSchemaItem.api) {
         searchRequestTask.push(async () => {
           const res = await (searchSchemaItem.api as () => AxiosPromise)()
@@ -224,15 +228,19 @@ const filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): For
         }
         if (!(schemaItem.form && schemaItem.form.component)) component = 'Select'
       }
-      const formSchemaItem = {
-        // 默认为 input
-        component: component,
-        componentProps: comonentProps,
-        value: defaultValue,
-        ...schemaItem.form,
-        field: schemaItem.field,
-        label: schemaItem.form?.label || schemaItem.label
-      }
+
+      // updated by AKing: 解决了当使用默认的dict选项时,form中事件不能触发的问题
+      const formSchemaItem = merge(
+        {
+          // 默认为 input
+          component,
+          value: defaultValue,
+          ...schemaItem.form,
+          field: schemaItem.field,
+          label: schemaItem.form?.label || schemaItem.label
+        },
+        { componentProps: comonentProps }
+      )
 
       if (formSchemaItem.api) {
         formRequestTask.push(async () => {

+ 1 - 1
src/layout/Layout.vue

@@ -50,7 +50,7 @@ export default defineComponent({
       <section class={[prefixCls, `${prefixCls}__${layout.value}`, 'w-[100%] h-[100%] relative']}>
         {mobile.value && !collapse.value ? (
           <div
-            class="absolute top-0 left-0 w-full h-full opacity-30 z-99 bg-[var(--el-color-black)]"
+            class="absolute left-0 top-0 z-99 h-full w-full bg-[var(--el-color-black)] opacity-30"
             onClick={handleClickOutside}
           ></div>
         ) : undefined}

+ 4 - 3
src/layout/components/Breadcrumb/src/Breadcrumb.vue

@@ -52,10 +52,10 @@ export default defineComponent({
         return (
           <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
             {meta?.icon && breadcrumbIcon.value ? (
-              <>
+              <div class="flex items-center">
                 <Icon icon={meta.icon} class="mr-[2px]" svgClass="inline-block"></Icon>
                 {t(v?.meta?.title)}
-              </>
+              </div>
             ) : (
               t(v?.meta?.title)
             )}
@@ -114,9 +114,10 @@ $prefix-cls: #{$elNamespace}-breadcrumb;
       }
     }
   }
-
   :deep(&__item):last-child {
     .#{$prefix-cls}__inner {
+      display: flex;
+      align-items: center;
       color: var(--el-text-color-placeholder);
 
       &:hover {

+ 1 - 1
src/layout/components/Footer/src/Footer.vue

@@ -17,7 +17,7 @@ const title = computed(() => appStore.getTitle)
 <template>
   <div
     :class="prefixCls"
-    class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-contnet-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
+    class="h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)]"
   >
     <p style="font-size: 14px">Copyright ©2022-{{ title }}</p>
   </div>

+ 1 - 1
src/layout/components/Logo/src/Logo.vue

@@ -67,7 +67,7 @@ watch(
       to="/"
     >
       <img
-        class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
+        class="h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)]"
         src="@/assets/imgs/logo.png"
       />
       <div

+ 4 - 4
src/layout/components/Menu/src/Menu.vue

@@ -172,7 +172,7 @@ $prefix-cls: #{$namespace}-menu;
     .#{$elNamespace}-menu-item.is-active {
       position: relative;
 
-      &:after {
+      &::after {
         @extend .is-active--after;
       }
     }
@@ -195,7 +195,7 @@ $prefix-cls: #{$namespace}-menu;
       position: relative;
       background-color: var(--left-menu-collapse-bg-active-color) !important;
 
-      &:after {
+      &::after {
         @extend .is-active--after;
       }
     }
@@ -226,7 +226,7 @@ $prefix-cls: #{$namespace}-menu;
       .#{$elNamespace}-menu-item.is-active {
         position: relative;
 
-        &:after {
+        &::after {
           display: none !important;
         }
       }
@@ -282,7 +282,7 @@ $prefix-cls: #{$namespace}-menu-popper;
       background-color: var(--left-menu-bg-active-color) !important;
     }
 
-    &:after {
+    &::after {
       @extend .is-active--after;
     }
   }

+ 1 - 1
src/layout/components/Setting/src/Setting.vue

@@ -200,7 +200,7 @@ const clear = () => {
 <template>
   <div
     :class="prefixCls"
-    class="fixed top-[45%] right-0 w-40px h-40px text-center leading-40px bg-[var(--el-color-primary)] cursor-pointer"
+    class="fixed right-0 top-[45%] h-40px w-40px cursor-pointer bg-[var(--el-color-primary)] text-center leading-40px"
     @click="drawer = true"
   >
     <Icon color="#fff" icon="ep:setting" />

+ 1 - 1
src/layout/components/Setting/src/components/ColorRadioPicker.vue

@@ -48,7 +48,7 @@ watch(
       :style="{
         background: item
       }"
-      class="w-20px h-20px cursor-pointer rounded-2px border-solid border-gray-300 border-2px text-center leading-20px mb-5px"
+      class="mb-5px h-20px w-20px cursor-pointer border-2px border-gray-300 rounded-2px border-solid text-center leading-20px"
       @click="colorVal = item"
     >
       <Icon v-if="colorVal === item" :size="16" color="#fff" icon="ep:check" />

+ 17 - 17
src/layout/components/Setting/src/components/InterfaceDisplay.vue

@@ -141,84 +141,84 @@ watch(
 
 <template>
   <div :class="prefixCls">
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.breadcrumb') }}</span>
       <ElSwitch v-model="breadcrumb" @change="breadcrumbChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.breadcrumbIcon') }}</span>
       <ElSwitch v-model="breadcrumbIcon" @change="breadcrumbIconChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.hamburgerIcon') }}</span>
       <ElSwitch v-model="hamburger" @change="hamburgerChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.screenfullIcon') }}</span>
       <ElSwitch v-model="screenfull" @change="screenfullChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.sizeIcon') }}</span>
       <ElSwitch v-model="size" @change="sizeChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.localeIcon') }}</span>
       <ElSwitch v-model="locale" @change="localeChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.messageIcon') }}</span>
       <ElSwitch v-model="message" @change="messageChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.tagsView') }}</span>
       <ElSwitch v-model="tagsView" @change="tagsViewChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.tagsViewIcon') }}</span>
       <ElSwitch v-model="tagsViewIcon" @change="tagsViewIconChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.logo') }}</span>
       <ElSwitch v-model="logo" @change="logoChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.uniqueOpened') }}</span>
       <ElSwitch v-model="uniqueOpened" @change="uniqueOpenedChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.fixedHeader') }}</span>
       <ElSwitch v-model="fixedHeader" @change="fixedHeaderChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.footer') }}</span>
       <ElSwitch v-model="footer" @change="footerChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.greyMode') }}</span>
       <ElSwitch v-model="greyMode" @change="greyModeChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('setting.fixedMenu') }}</span>
       <ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
     </div>
 
-    <div class="flex justify-between items-center">
+    <div class="flex items-center justify-between">
       <span class="text-14px">{{ t('watermark.watermark') }}</span>
-      <ElInput v-model="water" class="w-20 right-1" @change="setWater()" />
+      <ElInput v-model="water" class="right-1 w-20" @change="setWater()" />
     </div>
   </div>
 </template>

+ 1 - 1
src/layout/components/Setting/src/components/LayoutRadioPicker.vue

@@ -55,7 +55,7 @@ const layout = computed(() => appStore.getLayout)
       ]"
       @click="appStore.setLayout('cutMenu')"
     >
-      <div class="absolute h-full w-[33%] top-0 left-[10%] bg-gray-200"></div>
+      <div class="absolute left-[10%] top-0 h-full w-[33%] bg-gray-200"></div>
     </div>
   </div>
 </template>

+ 1 - 1
src/layout/components/TabMenu/src/TabMenu.vue

@@ -176,7 +176,7 @@ export default defineComponent({
                       <Icon icon={item?.meta?.icon}></Icon>
                     </div>
                     {!unref(showTitle) ? undefined : (
-                      <p class="break-words mt-5px px-2px">{t(item.meta?.title)}</p>
+                      <p class="mt-5px break-words px-2px">{t(item.meta?.title)}</p>
                     )}
                   </div>
                 )

+ 8 - 8
src/layout/components/TagsView/src/TagsView.vue

@@ -263,11 +263,11 @@ watch(
   <div
     :id="prefixCls"
     :class="prefixCls"
-    class="flex w-full relative bg-[#fff] dark:bg-[var(--el-bg-color)]"
+    class="relative w-full flex bg-[#fff] dark:bg-[var(--el-bg-color)]"
   >
     <span
       :class="`${prefixCls}__tool ${prefixCls}__tool--first`"
-      class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
+      class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       @click="move(-200)"
     >
       <Icon
@@ -276,9 +276,9 @@ watch(
         :hover-color="isDark ? '#fff' : 'var(--el-color-black)'"
       />
     </span>
-    <div class="overflow-hidden flex-1">
+    <div class="flex-1 overflow-hidden">
       <ElScrollbar ref="scrollbarRef" class="h-full" @scroll="scroll">
-        <div class="flex h-full">
+        <div class="h-full flex">
           <ContextMenu
             :ref="itemRefs.set"
             :schema="[
@@ -354,7 +354,7 @@ watch(
               <router-link :ref="tagLinksRefs.set" :to="{ ...item }" custom v-slot="{ navigate }">
                 <div
                   @click="navigate"
-                  class="h-full flex justify-center items-center whitespace-nowrap pl-15px"
+                  class="h-full flex items-center justify-center whitespace-nowrap pl-15px"
                 >
                   <Icon
                     v-if="
@@ -384,7 +384,7 @@ watch(
     </div>
     <span
       :class="`${prefixCls}__tool`"
-      class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
+      class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       @click="move(200)"
     >
       <Icon
@@ -395,7 +395,7 @@ watch(
     </span>
     <span
       :class="`${prefixCls}__tool`"
-      class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer"
+      class="h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       @click="refreshSelectedTag(selectedTag)"
     >
       <Icon
@@ -460,7 +460,7 @@ watch(
     >
       <span
         :class="`${prefixCls}__tool`"
-        class="w-[var(--tags-view-height)] h-[var(--tags-view-height)] flex items-center justify-center cursor-pointer block"
+        class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
       >
         <Icon
           icon="ep:menu"

+ 2 - 2
src/layout/components/UserInfo/src/UserInfo.vue

@@ -53,8 +53,8 @@ const toDocument = () => {
 <template>
   <ElDropdown class="custom-hover" :class="prefixCls" trigger="click">
     <div class="flex items-center">
-      <img :src="avatar" alt="" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" />
-      <span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">
+      <ElAvatar :src="avatar" alt="" class="w-[calc(var(--logo-height)-25px)] rounded-[50%]" />
+      <span class="pl-[5px] text-14px text-[var(--top-header-text-color)] <lg:hidden">
         {{ userName }}
       </span>
     </div>

+ 7 - 7
src/layout/components/useRenderLayout.tsx

@@ -107,7 +107,7 @@ export const useRenderLayout = () => {
               ></ToolHeader>
 
               {tagsView.value ? (
-                <TagsView class="layout-border__bottom layout-border__top"></TagsView>
+                <TagsView class="layout-border__top layout-border__bottom"></TagsView>
               ) : undefined}
             </div>
 
@@ -121,13 +121,13 @@ export const useRenderLayout = () => {
   const renderTopLeft = () => {
     return (
       <>
-        <div class="flex items-center bg-[var(--top-header-bg-color)] relative layout-border__bottom dark:bg-[var(--el-bg-color)]">
+        <div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom dark:bg-[var(--el-bg-color)]">
           {logo.value ? <Logo class="custom-hover"></Logo> : undefined}
 
           <ToolHeader class="flex-1"></ToolHeader>
         </div>
-        <div class="absolute top-[var(--logo-height)+1px] left-0 w-full h-[calc(100%-1px-var(--logo-height))] flex">
-          <Menu class="!h-full relative layout-border__right"></Menu>
+        <div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
+          <Menu class="relative layout-border__right !h-full"></Menu>
           <div
             class={[
               `${prefixCls}-content`,
@@ -187,7 +187,7 @@ export const useRenderLayout = () => {
           ]}
         >
           {logo.value ? <Logo class="custom-hover"></Logo> : undefined}
-          <Menu class="flex-1 px-10px h-[var(--top-tool-height)]"></Menu>
+          <Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
           <ToolHeader></ToolHeader>
         </div>
         <div
@@ -233,12 +233,12 @@ export const useRenderLayout = () => {
   const renderCutMenu = () => {
     return (
       <>
-        <div class="flex items-center bg-[var(--top-header-bg-color)] relative layout-border__bottom">
+        <div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom">
           {logo.value ? <Logo class="custom-hover !pr-15px"></Logo> : undefined}
 
           <ToolHeader class="flex-1"></ToolHeader>
         </div>
-        <div class="absolute top-[var(--logo-height)] left-0 w-[calc(100%-2px)] h-[calc(100%-var(--logo-height))] flex">
+        <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
           <TabMenu></TabMenu>
           <div
             class={[

+ 13 - 15
src/router/modules/remaining.ts

@@ -331,9 +331,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
     ]
   },
   {
-    path: '/product',
+    path: '/mall/product', // 商品中心
     component: Layout,
-    name: 'Product',
     meta: {
       hidden: true
     },
@@ -347,12 +346,12 @@ const remainingRouter: AppRouteRecordRaw[] = [
           hidden: true,
           canTo: true,
           icon: 'ep:edit',
-          title: '添加商品',
-          activeMenu: '/product/product-spu'
+          title: '商品添加',
+          activeMenu: '/mall/product/spu'
         }
       },
       {
-        path: 'spu/edit/:spuId(\\d+)',
+        path: 'spu/edit/:id(\\d+)',
         component: () => import('@/views/mall/product/spu/form/index.vue'),
         name: 'ProductSpuEdit',
         meta: {
@@ -360,12 +359,12 @@ const remainingRouter: AppRouteRecordRaw[] = [
           hidden: true,
           canTo: true,
           icon: 'ep:edit',
-          title: '编辑商品',
-          activeMenu: '/product/product-spu'
+          title: '商品编辑',
+          activeMenu: '/mall/product/spu'
         }
       },
       {
-        path: 'spu/detail/:spuId(\\d+)',
+        path: 'spu/detail/:id(\\d+)',
         component: () => import('@/views/mall/product/spu/form/index.vue'),
         name: 'ProductSpuDetail',
         meta: {
@@ -374,7 +373,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
           canTo: true,
           icon: 'ep:view',
           title: '商品详情',
-          activeMenu: '/product/product-spu'
+          activeMenu: '/mall/product/spu'
         }
       },
       {
@@ -393,24 +392,23 @@ const remainingRouter: AppRouteRecordRaw[] = [
     ]
   },
   {
-    path: '/trade',
+    path: '/mall/trade', // 交易中心
     component: Layout,
-    name: 'Order',
     meta: {
       hidden: true
     },
     children: [
       {
-        path: 'order/detail/:orderId(\\d+)',
+        path: 'order/detail/:id(\\d+)',
         component: () => import('@/views/mall/trade/order/detail/index.vue'),
         name: 'TradeOrderDetail',
-        meta: { title: '订单详情', icon: '', activeMenu: '/trade/trade/order' }
+        meta: { title: '订单详情', icon: 'ep:view', activeMenu: '/mall/trade/order' }
       },
       {
-        path: 'after-sale/detail/:orderId(\\d+)',
+        path: 'after-sale/detail/:id(\\d+)',
         component: () => import('@/views/mall/trade/afterSale/detail/index.vue'),
         name: 'TradeAfterSaleDetail',
-        meta: { title: '退款详情', icon: '', activeMenu: '/trade/trade/after-sale' }
+        meta: { title: '退款详情', icon: 'ep:view', activeMenu: '/mall/trade/after-sale' }
       }
     ]
   },

+ 1 - 1
src/styles/var.css

@@ -48,7 +48,7 @@
 
   --app-content-padding: 20px;
 
-  --app-contnet-bg-color: #f5f7f9;
+  --app-content-bg-color: #f5f7f9;
 
   --app-footer-height: 50px;
 

+ 80 - 24
src/utils/constants.ts

@@ -4,12 +4,20 @@
  * 枚举类
  */
 
+// ========== COMMON 模块 ==========
 // 全局通用状态枚举
 export const CommonStatusEnum = {
   ENABLE: 0, // 开启
   DISABLE: 1 // 禁用
 }
 
+// 全局用户类型枚举
+export const UserTypeEnum = {
+  MEMBER: 1, // 会员
+  ADMIN: 2 // 管理员
+}
+
+// ========== SYSTEM 模块 ==========
 /**
  * 菜单的类型枚举
  */
@@ -38,6 +46,25 @@ export const SystemDataScopeEnum = {
   DEPT_SELF: 5 // 仅本人数据权限
 }
 
+/**
+ * 用户的社交平台的类型枚举
+ */
+export const SystemUserSocialTypeEnum = {
+  DINGTALK: {
+    title: '钉钉',
+    type: 20,
+    source: 'dingtalk',
+    img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
+  },
+  WECHAT_ENTERPRISE: {
+    title: '企业微信',
+    type: 30,
+    source: 'wechat_enterprise',
+    img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
+  }
+}
+
+// ========== INFRA 模块 ==========
 /**
  * 代码生成模板类型
  */
@@ -65,24 +92,7 @@ export const InfraApiErrorLogProcessStatusEnum = {
   IGNORE: 2 // 已忽略
 }
 
-/**
- * 用户的社交平台的类型枚举
- */
-export const SystemUserSocialTypeEnum = {
-  DINGTALK: {
-    title: '钉钉',
-    type: 20,
-    source: 'dingtalk',
-    img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
-  },
-  WECHAT_ENTERPRISE: {
-    title: '企业微信',
-    type: 30,
-    source: 'wechat_enterprise',
-    img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
-  }
-}
-
+// ========== PAY 模块 ==========
 /**
  * 支付渠道枚举
  */
@@ -177,6 +187,7 @@ export const PayOrderStatusEnum = {
   }
 }
 
+// ========== MALL - 商品模块 ==========
 /**
  * 商品 SPU 状态
  */
@@ -195,6 +206,7 @@ export const ProductSpuStatusEnum = {
   }
 }
 
+// ========== MALL - 营销模块 ==========
 /**
  * 优惠劵模板的有限期类型的枚举
  */
@@ -273,17 +285,22 @@ export const PromotionDiscountTypeEnum = {
   }
 }
 
+// ========== MALL - 交易模块 ==========
 /**
  * 分销关系绑定模式枚举
  */
 export const BrokerageBindModeEnum = {
   ANYTIME: {
-    mode: 0,
-    name: '没有推广人'
+    mode: 1,
+    name: '首次绑定'
   },
   REGISTER: {
-    mode: 1,
-    name: '新用户'
+    mode: 2,
+    name: '注册绑定'
+  },
+  OVERRIDE: {
+    mode: 3,
+    name: '覆盖绑定'
   }
 }
 /**
@@ -291,11 +308,11 @@ export const BrokerageBindModeEnum = {
  */
 export const BrokerageEnabledConditionEnum = {
   ALL: {
-    condition: 0,
+    condition: 1,
     name: '人人分销'
   },
   ADMIN: {
-    condition: 1,
+    condition: 2,
     name: '指定分销'
   }
 }
@@ -358,3 +375,42 @@ export const BrokerageWithdrawTypeEnum = {
     name: '支付宝'
   }
 }
+
+/**
+ * 配送方式枚举
+ */
+export const DeliveryTypeEnum = {
+  EXPRESS: {
+    type: 1,
+    name: '快递发货'
+  },
+  PICK_UP: {
+    type: 2,
+    name: '到店自提'
+  }
+}
+/**
+ * 交易订单 - 状态
+ */
+export const TradeOrderStatusEnum = {
+  UNPAID: {
+    status: 0,
+    name: '待支付'
+  },
+  UNDELIVERED: {
+    status: 10,
+    name: '待发货'
+  },
+  DELIVERED: {
+    status: 20,
+    name: '已发货'
+  },
+  COMPLETED: {
+    status: 30,
+    name: '已完成'
+  },
+  CANCELED: {
+    status: 40,
+    name: '已取消'
+  }
+}

+ 3 - 2
src/utils/dict.ts

@@ -168,7 +168,7 @@ export enum DICT_TYPE {
   BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式
   BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式
   BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行
-  BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金冻结时间
+  BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型
   BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型
   BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态
   BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态
@@ -180,5 +180,6 @@ export enum DICT_TYPE {
   PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
   PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
   PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
-  PROMOTION_CONDITION_TYPE = 'promotion_condition_type' // 营销的条件类型枚举
+  PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
+  PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status' // 砍价记录的状态
 }

+ 66 - 1
src/utils/formatTime.ts

@@ -11,7 +11,7 @@ import dayjs from 'dayjs'
  * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
  * @returns 返回拼接后的时间字符串
  */
-export function formatDate(date: Date | number, format?: string): string {
+export function formatDate(date: dayjs.ConfigType, format?: string): string {
   // 日期不存在,则返回空
   if (!date) {
     return ''
@@ -221,3 +221,68 @@ export function convertDate(param: Date | string) {
   }
   return param
 }
+
+/**
+ * 指定的两个日期, 是否为同一天
+ * @param a 日期 A
+ * @param b 日期 B
+ */
+export function isSameDay(a: dayjs.ConfigType, b: dayjs.ConfigType): boolean {
+  if (!a || !b) return false
+
+  const aa = dayjs(a)
+  const bb = dayjs(b)
+  return aa.year() == bb.year() && aa.month() == bb.month() && aa.day() == bb.day()
+}
+
+/**
+ * 获取一天的开始时间、截止时间
+ * @param date 日期
+ * @param days 天数
+ */
+export function getDayRange(
+  date: dayjs.ConfigType,
+  days: number
+): [dayjs.ConfigType, dayjs.ConfigType] {
+  const day = dayjs(date).add(days, 'd')
+  return getDateRange(day, day)
+}
+
+/**
+ * 获取最近7天的开始时间、截止时间
+ */
+export function getLast7Days(): [dayjs.ConfigType, dayjs.ConfigType] {
+  const lastWeekDay = dayjs().subtract(7, 'd')
+  const yesterday = dayjs().subtract(1, 'd')
+  return getDateRange(lastWeekDay, yesterday)
+}
+
+/**
+ * 获取最近30天的开始时间、截止时间
+ */
+export function getLast30Days(): [dayjs.ConfigType, dayjs.ConfigType] {
+  const lastMonthDay = dayjs().subtract(30, 'd')
+  const yesterday = dayjs().subtract(1, 'd')
+  return getDateRange(lastMonthDay, yesterday)
+}
+
+/**
+ * 获取最近1年的开始时间、截止时间
+ */
+export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
+  const lastYearDay = dayjs().subtract(1, 'y')
+  const yesterday = dayjs().subtract(1, 'd')
+  return getDateRange(lastYearDay, yesterday)
+}
+
+/**
+ * 获取指定日期的开始时间、截止时间
+ * @param beginDate 开始日期
+ * @param endDate 截止日期
+ */
+export function getDateRange(
+  beginDate: dayjs.ConfigType,
+  endDate: dayjs.ConfigType
+): [dayjs.ConfigType, dayjs.ConfigType] {
+  return [dayjs(beginDate).startOf('d'), dayjs(endDate).endOf('d')]
+}

+ 3 - 2
src/utils/index.ts

@@ -230,6 +230,7 @@ export const yuanToFen = (amount: string | number): number => {
 /**
  * 分转元
  */
-export const fenToYuan = (amount: string | number): number => {
-  return Number((Number(amount) / 100).toFixed(2))
+export const fenToYuan = (price: string | number): number => {
+  price = Number(price)
+  return (price / 100.0).toFixed(2)
 }

+ 4 - 3
src/utils/tree.ts

@@ -13,7 +13,8 @@ export const defaultProps = {
   children: 'children',
   label: 'name',
   value: 'id',
-  isLeaf: 'leaf'
+  isLeaf: 'leaf',
+  emitPath: false // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值
 }
 
 const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
@@ -377,10 +378,10 @@ export const treeToString = (tree: any[], nodeId) => {
   function performAThoroughValidation(arr) {
     for (const item of arr) {
       if (item.id === nodeId) {
-        str += `/${item.name}`
+        str += ` / ${item.name}`
         return true
       } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
-        str += `/${item.name}`
+        str += ` / ${item.name}`
         if (performAThoroughValidation(item.children)) {
           return true
         }

+ 10 - 10
src/views/Home/Index.vue

@@ -5,7 +5,7 @@
         <el-row :gutter="20" justify="space-between">
           <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
             <div class="flex items-center">
-              <img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" />
+              <img :src="avatar" alt="" class="mr-20px h-70px w-70px rounded-[50%]" />
               <div>
                 <div class="text-20px">
                   {{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
@@ -17,9 +17,9 @@
             </div>
           </el-col>
           <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
-            <div class="flex h-70px items-center justify-end lt-sm:mt-10px">
+            <div class="h-70px flex items-center justify-end lt-sm:mt-10px">
               <div class="px-8px text-right">
-                <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
+                <div class="mb-20px text-14px text-gray-400">{{ t('workplace.project') }}</div>
                 <CountTo
                   class="text-20px"
                   :start-val="0"
@@ -29,7 +29,7 @@
               </div>
               <el-divider direction="vertical" />
               <div class="px-8px text-right">
-                <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
+                <div class="mb-20px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
                 <CountTo
                   class="text-20px"
                   :start-val="0"
@@ -39,7 +39,7 @@
               </div>
               <el-divider direction="vertical" border-style="dashed" />
               <div class="px-8px text-right">
-                <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
+                <div class="mb-20px text-14px text-gray-400">{{ t('workplace.access') }}</div>
                 <CountTo
                   class="text-20px"
                   :start-val="0"
@@ -58,7 +58,7 @@
     <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-10px">
       <el-card shadow="never">
         <template #header>
-          <div class="flex justify-between h-3">
+          <div class="h-3 flex justify-between">
             <span>{{ t('workplace.project') }}</span>
             <el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
           </div>
@@ -80,7 +80,7 @@
                   <span class="text-16px">{{ item.name }}</span>
                 </div>
                 <div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
-                <div class="mt-20px text-12px text-gray-400 flex justify-between">
+                <div class="mt-20px flex justify-between text-12px text-gray-400">
                   <span>{{ item.personal }}</span>
                   <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
                 </div>
@@ -114,7 +114,7 @@
     <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-10px">
       <el-card shadow="never">
         <template #header>
-          <div class="flex justify-between h-3">
+          <div class="h-3 flex justify-between">
             <span>{{ t('workplace.shortcutOperation') }}</span>
           </div>
         </template>
@@ -133,7 +133,7 @@
       </el-card>
       <el-card shadow="never" class="mt-10px">
         <template #header>
-          <div class="flex justify-between h-3">
+          <div class="h-3 flex justify-between">
             <span>{{ t('workplace.notice') }}</span>
             <el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
           </div>
@@ -141,7 +141,7 @@
         <el-skeleton :loading="loading" animated>
           <div v-for="(item, index) in notice" :key="`dynamics-${index}`">
             <div class="flex items-center">
-              <img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" />
+              <img :src="avatar" alt="" class="mr-20px h-35px w-35px rounded-[50%]" />
               <div>
                 <div class="text-14px">
                   <Highlight :keys="item.keys.map((v) => t(v))">

+ 4 - 4
src/views/Home/Index2.vue

@@ -20,7 +20,7 @@
                   :duration="2600"
                   :end-val="102400"
                   :start-val="0"
-                  class="text-20px font-700 text-right"
+                  class="text-right text-20px font-700"
                 />
               </div>
             </div>
@@ -49,7 +49,7 @@
                   :duration="2600"
                   :end-val="81212"
                   :start-val="0"
-                  class="text-20px font-700 text-right"
+                  class="text-right text-20px font-700"
                 />
               </div>
             </div>
@@ -78,7 +78,7 @@
                   :duration="2600"
                   :end-val="9280"
                   :start-val="0"
-                  class="text-20px font-700 text-right"
+                  class="text-right text-20px font-700"
                 />
               </div>
             </div>
@@ -107,7 +107,7 @@
                   :duration="2600"
                   :end-val="13600"
                   :start-val="0"
-                  class="text-20px font-700 text-right"
+                  class="text-right text-20px font-700"
                 />
               </div>
             </div>

+ 17 - 17
src/views/Login/Login.vue

@@ -1,19 +1,19 @@
 <template>
   <div
     :class="prefixCls"
-    class="h-[100%] relative lt-xl:bg-[var(--login-bg-color)] lt-sm:px-10px lt-xl:px-10px lt-md:px-10px"
+    class="relative h-[100%] lt-xl:bg-[var(--login-bg-color)] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
   >
-    <div class="relative h-full flex mx-auto">
+    <div class="relative mx-auto h-full flex">
       <div
         :class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
       >
         <!-- 左上角的 logo + 系统标题 -->
-        <div class="flex items-center relative text-white">
-          <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
+        <div class="relative flex items-center text-white">
+          <img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
           <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
         </div>
         <!-- 左边的背景图 + 欢迎语 -->
-        <div class="flex justify-center items-center h-[calc(100%-60px)]">
+        <div class="h-[calc(100%-60px)] flex items-center justify-center">
           <TransitionGroup
             appear
             enter-active-class="animate__animated animate__bounceInLeft"
@@ -21,41 +21,41 @@
           >
             <img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
             <div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
-            <div key="3" class="mt-5 font-normal text-white text-14px">
+            <div key="3" class="mt-5 text-14px font-normal text-white">
               {{ t('login.message') }}
             </div>
           </TransitionGroup>
         </div>
       </div>
-      <div class="flex-1 p-30px lt-sm:p-10px dark:bg-[var(--login-bg-color)] relative">
+      <div class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px">
         <!-- 右上角的主题、语言选择 -->
         <div
-          class="flex justify-between items-center text-white at-2xl:justify-end at-xl:justify-end"
+          class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
         >
           <div class="flex items-center at-2xl:hidden at-xl:hidden">
-            <img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
+            <img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
             <span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
           </div>
-          <div class="flex justify-end items-center space-x-10px">
+          <div class="flex items-center justify-end space-x-10px">
             <ThemeSwitch />
-            <LocaleDropdown class="lt-xl:text-white dark:text-white" />
+            <LocaleDropdown class="dark:text-white lt-xl:text-white" />
           </div>
         </div>
         <!-- 右边的登录界面 -->
         <Transition appear enter-active-class="animate__animated animate__bounceInRight">
           <div
-            class="h-full flex items-center m-auto w-[100%] at-2xl:max-w-500px at-xl:max-w-500px at-md:max-w-500px at-lg:max-w-500px"
+            class="m-auto h-full w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px"
           >
             <!-- 账号登录 -->
-            <LoginForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
+            <LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
             <!-- 手机登录 -->
-            <MobileForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
+            <MobileForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
             <!-- 二维码登录 -->
-            <QrCodeForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
+            <QrCodeForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
             <!-- 注册 -->
-            <RegisterForm class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
+            <RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
             <!-- 三方登录 -->
-            <SSOLoginVue class="p-20px h-auto m-auto lt-xl:(rounded-3xl light:bg-white)" />
+            <SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
           </div>
         </Transition>
       </div>

+ 3 - 3
src/views/Login/components/LoginForm.vue

@@ -112,13 +112,13 @@
       <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
       <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
         <el-form-item>
-          <div class="flex justify-between w-[100%]">
+          <div class="w-[100%] flex justify-between">
             <Icon
               v-for="(item, key) in socialList"
               :key="key"
               :icon="item.icon"
               :size="30"
-              class="cursor-pointer anticon"
+              class="anticon cursor-pointer"
               color="#999"
               @click="doSocialLogin(item.type)"
             />
@@ -128,7 +128,7 @@
       <el-divider content-position="center">萌新必读</el-divider>
       <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
         <el-form-item>
-          <div class="flex justify-between w-[100%]">
+          <div class="w-[100%] flex justify-between">
             <el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
             <el-link href="https://doc.iocoder.cn/video/" target="_blank">🔥视频教程</el-link>
             <el-link href="https://www.iocoder.cn/Interview/good-collection/" target="_blank">

+ 1 - 1
src/views/Login/components/LoginFormTitle.vue

@@ -1,5 +1,5 @@
 <template>
-  <h2 class="mb-3 text-2xl font-bold text-center xl:text-3xl enter-x xl:text-center">
+  <h2 class="enter-x mb-3 text-center text-2xl font-bold xl:text-center xl:text-3xl">
     {{ getFormTitle }}
   </h2>
 </template>

+ 1 - 1
src/views/Login/components/QrCodeForm.vue

@@ -10,7 +10,7 @@
     </el-col>
     <el-divider class="enter-x">{{ t('login.qrcode') }}</el-divider>
     <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
-      <div class="w-[100%] mt-15px">
+      <div class="mt-15px w-[100%]">
         <XButton :title="t('login.backLogin')" class="w-[100%]" @click="handleBackLogin()" />
       </div>
     </el-col>

+ 1 - 1
src/views/Login/components/RegisterForm.vue

@@ -29,7 +29,7 @@
           @click="loginRegister()"
         />
       </div>
-      <div class="w-[100%] mt-15px">
+      <div class="mt-15px w-[100%]">
         <XButton :title="t('login.hasUser')" class="w-[100%]" @click="handleBackLogin()" />
       </div>
     </template>

+ 2 - 2
src/views/Profile/Index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="flex">
-    <el-card class="w-1/3 user" shadow="hover">
+    <el-card class="user w-1/3" shadow="hover">
       <template #header>
         <div class="card-header">
           <span>{{ t('profile.user.title') }}</span>
@@ -8,7 +8,7 @@
       </template>
       <ProfileUser />
     </el-card>
-    <el-card class="w-2/3 user ml-3" shadow="hover">
+    <el-card class="user ml-3 w-2/3" shadow="hover">
       <template #header>
         <div class="card-header">
           <span>{{ t('profile.info.title') }}</span>

+ 9 - 0
src/views/bpm/processInstance/detail/ProcessInstanceTaskList.vue

@@ -69,6 +69,9 @@ const getTimelineItemIcon = (item) => {
   if (item.result === 4) {
     return 'el-icon-remove-outline'
   }
+  if (item.result === 5) {
+    return 'el-icon-back'
+  }
   return ''
 }
 
@@ -86,6 +89,12 @@ const getTimelineItemType = (item) => {
   if (item.result === 4) {
     return 'info'
   }
+  if (item.result === 5) {
+    return 'warning'
+  }
+  if (item.result === 6) {
+    return 'default'
+  }
   return ''
 }
 </script>

+ 86 - 0
src/views/bpm/processInstance/detail/TaskDelegateForm.vue

@@ -0,0 +1,86 @@
+<template>
+  <Dialog v-model="dialogVisible" title="委派任务" width="500">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="110px"
+    >
+      <el-form-item label="接收人" prop="delegateUserId">
+        <el-select v-model="formData.delegateUserId" clearable style="width: 100%">
+          <el-option
+            v-for="item in userList"
+            :key="item.id"
+            :label="item.nickname"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="委派理由" prop="reason">
+        <el-input v-model="formData.reason" clearable placeholder="请输入委派理由" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import * as TaskApi from '@/api/bpm/task'
+import * as UserApi from '@/api/system/user'
+
+defineOptions({ name: 'BpmTaskDelegateForm' })
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const formData = ref({
+  id: '',
+  delegateUserId: undefined
+})
+const formRules = ref({
+  delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }]
+})
+
+const formRef = ref() // 表单 Ref
+const userList = ref<any[]>([]) // 用户列表
+
+/** 打开弹窗 */
+const open = async (id: string) => {
+  dialogVisible.value = true
+  resetForm()
+  formData.value.id = id
+  // 获得用户列表
+  userList.value = await UserApi.getSimpleUserList()
+}
+defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    await TaskApi.delegateTask(formData.value)
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: '',
+    delegateUserId: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 90 - 0
src/views/bpm/processInstance/detail/TaskReturnDialogForm.vue

@@ -0,0 +1,90 @@
+<template>
+  <Dialog v-model="dialogVisible" title="回退" width="500">
+    <el-form
+      ref="formRef"
+      v-loading="formLoading"
+      :model="formData"
+      :rules="formRules"
+      label-width="110px"
+    >
+      <el-form-item label="退回节点" prop="targetDefinitionKey">
+        <el-select v-model="formData.targetDefinitionKey" clearable style="width: 100%">
+          <el-option
+            v-for="item in returnList"
+            :key="item.definitionKey"
+            :label="item.name"
+            :value="item.definitionKey"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="回退理由" prop="reason">
+        <el-input v-model="formData.reason" clearable placeholder="请输入回退理由" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" name="TaskRollbackDialogForm" setup>
+import * as TaskApi from '@/api/bpm/task'
+
+const message = useMessage() // 消息弹窗
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const formData = ref({
+  id: '',
+  targetDefinitionKey: undefined,
+  reason: ''
+})
+const formRules = ref({
+  targetDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
+  reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
+})
+
+const formRef = ref() // 表单 Ref
+const returnList = ref([])
+/** 打开弹窗 */
+const open = async (id: string) => {
+  returnList.value = await TaskApi.getReturnList({ taskId: id })
+  if (returnList.value.length === 0) {
+    message.warning('当前没有可回退的节点')
+    return false
+  }
+  dialogVisible.value = true
+  resetForm()
+  formData.value.id = id
+}
+defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+  // 提交请求
+  formLoading.value = true
+  try {
+    await TaskApi.returnTask(formData.value)
+    message.success('回退成功')
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: '',
+    targetDefinitionKey: undefined,
+    reason: ''
+  }
+  formRef.value?.resetFields()
+}
+</script>

+ 12 - 5
src/views/bpm/processInstance/detail/index.vue

@@ -91,6 +91,10 @@
 
     <!-- 弹窗:转派审批人 -->
     <TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
+    <!-- 弹窗,回退节点 -->
+    <TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
+    <!-- 委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
+    <TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
   </ContentWrap>
 </template>
 <script lang="ts" setup>
@@ -103,6 +107,8 @@ import * as TaskApi from '@/api/bpm/task'
 import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue'
 import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
 import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
+import TaskReturnDialog from './TaskReturnDialogForm.vue'
+import TaskDelegateForm from './taskDelegateForm.vue'
 import { registerComponent } from '@/utils/routerHelper'
 
 defineOptions({ name: 'BpmProcessInstanceDetail' })
@@ -166,16 +172,17 @@ const openTaskUpdateAssigneeForm = (id: string) => {
   taskUpdateAssigneeFormRef.value.open(id)
 }
 
+const taskDelegateForm = ref()
 /** 处理审批退回的操作 */
 const handleDelegate = async (task) => {
-  message.error('暂不支持【委派】功能,可以使用【转派】替代!')
-  console.log(task)
+  taskDelegateForm.value.open(task.id)
 }
 
+//回退弹框组件
+const taskReturnDialogRef = ref()
 /** 处理审批退回的操作 */
 const handleBack = async (task) => {
-  message.error('暂不支持【退回】功能!')
-  console.log(task)
+  taskReturnDialogRef.value.open(task.id)
 }
 
 /** 获得详情 */
@@ -256,7 +263,7 @@ const getTaskList = async () => {
     auditForms.value = []
     tasks.value.forEach((task) => {
       // 2.1 只有待处理才需要
-      if (task.result !== 1) {
+      if (task.result !== 1 && task.result !== 6) {
         return
       }
       // 2.2 自己不是处理人

+ 1 - 1
src/views/infra/build/index.vue

@@ -2,7 +2,7 @@
   <ContentWrap>
     <el-row>
       <el-col>
-        <div class="mb-2 float-right">
+        <div class="float-right mb-2">
           <el-button size="small" type="primary" @click="showJson">生成 JSON</el-button>
           <el-button size="small" type="success" @click="showOption">生成 Options</el-button>
           <el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>

+ 2 - 2
src/views/infra/codegen/PreviewCode.vue

@@ -20,8 +20,8 @@
             ref="treeRef"
             :data="preview.fileTree"
             :expand-on-click-node="false"
-            default-expand-all
             highlight-current
+            default-expand-all
             node-key="id"
             @node-click="handleNodeClick"
           />
@@ -31,7 +31,7 @@
       <el-card
         v-loading="loading"
         :gutter="12"
-        class="w-2/3 ml-3"
+        class="ml-3 w-2/3"
         element-loading-text="加载代码中..."
         shadow="hover"
       >

+ 3 - 3
src/views/infra/webSocket/index.vue

@@ -7,7 +7,7 @@
         </div>
       </template>
       <div class="flex items-center">
-        <span class="text-lg font-medium mr-4"> 连接状态: </span>
+        <span class="mr-4 text-lg font-medium"> 连接状态: </span>
         <el-tag :color="getTagColor">{{ status }}</el-tag>
       </div>
       <hr class="my-4" />
@@ -20,7 +20,7 @@
           {{ getIsOpen ? '关闭连接' : '开启连接' }}
         </el-button>
       </div>
-      <p class="text-lg font-medium mt-4">设置</p>
+      <p class="mt-4 text-lg font-medium">设置</p>
       <hr class="my-4" />
       <el-input
         v-model="sendValue"
@@ -43,7 +43,7 @@
         <ul>
           <li v-for="item in getList" :key="item.time" class="mt-2">
             <div class="flex items-center">
-              <span class="mr-2 text-primary font-medium">收到消息:</span>
+              <span class="text-primary mr-2 font-medium">收到消息:</span>
               <span>{{ formatDate(item.time) }}</span>
             </div>
             <div>

+ 5 - 5
src/views/mall/product/category/index.vue

@@ -35,14 +35,14 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
-      <el-table-column label="分类名称" prop="name" sortable />
-      <el-table-column label="移动端分类图" align="center" prop="picUrl">
+      <el-table-column label="名称" min-width="240" prop="name" sortable />
+      <el-table-column label="分类图" align="center" min-width="80" prop="picUrl">
         <template #default="scope">
-          <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-30px" />
+          <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-36px" />
         </template>
       </el-table-column>
-      <el-table-column label="分类排序" align="center" prop="sort" />
-      <el-table-column label="开启状态" align="center" prop="status">
+      <el-table-column label="排序" align="center" min-width="150" prop="sort" />
+      <el-table-column label="状态" align="center" min-width="150" prop="status">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
         </template>

+ 7 - 6
src/views/mall/product/comment/CommentForm.vue

@@ -8,7 +8,7 @@
       v-loading="formLoading"
     >
       <el-form-item label="商品" prop="spuId">
-        <div @click="handleSelectSpu" class="w-60px h-60px">
+        <div @click="handleSelectSpu" class="h-60px w-60px">
           <div v-if="spuData && spuData.picUrl">
             <el-image :src="spuData.picUrl" />
           </div>
@@ -18,7 +18,7 @@
         </div>
       </el-form-item>
       <el-form-item label="商品规格" prop="skuId" v-if="formData.spuId">
-        <div @click="handleSelectSku" class="w-60px h-60px">
+        <div @click="handleSelectSku" class="h-60px w-60px">
           <div v-if="skuData && skuData.picUrl">
             <el-image :src="skuData.picUrl" />
           </div>
@@ -150,6 +150,7 @@ const resetForm = () => {
     userNickname: undefined,
     userAvatar: undefined,
     spuId: undefined,
+    spuName: undefined,
     skuId: undefined,
     descriptionScores: 5,
     benefitScores: 5,
@@ -182,11 +183,11 @@ const handleSkuChange = (sku: ProductSpuApi.Sku) => {
 <style>
 .select-box {
   display: flex;
-  align-items: center;
-  justify-content: center;
-  border: 1px dashed var(--el-border-color-darker);
-  border-radius: 8px;
   width: 100%;
   height: 100%;
+  border: 1px dashed var(--el-border-color-darker);
+  border-radius: 8px;
+  align-items: center;
+  justify-content: center;
 }
 </style>

+ 11 - 12
src/views/mall/product/comment/index.vue

@@ -59,16 +59,15 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false">
-      <el-table-column label="评论编号" align="center" prop="id" min-width="60" />
-      <el-table-column label="用户名称" align="center" prop="userNickname" width="80" />
-      <el-table-column label="商品信息" align="center" min-width="300">
+      <el-table-column label="评论编号" align="center" prop="id" min-width="50" />
+      <el-table-column label="商品信息" align="center" min-width="400">
         <template #default="scope">
-          <div class="flex row items-center gap-x-4px">
+          <div class="row flex items-center gap-x-4px">
             <el-image
               v-if="scope.row.skuPicUrl"
               :src="scope.row.skuPicUrl"
               :preview-src-list="[scope.row.skuPicUrl]"
-              class="w-40px h-40px shrink-0"
+              class="h-40px w-40px shrink-0"
               preview-teleported
             />
             <div>{{ scope.row.spuName }}</div>
@@ -82,10 +81,10 @@
           </div>
         </template>
       </el-table-column>
-      <el-table-column label="评分星级" align="center" prop="scores" width="80" />
-      <el-table-column label="描述星级" align="center" prop="descriptionScores" width="80" />
-      <el-table-column label="服务星级" align="center" prop="benefitScores" width="80" />
-      <el-table-column label="评论内容" align="center" prop="content" min-width="80">
+      <el-table-column label="用户名称" align="center" prop="userNickname" width="100" />
+      <el-table-column label="商品评分" align="center" prop="descriptionScores" width="90" />
+      <el-table-column label="服务评分" align="center" prop="benefitScores" width="90" />
+      <el-table-column label="评论内容" align="center" prop="content" min-width="210">
         <template #default="scope">
           <p>{{ scope.row.content }}</p>
           <div class="flex justify-center gap-x-4px">
@@ -95,7 +94,7 @@
               :src="picUrl"
               :preview-src-list="scope.row.picUrls"
               :initial-index="index"
-              class="w-40px h-40px"
+              class="h-40px w-40px"
               preview-teleported
             />
           </div>
@@ -105,7 +104,7 @@
         label="回复内容"
         align="center"
         prop="replyContent"
-        min-width="100"
+        min-width="250"
         show-overflow-tooltip
       />
       <el-table-column
@@ -113,7 +112,7 @@
         align="center"
         prop="createTime"
         :formatter="dateFormatter"
-        width="170"
+        width="180"
       />
       <el-table-column label="是否展示" align="center" width="80px">
         <template #default="scope">

+ 3 - 3
src/views/mall/product/property/index.vue

@@ -53,8 +53,8 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column align="center" label="编号" prop="id" />
-      <el-table-column align="center" label="名称" prop="name" />
+      <el-table-column align="center" label="编号" min-width="60" prop="id" />
+      <el-table-column align="center" label="属性名称" prop="name" min-width="150" />
       <el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
       <el-table-column
         :formatter="dateFormatter"
@@ -165,7 +165,7 @@ const handleDelete = async (id: number) => {
 
 /** 跳转商品属性列表 */
 const goValueList = (id: number) => {
-  push({ path: '/product/property/value/' + id })
+  push({ name: 'ProductPropertyValue', params: { propertyId: id } })
 }
 
 /** 初始化 **/

+ 2 - 2
src/views/mall/product/property/value/index.vue

@@ -45,8 +45,8 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="编号" align="center" prop="id" />
-      <el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" />
+      <el-table-column label="编号" align="center" min-width="60" prop="id" />
+      <el-table-column label="属性值名称" align="center" min-width="150" prop="name" />
       <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
       <el-table-column
         label="创建时间"

+ 2 - 2
src/views/mall/product/spu/components/SkuList.vue

@@ -124,7 +124,7 @@
     <el-table-column v-if="isComponent" type="selection" width="45" />
     <el-table-column align="center" label="图片" min-width="80">
       <template #default="{ row }">
-        <el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
+        <el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
       </template>
     </el-table-column>
     <template v-if="formData!.specType && !isBatch">
@@ -204,7 +204,7 @@
     <el-table-column v-if="isComponent" type="selection" width="45" />
     <el-table-column align="center" label="图片" min-width="80">
       <template #default="{ row }">
-        <el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
+        <el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
       </template>
     </el-table-column>
     <template v-if="formData!.specType">

+ 3 - 2
src/views/mall/product/spu/components/SkuTableSelect.vue

@@ -12,7 +12,7 @@
         <template #default="{ row }">
           <el-image
             :src="row.picUrl"
-            class="w-30px h-30px"
+            class="h-30px w-30px"
             :preview-src-list="[row.picUrl]"
             preview-teleported
           />
@@ -25,7 +25,7 @@
       </el-table-column>
       <el-table-column align="center" label="销售价(元)" min-width="80">
         <template #default="{ row }">
-          {{ row.price }}
+          {{ fenToYuan(row.price) }}
         </template>
       </el-table-column>
     </el-table>
@@ -36,6 +36,7 @@
 import { ElTable } from 'element-plus'
 import * as ProductSpuApi from '@/api/mall/product/spu'
 import { propTypes } from '@/utils/propTypes'
+import { fenToYuan } from '@/utils'
 
 defineOptions({ name: 'SkuTableSelect' })
 

+ 1 - 1
src/views/mall/product/spu/components/SpuTableSelect.vue

@@ -73,7 +73,7 @@
           <template #default="{ row }">
             <el-image
               :src="row.picUrl"
-              class="w-30px h-30px"
+              class="h-30px w-30px"
               :preview-src-list="[row.picUrl]"
               preview-teleported
             />

+ 16 - 31
src/views/mall/product/spu/form/BasicInfoForm.vue

@@ -15,15 +15,14 @@
       </el-col>
       <el-col :span="12">
         <el-form-item label="商品分类" prop="categoryId">
-          <el-tree-select
+          <el-cascader
             v-model="formData.categoryId"
-            :data="categoryList"
+            :options="categoryList"
             :props="defaultProps"
-            check-strictly
             class="w-1/1"
-            node-key="id"
+            clearable
             placeholder="请选择商品分类"
-            @change="categoryNodeClick"
+            filterable
           />
         </el-form-item>
       </el-col>
@@ -74,8 +73,6 @@
               :value="item.id"
             />
           </el-select>
-          <!-- TODO 可能情况:善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
-          <!-- <el-button class="ml-20px">运费模板</el-button>-->
         </el-form-item>
       </el-col>
       <el-col :span="12">
@@ -102,7 +99,7 @@
         <el-form-item label="分销类型" props="subCommissionType">
           <el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
             <el-radio :label="false">默认设置</el-radio>
-            <el-radio :label="true" class="radio">自行设置</el-radio>
+            <el-radio :label="true" class="radio">单独设置</el-radio>
           </el-radio-group>
         </el-form-item>
       </el-col>
@@ -117,7 +114,7 @@
           />
         </el-form-item>
         <el-form-item v-if="formData.specType" label="商品属性">
-          <el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
+          <el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
           <ProductAttributes :propertyList="propertyList" @success="generateSkus" />
         </el-form-item>
         <template v-if="formData.specType && propertyList.length > 0">
@@ -139,7 +136,7 @@
 
   <!-- 情况二:详情 -->
   <Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
-    <template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
+    <template #categoryId="{ row }"> {{ formatCategoryName(row.categoryId) }}</template>
     <template #brandId="{ row }">
       {{ brandList.find((item) => item.id === row.brandId)?.name }}
     </template>
@@ -150,17 +147,17 @@
       {{ row.specType ? '多规格' : '单规格' }}
     </template>
     <template #subCommissionType="{ row }">
-      {{ row.subCommissionType ? '自行设置' : '默认设置' }}
+      {{ row.subCommissionType ? '单独设置' : '默认设置' }}
     </template>
     <template #picUrl="{ row }">
-      <el-image :src="row.picUrl" class="w-60px h-60px" @click="imagePreview(row.picUrl)" />
+      <el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
     </template>
     <template #sliderPicUrls="{ row }">
       <el-image
         v-for="(item, index) in row.sliderPicUrls"
         :key="index"
         :src="item.url"
-        class="w-60px h-60px mr-10px"
+        class="mr-10px h-60px w-60px"
         @click="imagePreview(row.sliderPicUrls)"
       />
     </template>
@@ -206,17 +203,17 @@ const ruleConfig: RuleConfig[] = [
   {
     name: 'price',
     rule: (arg) => arg >= 0.01,
-    message: '商品销售价格必须大于等于 0.01 !!!'
+    message: '商品销售价格必须大于等于 0.01 !!!'
   },
   {
     name: 'marketPrice',
     rule: (arg) => arg >= 0.01,
-    message: '商品市场价格必须大于等于 0.01 !!!'
+    message: '商品市场价格必须大于等于 0.01 !!!'
   },
   {
     name: 'costPrice',
     rule: (arg) => arg >= 0.01,
-    message: '商品成本价格必须大于等于 0.01 !!!'
+    message: '商品成本价格必须大于等于 0.01 !!!'
   }
 ]
 
@@ -359,23 +356,11 @@ const onChangeSpec = () => {
 }
 
 const categoryList = ref([]) // 分类树
-/**
- * 选择分类时触发校验
- */
-const categoryNodeClick = () => {
-  if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
-    formData.categoryId = null
-    message.warning('必须选择二级及以下节点!!')
-  }
-}
-/**
- * 获取分类的节点的完整结构
- *
- * @param categoryId 分类id
- */
-const categoryString = (categoryId) => {
+/** 获取分类的节点的完整结构 */
+const formatCategoryName = (categoryId) => {
   return treeToString(categoryList.value, categoryId)
 }
+
 const brandList = ref([]) // 精简商品品牌列表
 const deliveryTemplateList = ref([]) // 运费模版
 onMounted(async () => {

+ 1 - 1
src/views/mall/product/spu/form/OtherSettingsForm.vue

@@ -41,7 +41,7 @@
         </el-form-item>
       </el-col>
       <el-col :span="24">
-        <!--   TODO tag展示暂时不考虑排序 -->
+        <!--   TODO @puhui999:tag展示暂时不考虑排序;支持拖动排序 -->
         <el-form-item label="活动优先级">
           <el-tag>默认</el-tag>
           <el-tag class="ml-2" type="success">秒杀</el-tag>

+ 2 - 2
src/views/mall/product/spu/form/index.vue

@@ -102,7 +102,7 @@ const getDetail = async () => {
   if ('ProductSpuDetail' === name) {
     isDetail.value = true
   }
-  const id = params.spuId as unknown as number
+  const id = params.id as unknown as number
   if (id) {
     formLoading.value = true
     try {
@@ -161,7 +161,7 @@ const submitForm = async () => {
     deepCopyFormData.sliderPicUrls = newSliderPicUrls
     // 校验都通过后提交表单
     const data = deepCopyFormData as ProductSpuApi.Spu
-    const id = params.spuId as unknown as number
+    const id = params.id as unknown as number
     if (!id) {
       await ProductSpuApi.createSpu(data)
       message.success(t('common.createSuccess'))

+ 54 - 82
src/views/mall/product/spu/index.vue

@@ -18,15 +18,14 @@
         />
       </el-form-item>
       <el-form-item label="商品分类" prop="categoryId">
-        <el-tree-select
+        <el-cascader
           v-model="queryParams.categoryId"
-          :data="categoryList"
+          :options="categoryList"
           :props="defaultProps"
-          check-strictly
           class="w-1/1"
-          node-key="id"
+          clearable
           placeholder="请选择商品分类"
-          @change="nodeClick"
+          filterable
         />
       </el-form-item>
       <el-form-item label="创建时间" prop="createTime">
@@ -78,7 +77,7 @@
       />
     </el-tabs>
     <el-table v-loading="loading" :data="list">
-      <el-table-column type="expand" width="30">
+      <el-table-column type="expand">
         <template #default="{ row }">
           <el-form class="spu-table-expand" label-position="left">
             <el-row>
@@ -86,17 +85,17 @@
                 <el-row>
                   <el-col :span="8">
                     <el-form-item label="商品分类:">
-                      <span>{{ categoryString(row.categoryId) }}</span>
+                      <span>{{ formatCategoryName(row.categoryId) }}</span>
                     </el-form-item>
                   </el-col>
                   <el-col :span="8">
                     <el-form-item label="市场价:">
-                      <span>{{ floatToFixed2(row.marketPrice) }}元</span>
+                      <span>{{ fenToYuan(row.marketPrice) }}</span>
                     </el-form-item>
                   </el-col>
                   <el-col :span="8">
                     <el-form-item label="成本价:">
-                      <span>{{ floatToFixed2(row.costPrice) }}元</span>
+                      <span>{{ fenToYuan(row.costPrice) }}</span>
                     </el-form-item>
                   </el-col>
                 </el-row>
@@ -106,9 +105,8 @@
               <el-col :span="24">
                 <el-row>
                   <el-col :span="8">
-                    <el-form-item label="收藏:">
-                      <!-- TODO 没有这个属性,暂时写死 5 个 -->
-                      <span>5</span>
+                    <el-form-item label="浏览量:">
+                      <span>{{ row.browseCount }}</span>
                     </el-form-item>
                   </el-col>
                   <el-col :span="8">
@@ -122,15 +120,15 @@
           </el-form>
         </template>
       </el-table-column>
-      <el-table-column key="id" align="center" label="商品编号" prop="id" />
+      <el-table-column align="center" label="商品编号" min-width="60" prop="id" />
       <el-table-column label="商品图" min-width="80">
         <template #default="{ row }">
-          <el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
+          <el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
         </template>
       </el-table-column>
       <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
       <el-table-column align="center" label="商品售价" min-width="90" prop="price">
-        <template #default="{ row }"> {{ floatToFixed2(row.price) }}元</template>
+        <template #default="{ row }"> {{ fenToYuan(row.price) }}元</template>
       </el-table-column>
       <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
       <el-table-column align="center" label="库存" min-width="90" prop="stock" />
@@ -152,7 +150,7 @@
               active-text="上架"
               inactive-text="下架"
               inline-prompt
-              @change="changeStatus(row)"
+              @change="handleStatusChange(row)"
             />
           </template>
           <template v-else>
@@ -191,7 +189,7 @@
               v-hasPermi="['product:spu:update']"
               link
               type="primary"
-              @click="changeStatus(row, ProductSpuStatusEnum.DISABLE.status)"
+              @click="handleStatus02Change(row, ProductSpuStatusEnum.DISABLE.status)"
             >
               恢复到仓库
             </el-button>
@@ -201,7 +199,7 @@
               v-hasPermi="['product:spu:update']"
               link
               type="primary"
-              @click="changeStatus(row, ProductSpuStatusEnum.RECYCLE.status)"
+              @click="handleStatus02Change(row, ProductSpuStatusEnum.RECYCLE.status)"
             >
               加入回收站
             </el-button>
@@ -220,12 +218,11 @@
 </template>
 <script lang="ts" setup>
 import { TabsPaneContext } from 'element-plus'
-import { cloneDeep } from 'lodash-es'
 import { createImageViewer } from '@/components/ImageViewer'
 import { dateFormatter } from '@/utils/formatTime'
-import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
+import { defaultProps, handleTree, treeToString } from '@/utils/tree'
 import { ProductSpuStatusEnum } from '@/utils/constants'
-import { floatToFixed2 } from '@/utils'
+import { fenToYuan } from '@/utils'
 import download from '@/utils/download'
 import * as ProductSpuApi from '@/api/mall/product/spu'
 import * as ProductCategoryApi from '@/api/mall/product/category'
@@ -254,7 +251,7 @@ const tabsData = ref([
   },
   {
     count: 0,
-    name: '已经售空商品',
+    name: '已售罄商品',
     type: 2
   },
   {
@@ -303,43 +300,37 @@ const getList = async () => {
   }
 }
 
-/**
- * 更改 SPU 状态
- *
- * @param row
- * @param status 更改前的值
- */
-const changeStatus = async (row, status?: number) => {
-  const deepCopyValue = cloneDeep(unref(row))
-  if (typeof status !== 'undefined') deepCopyValue.status = status
+/** 添加到仓库 / 回收站的状态 */
+const handleStatus02Change = async (row, newStatus: number) => {
+  try {
+    // 二次确认
+    const text = newStatus === ProductSpuStatusEnum.RECYCLE.status ? '加入到回收站' : '恢复到仓库'
+    await message.confirm(`确认要"${row.name}"${text}吗?`)
+    // 发起修改
+    await ProductSpuApi.updateStatus({ id: row.id, status: newStatus })
+    message.success(text + '成功')
+    // 刷新 tabs 数据
+    await getTabsCount()
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 更新上架/下架状态 */
+const handleStatusChange = async (row) => {
   try {
-    let text = ''
-    switch (deepCopyValue.status) {
-      case ProductSpuStatusEnum.DISABLE.status:
-        text = ProductSpuStatusEnum.DISABLE.name
-        break
-      case ProductSpuStatusEnum.ENABLE.status:
-        text = ProductSpuStatusEnum.ENABLE.name
-        break
-      case ProductSpuStatusEnum.RECYCLE.status:
-        text = `加入${ProductSpuStatusEnum.RECYCLE.name}`
-        break
-    }
-    await message.confirm(
-      deepCopyValue.status === -1
-        ? `确认要将[${row.name}]${text}吗?`
-        : row.status === -1 // 再判断一次原对象是否等于-1,例: 把回收站中的商品恢复到仓库中,事件触发时原对象status为-1 深拷贝对象status被赋值为0
-        ? `确认要将[${row.name}]恢复到仓库吗?`
-        : `确认要${text}[${row.name}]吗?`
-    )
-    await ProductSpuApi.updateStatus({ id: deepCopyValue.id, status: deepCopyValue.status })
-    message.success('更新状态成功')
+    // 二次确认
+    const text = row.status ? '上架' : '下架'
+    await message.confirm(`确认要${text}"${row.name}"吗?`)
+    // 发起修改
+    await ProductSpuApi.updateStatus({ id: row.id, status: row.status })
+    message.success(text + '成功')
     // 刷新 tabs 数据
     await getTabsCount()
     // 刷新列表
     await getList()
   } catch {
-    // 取消更改状态时回显数据
+    // 异常时,需要重置回之前的值
     row.status =
       row.status === ProductSpuStatusEnum.DISABLE.status
         ? ProductSpuStatusEnum.ENABLE.status
@@ -380,26 +371,20 @@ const resetQuery = () => {
   handleQuery()
 }
 
-/**
- * 新增或修改
- *
- * @param id 商品 SPU 编号
- */
+/** 新增或修改 */
 const openForm = (id?: number) => {
   // 修改
   if (typeof id === 'number') {
-    push({ name: 'ProductSpuEdit', params: { spuId: id } })
+    push({ name: 'ProductSpuEdit', params: { id } })
     return
   }
   // 新增
   push({ name: 'ProductSpuAdd' })
 }
 
-/**
- * 查看商品详情
- */
+/** 查看商品详情 */
 const openDetail = (id: number) => {
-  push({ name: 'ProductSpuDetail', params: { spuId: id } })
+  push({ name: 'ProductSpuDetail', params: { id } })
 }
 
 /** 导出按钮操作 */
@@ -417,6 +402,12 @@ const handleExport = async () => {
   }
 }
 
+const categoryList = ref() // 分类树
+/** 获取分类的节点的完整结构 */
+const formatCategoryName = (categoryId) => {
+  return treeToString(categoryList.value, categoryId)
+}
+
 // 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
 watch(
   () => currentRoute.value,
@@ -425,25 +416,6 @@ watch(
   }
 )
 
-const categoryList = ref() // 分类树
-/**
- * 获取分类的节点的完整结构
- * @param categoryId 分类id
- */
-const categoryString = (categoryId) => {
-  return treeToString(categoryList.value, categoryId)
-}
-
-/**
- * 校验所选是否为二级及以下节点
- */
-const nodeClick = () => {
-  if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
-    queryParams.value.categoryId = null
-    message.warning('必须选择二级及以下节点!!')
-  }
-}
-
 /** 初始化 **/
 onMounted(async () => {
   await getTabsCount()

+ 7 - 6
src/views/mall/promotion/bargain/activity/BargainActivityForm.vue

@@ -30,7 +30,7 @@
           <el-table-column align="center" label="砍价底价(元)" min-width="168">
             <template #default="{ row: sku }">
               <el-input-number
-                v-model="sku.productConfig.bargainPrice"
+                v-model="sku.productConfig.bargainMinPrice"
                 :min="0"
                 :precision="2"
                 :step="0.1"
@@ -86,7 +86,7 @@ const ruleConfig: RuleConfig[] = [
     message: '商品砍价起始价格不能小于 0 !!!'
   },
   {
-    name: 'productConfig.bargainPrice',
+    name: 'productConfig.bargainMinPrice',
     rule: (arg) => arg >= 0,
     message: '商品砍价底价不能小于 0 !!!'
   },
@@ -123,14 +123,14 @@ const getSpuDetails = async (
       spuId: spu.id!,
       skuId: sku.id!,
       bargainFirstPrice: 1,
-      bargainPrice: 1,
+      bargainMinPrice: 1,
       stock: 1
     }
     if (typeof products !== 'undefined') {
       const product = products.find((item) => item.skuId === sku.id)
       if (product) {
         product.bargainFirstPrice = formatToFraction(product.bargainFirstPrice)
-        product.bargainPrice = formatToFraction(product.bargainPrice)
+        product.bargainMinPrice = formatToFraction(product.bargainMinPrice)
       }
       config = product || config
     }
@@ -173,7 +173,7 @@ const open = async (type: string, id?: number) => {
             spuId: data.spuId!,
             skuId: data.skuId,
             bargainFirstPrice: data.bargainFirstPrice, // 砍价起始价格,单位分
-            bargainPrice: data.bargainPrice, // 砍价底价
+            bargainMinPrice: data.bargainMinPrice, // 砍价底价
             stock: data.stock // 活动库存
           }
         ]
@@ -204,12 +204,13 @@ const submitForm = async () => {
   // 提交请求
   formLoading.value = true
   try {
+    // TODO @puhui999: 这样要深克隆
     const data = formRef.value.formModel as BargainActivityApi.BargainActivityVO
     const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
     products.forEach((item: BargainProductVO) => {
       // 砍价价格元转分
       item.bargainFirstPrice = convertToInteger(item.bargainFirstPrice)
-      item.bargainPrice = convertToInteger(item.bargainPrice)
+      item.bargainMinPrice = convertToInteger(item.bargainMinPrice)
     })
     // 用户每次砍价金额分转元, 元转分
     data.randomMinPrice = convertToInteger(data.randomMinPrice)

+ 2 - 21
src/views/mall/promotion/bargain/activity/bargainActivity.data.ts

@@ -6,7 +6,7 @@ export const rules = reactive({
   name: [required],
   startTime: [required],
   endTime: [required],
-  userSize: [required],
+  helpMaxCount: [required],
   bargainCount: [required],
   singleLimitCount: [required]
 })
@@ -72,7 +72,7 @@ const crudSchemas = reactive<CrudSchema[]>([
   },
   {
     label: '砍价人数',
-    field: 'userSize',
+    field: 'helpMaxCount',
     isSearch: false,
     form: {
       component: 'InputNumber',
@@ -132,20 +132,6 @@ const crudSchemas = reactive<CrudSchema[]>([
       value: 0
     }
   },
-  {
-    label: '砍价成功数量',
-    field: 'successCount',
-    isSearch: false,
-    isForm: false
-  },
-  {
-    label: '活动状态',
-    field: 'status',
-    dictType: DICT_TYPE.COMMON_STATUS,
-    dictClass: 'number',
-    isSearch: true,
-    isForm: false
-  },
   {
     label: '拼团商品',
     field: 'spuId',
@@ -155,11 +141,6 @@ const crudSchemas = reactive<CrudSchema[]>([
         span: 24
       }
     }
-  },
-  {
-    label: '操作',
-    field: 'action',
-    isForm: false
   }
 ])
 export const { allSchemas } = useCrudSchemas(crudSchemas)

+ 195 - 70
src/views/mall/promotion/bargain/activity/index.vue

@@ -1,90 +1,195 @@
 <template>
-  <!-- 搜索工作栏 -->
   <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
-      <!-- 新增等操作按钮 -->
-      <template #actionMore>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="活动名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入活动名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="活动状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择活动状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
         <el-button
-          v-hasPermi="['promotion:bargain-activity:create']"
-          plain
           type="primary"
+          plain
           @click="openForm('create')"
+          v-hasPermi="['promotion:bargain-activity:create']"
         >
-          <Icon class="mr-5px" icon="ep:plus" />
-          新增
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
         </el-button>
-      </template>
-    </Search>
+      </el-form-item>
+    </el-form>
   </ContentWrap>
 
   <!-- 列表 -->
   <ContentWrap>
-    <Table
-      v-model:currentPage="tableObject.currentPage"
-      v-model:pageSize="tableObject.pageSize"
-      :columns="allSchemas.tableColumns"
-      :data="tableObject.tableList"
-      :loading="tableObject.loading"
-      :pagination="{
-        total: tableObject.total
-      }"
-    >
-      <template #spuId="{ row }">
-        <el-image
-          :src="row.picUrl"
-          class="w-30px h-30px align-middle mr-5px"
-          @click="imagePreview(row.picUrl)"
-        />
-        <span class="align-middle">{{ row.spuName }}</span>
-      </template>
-      <template #action="{ row }">
-        <el-button
-          v-hasPermi="['promotion:bargain-activity:update']"
-          link
-          type="primary"
-          @click="openForm('update', row.id)"
-        >
-          编辑
-        </el-button>
-        <el-button
-          v-hasPermi="['promotion:bargain-activity:delete']"
-          link
-          type="danger"
-          @click="handleDelete(row.id)"
-        >
-          删除
-        </el-button>
-      </template>
-    </Table>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="活动编号" prop="id" min-width="80" />
+      <el-table-column label="活动名称" prop="name" min-width="140" />
+      <el-table-column label="活动时间" min-width="210">
+        <template #default="scope">
+          {{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
+          ~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
+        </template>
+      </el-table-column>
+      <el-table-column label="商品图片" prop="spuName" min-width="80">
+        <template #default="scope">
+          <el-image
+            :src="scope.row.picUrl"
+            class="h-40px w-40px"
+            :preview-src-list="[scope.row.picUrl]"
+            preview-teleported
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="商品标题" prop="spuName" min-width="300" />
+      <el-table-column
+        label="起始价格"
+        prop="bargainFirstPrice"
+        min-width="100"
+        :formatter="fenToYuanFormat"
+      />
+      <el-table-column
+        label="砍价底价"
+        prop="bargainMinPrice"
+        min-width="100"
+        :formatter="fenToYuanFormat"
+      />
+      <el-table-column label="总砍价人数" prop="recordUserCount" min-width="100" />
+      <el-table-column label="成功砍价人数" prop="recordSuccessUserCount" min-width="110" />
+      <el-table-column label="助力人数" prop="helpUserCount" min-width="100" />
+      <el-table-column label="活动状态" align="center" prop="status" min-width="100">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="库存" align="center" prop="stock" min-width="80" />
+      <el-table-column label="总库存" align="center" prop="totalStock" min-width="80" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" width="150px" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['promotion:bargain-activity:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleClose(scope.row.id)"
+            v-if="scope.row.status === 0"
+            v-hasPermi="['promotion:bargain-activity:close']"
+          >
+            关闭
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-else
+            v-hasPermi="['promotion:bargain-activity:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
   <BargainActivityForm ref="formRef" @success="getList" />
 </template>
-<script lang="ts" setup>
-import { allSchemas } from './bargainActivity.data'
+
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
 import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivity'
 import BargainActivityForm from './BargainActivityForm.vue'
-import { createImageViewer } from '@/components/ImageViewer'
-import { sortTableColumns } from '@/hooks/web/useCrudSchemas'
+import { formatDate } from '@/utils/formatTime'
+import { fenToYuanFormat } from '@/utils/formatter'
+import { fenToYuan } from '@/utils'
 
 defineOptions({ name: 'PromotionBargainActivity' })
 
-// tableObject:表格的属性对象,可获得分页大小、条数等属性
-// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
-// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
-const { tableObject, tableMethods } = useTable({
-  getListApi: BargainActivityApi.getBargainActivityPage, // 分页接口
-  delListApi: BargainActivityApi.deleteBargainActivity // 删除接口
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: null,
+  status: null
 })
-// 获得表格的各种操作
-const { getList, setSearchParams } = tableMethods
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
 
-/** 商品图预览 */
-const imagePreview = (imgUrl: string) => {
-  createImageViewer({
-    urlList: [imgUrl]
-  })
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await BargainActivityApi.getBargainActivityPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
 }
 
 /** 添加/修改操作 */
@@ -93,15 +198,35 @@ const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
 
+// TODO 芋艿:这里要改下
+/** 关闭按钮操作 */
+const handleClose = async (id: number) => {
+  try {
+    // 关闭的二次确认
+    await message.confirm('确认关闭该秒杀活动吗?')
+    // 发起关闭
+    await BargainActivityApi.closeSeckillActivity(id)
+    message.success('关闭成功')
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
 /** 删除按钮操作 */
-const handleDelete = (id: number) => {
-  tableMethods.delList(id, false)
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await BargainActivityApi.deleteBargainActivity(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
 /** 初始化 **/
-onMounted(() => {
-  // 获得活动列表
-  sortTableColumns(allSchemas.tableColumns, 'spuId')
-  getList()
+onMounted(async () => {
+  await getList()
 })
 </script>

+ 90 - 0
src/views/mall/promotion/bargain/record/BargainRecordListDialog.vue

@@ -0,0 +1,90 @@
+<template>
+  <Dialog v-model="dialogVisible" title="助力列表">
+    <!-- 列表 -->
+    <ContentWrap>
+      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+        <el-table-column label="用户编号" prop="userId" min-width="80px" />
+        <el-table-column label="用户头像" prop="avatar" min-width="80px">
+          <template #default="scope">
+            <el-avatar :src="scope.row.avatar" />
+          </template>
+        </el-table-column>
+        <el-table-column label="用户昵称" prop="nickname" min-width="100px" />
+        <el-table-column
+          label="砍价金额"
+          prop="reducePrice"
+          min-width="100px"
+          :formatter="fenToYuanFormat"
+        />
+        <el-table-column
+          label="助力时间"
+          align="center"
+          prop="createTime"
+          :formatter="dateFormatter"
+          width="180px"
+        />
+      </el-table>
+      <!-- 分页 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </ContentWrap>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import * as BargainHelpApi from '@/api/mall/promotion/bargain/bargainHelp'
+import { fenToYuanFormat } from '@/utils/formatter'
+
+/** 助力列表 */
+defineOptions({ name: 'BargainRecordListDialog' })
+
+const message = useMessage() // 消息弹窗
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  recordId: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 打开弹窗 */
+const dialogVisible = ref(false) // 弹窗的是否展示
+const open = async (recordId: any) => {
+  dialogVisible.value = true
+  queryParams.recordId = recordId
+  resetQuery()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await BargainHelpApi.getBargainHelpPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  handleQuery()
+}
+</script>

+ 195 - 0
src/views/mall/promotion/bargain/record/index.vue

@@ -0,0 +1,195 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="砍价状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择砍价状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="daterange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+        <el-button
+          type="primary"
+          plain
+          @click="openForm('create')"
+          v-hasPermi="['promotion:bargain-record:create']"
+        >
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          @click="handleExport"
+          :loading="exportLoading"
+          v-hasPermi="['promotion:bargain-record:export']"
+        >
+          <Icon icon="ep:download" class="mr-5px" /> 导出
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="编号" min-width="50" prop="id" />
+      <el-table-column label="发起用户" min-width="120">
+        <template #default="scope">
+          <el-image
+            :src="scope.row.avatar"
+            class="h-20px w-20px"
+            :preview-src-list="[scope.row.avatar]"
+            preview-teleported
+          />
+          {{ scope.row.nickname }}
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="发起时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="砍价活动" min-width="150" prop="activity.name" />
+      <el-table-column
+        label="最低价"
+        min-width="100"
+        prop="activity.bargainMinPrice"
+        :formatter="fenToYuanFormat"
+      />
+      <el-table-column
+        label="当前价"
+        min-width="100"
+        prop="bargainPrice"
+        :formatter="fenToYuanFormat"
+      />
+      <el-table-column label="总砍价次数" min-width="100" prop="activity.helpMaxCount" />
+      <el-table-column label="剩余砍价次数" min-width="100" prop="helpCount" />
+      <el-table-column label="砍价状态" align="center" prop="status">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column
+        label="结束时间"
+        align="center"
+        prop="endTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="订单编号" align="center" prop="orderId" />
+      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openRecordListDialog(scope.row.id)"
+            v-hasPermi="['promotion:bargain-help:query']"
+          >
+            助力
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <!-- 表单弹窗 -->
+  <BargainRecordListDialog ref="recordListDialogRef" />
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
+import * as BargainRecordApi from '@/api/mall/promotion/bargain/bargainRecord'
+import { fenToYuanFormat } from '@/utils/formatter'
+import BargainRecordListDialog from './BargainRecordListDialog.vue'
+
+defineOptions({ name: 'PromotionBargainRecord' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  status: null,
+  createTime: []
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await BargainRecordApi.getBargainRecordPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 打开[助力]弹窗 */
+const recordListDialogRef = ref()
+const openRecordListDialog = (id?: number) => {
+  recordListDialogRef.value.open(id)
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>

+ 1 - 0
src/views/mall/promotion/combination/activity/CombinationActivityForm.vue

@@ -167,6 +167,7 @@ const submitForm = async () => {
     products.forEach((item: CombinationActivityApi.CombinationProductVO) => {
       item.combinationPrice = convertToInteger(item.combinationPrice)
     })
+    // TODO @puhui999: 这样要深克隆
     const data = formRef.value.formModel as CombinationActivityApi.CombinationActivityVO
     data.products = products
     // 真正提交

+ 1 - 1
src/views/mall/promotion/combination/activity/index.vue

@@ -33,7 +33,7 @@
       <template #spuId="{ row }">
         <el-image
           :src="row.picUrl"
-          class="w-30px h-30px align-middle mr-5px"
+          class="mr-5px h-30px w-30px align-middle"
           @click="imagePreview(row.picUrl)"
         />
         <span class="align-middle">{{ row.spuName }}</span>

+ 1 - 1
src/views/mall/promotion/components/SpuAndSkuList.vue

@@ -18,7 +18,7 @@
     <el-table-column key="id" align="center" label="商品编号" prop="id" />
     <el-table-column label="商品图" min-width="80">
       <template #default="{ row }">
-        <el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
+        <el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
       </template>
     </el-table-column>
     <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />

+ 1 - 1
src/views/mall/promotion/components/SpuSelect.vue

@@ -70,7 +70,7 @@
         <el-table-column key="id" align="center" label="商品编号" prop="id" />
         <el-table-column label="商品图" min-width="80">
           <template #default="{ row }">
-            <el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
+            <el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
           </template>
         </el-table-column>
         <el-table-column

+ 1 - 1
src/views/mall/promotion/coupon/formatter.ts

@@ -9,7 +9,7 @@ export const discountFormat = (row: CouponTemplateVO) => {
     return `¥${floatToFixed2(row.discountPrice)}`
   }
   if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
-    return `${row.discountPrice}%`
+    return `${row.discountPercent}%`
   }
   return '未知【' + row.discountType + '】'
 }

+ 11 - 5
src/views/mall/promotion/coupon/index.vue

@@ -19,7 +19,7 @@
           @keyup="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="创建时间" prop="createTime">
+      <el-form-item label="领取时间" prop="createTime">
         <el-date-picker
           v-model="queryParams.createTime"
           value-format="YYYY-MM-DD HH:mm:ss"
@@ -50,12 +50,17 @@
 
     <!-- 列表 -->
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="会员信息" align="center" prop="nickname" />
-      <!-- TODO 芋艿:以后支持头像,支持跳转 -->
-      <el-table-column label="优惠劵" align="center" prop="name" />
-      <el-table-column label="优惠券类型" align="center" prop="discountType">
+      <el-table-column label="会员昵称" align="center" min-width="100" prop="nickname" />
+      <el-table-column label="优惠券名称" align="center" min-width="140" prop="name" />
+      <el-table-column label="类型" align="center" prop="discountType">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE" :value="scope.row.productScope" />
+        </template>
+      </el-table-column>
+      <el-table-column label="优惠" min-width="100" prop="discount">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
+          {{ discountFormat(scope.row) }}
         </template>
       </el-table-column>
       <el-table-column label="领取方式" align="center" prop="takeType">
@@ -109,6 +114,7 @@
 import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon/coupon'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
+import { discountFormat } from '@/views/mall/promotion/coupon/formatter'
 
 defineOptions({ name: 'PromotionCoupon' })
 

+ 15 - 13
src/views/mall/promotion/coupon/template/CouponTemplateForm.vue

@@ -26,7 +26,7 @@
         v-if="formData.productScope === PromotionProductScopeEnum.SPU.scope"
         prop="productSpuIds"
       >
-        <div class="flex items-center gap-1 flex-wrap">
+        <div class="flex flex-wrap items-center gap-1">
           <div class="select-box spu-pic" v-for="(spu, index) in productSpus" :key="spu.id">
             <el-image :src="spu.picUrl" />
             <Icon icon="ep:circle-close-filled" class="del-icon" @click="handleRemoveSpu(index)" />
@@ -62,7 +62,7 @@
         <el-input-number
           v-model="formData.discountPrice"
           placeholder="请输入优惠金额,单位:元"
-          class="!w-400px mr-2"
+          class="mr-2 !w-400px"
           :precision="2"
           :min="0"
         />
@@ -76,7 +76,7 @@
         <el-input-number
           v-model="formData.discountPercent"
           placeholder="优惠券折扣不能小于 1 折,且不可大于 9.9 折"
-          class="!w-400px mr-2"
+          class="mr-2 !w-400px"
           :precision="1"
           :min="1"
           :max="9.9"
@@ -91,7 +91,7 @@
         <el-input-number
           v-model="formData.discountLimitPrice"
           placeholder="请输入最多优惠"
-          class="!w-400px mr-2"
+          class="mr-2 !w-400px"
           :precision="2"
           :min="0"
         />
@@ -101,7 +101,7 @@
         <el-input-number
           v-model="formData.usePrice"
           placeholder="无门槛请设为 0"
-          class="!w-400px mr-2"
+          class="mr-2 !w-400px"
           :precision="2"
           :min="0"
         />
@@ -117,7 +117,7 @@
         <el-input-number
           v-model="formData.totalCount"
           placeholder="发放数量,没有之后不能领取或发放,-1 为不限制"
-          class="!w-400px mr-2"
+          class="mr-2 !w-400px"
           :precision="0"
           :min="-1"
         />
@@ -127,7 +127,7 @@
         <el-input-number
           v-model="formData.takeLimitCount"
           placeholder="设置为 -1 时,可无限领取"
-          class="!w-400px mr-2"
+          class="mr-2 !w-400px"
           :precision="0"
           :min="-1"
         />
@@ -423,22 +423,24 @@ const handleRemoveSpu = (index: number) => {
 <style scoped lang="scss">
 .select-box {
   display: flex;
-  align-items: center;
-  justify-content: center;
-  border: 1px dashed var(--el-border-color-darker);
-  border-radius: 8px;
   width: 60px;
   height: 60px;
+  border: 1px dashed var(--el-border-color-darker);
+  border-radius: 8px;
+  align-items: center;
+  justify-content: center;
 }
+
 .spu-pic {
   position: relative;
 }
+
 .del-icon {
   position: absolute;
+  top: -10px;
+  right: -10px;
   z-index: 1;
   width: 20px !important;
   height: 20px !important;
-  right: -10px;
-  top: -10px;
 }
 </style>

+ 18 - 21
src/views/mall/promotion/coupon/template/index.vue

@@ -19,7 +19,7 @@
           @keyup="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="优惠类型" prop="discountType">
+      <el-form-item label="优惠类型" prop="discountType">
         <el-select
           v-model="queryParams.discountType"
           class="!w-240px"
@@ -71,14 +71,6 @@
         >
           <Icon class="mr-5px" icon="ep:plus" /> 新增
         </el-button>
-        <el-button
-          plain
-          type="success"
-          @click="$router.push('/promotion/coupon')"
-          v-hasPermi="['promotion:coupon:query']"
-        >
-          <Icon icon="ep:operation" class="mr-5px" />会员优惠劵
-        </el-button>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -86,17 +78,29 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list">
-      <el-table-column label="优惠券名称" align="center" prop="name" />
-      <el-table-column label="优惠券类型" align="center" prop="discountType">
+      <el-table-column label="优惠券名称" min-width="140" prop="name" />
+      <el-table-column label="类型" min-width="80" prop="productScope">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE" :value="scope.row.productScope" />
+        </template>
+      </el-table-column>
+      <el-table-column label="优惠" min-width="100" prop="discount">
         <template #default="scope">
           <dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
+          {{ discountFormat(scope.row) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="领取方式" min-width="100" prop="takeType">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE" :value="scope.row.takeType" />
         </template>
       </el-table-column>
       <el-table-column
-        label="优惠金额 / 折扣"
+        label="使用时间"
         align="center"
-        prop="discount"
-        :formatter="discountFormat"
+        prop="validityType"
+        width="185"
+        :formatter="validityTypeFormat"
       />
       <el-table-column label="发放数量" align="center" prop="totalCount" />
       <el-table-column
@@ -111,13 +115,6 @@
         prop="takeLimitCount"
         :formatter="takeLimitCountFormat"
       />
-      <el-table-column
-        label="有效期限"
-        align="center"
-        prop="validityType"
-        width="190"
-        :formatter="validityTypeFormat"
-      />
       <el-table-column label="状态" align="center" prop="status">
         <template #default="scope">
           <el-switch

+ 211 - 90
src/views/mall/promotion/seckill/activity/index.vue

@@ -1,94 +1,205 @@
 <template>
-  <doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
-
-  <!-- 搜索工作栏 -->
   <ContentWrap>
-    <Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
-      <!-- 新增等操作按钮 -->
-      <template #actionMore>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="活动名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入活动名称"
+          clearable
+          @keyup.enter="handleQuery"
+          class="!w-240px"
+        />
+      </el-form-item>
+      <el-form-item label="活动状态" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          placeholder="请选择活动状态"
+          clearable
+          class="!w-240px"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
         <el-button
-          v-hasPermi="['promotion:seckill-activity:create']"
-          plain
           type="primary"
+          plain
           @click="openForm('create')"
+          v-hasPermi="['promotion:seckill-activity:create']"
         >
-          <Icon class="mr-5px" icon="ep:plus" /> 新增
+          <Icon icon="ep:plus" class="mr-5px" /> 新增
         </el-button>
-      </template>
-    </Search>
+      </el-form-item>
+    </el-form>
   </ContentWrap>
 
   <!-- 列表 -->
   <ContentWrap>
-    <Table
-      v-model:currentPage="tableObject.currentPage"
-      v-model:pageSize="tableObject.pageSize"
-      :columns="allSchemas.tableColumns"
-      :data="tableObject.tableList"
-      :expand="true"
-      :loading="tableObject.loading"
-      :pagination="{
-        total: tableObject.total
-      }"
-      @expand-change="expandChange"
-    >
-      <template #expand> 展示活动商品和商品相关属性活动配置</template>
-      <template #spuId="{ row }">
-        <el-image
-          :src="row.picUrl"
-          class="w-30px h-30px align-middle mr-5px"
-          @click="imagePreview(row.picUrl)"
-        />
-        <span class="align-middle">{{ row.spuName }}</span>
-      </template>
-      <template #configIds="{ row }">
-        <el-tag v-for="(name, index) in convertSeckillConfigNames(row)" :key="index" class="mr-5px">
-          {{ name }}
-        </el-tag>
-      </template>
-      <template #action="{ row }">
-        <el-button
-          v-hasPermi="['promotion:seckill-activity:update']"
-          link
-          type="primary"
-          @click="openForm('update', row.id)"
-        >
-          编辑
-        </el-button>
-        <el-button
-          v-hasPermi="['promotion:seckill-activity:delete']"
-          link
-          type="danger"
-          @click="handleDelete(row.id)"
-        >
-          删除
-        </el-button>
-      </template>
-    </Table>
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="活动编号" prop="id" min-width="80" />
+      <el-table-column label="活动名称" prop="name" min-width="140" />
+      <el-table-column
+        label="秒杀时段"
+        prop="configIds"
+        width="220px"
+        :show-overflow-tooltip="false"
+      >
+        <template #default="scope">
+          <el-tag v-for="(configId, index) in scope.row.configIds" :key="index" class="mr-5px">
+            {{ formatConfigNames(configId) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="活动时间" min-width="210">
+        <template #default="scope">
+          {{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
+          ~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
+        </template>
+      </el-table-column>
+      <el-table-column label="商品图片" prop="spuName" min-width="80">
+        <template #default="scope">
+          <el-image
+            :src="scope.row.picUrl"
+            class="h-40px w-40px"
+            :preview-src-list="[scope.row.picUrl]"
+            preview-teleported
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="商品标题" prop="spuName" min-width="300" />
+      <el-table-column
+        label="原价"
+        prop="marketPrice"
+        min-width="100"
+        :formatter="fenToYuanFormat"
+      />
+      <el-table-column label="秒杀价" prop="marketPrice" min-width="100">
+        <template #default="scope">
+          {{ formatSeckillPrice(scope.row.products) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="活动状态" align="center" prop="status" min-width="100">
+        <template #default="scope">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="库存" align="center" prop="stock" min-width="80" />
+      <el-table-column label="总库存" align="center" prop="totalStock" min-width="80" />
+      <el-table-column
+        label="创建时间"
+        align="center"
+        prop="createTime"
+        :formatter="dateFormatter"
+        width="180px"
+      />
+      <el-table-column label="操作" align="center" width="150px" fixed="right">
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openForm('update', scope.row.id)"
+            v-hasPermi="['promotion:seckill-activity:update']"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleClose(scope.row.id)"
+            v-if="scope.row.status === 0"
+            v-hasPermi="['promotion:seckill-activity:close']"
+          >
+            关闭
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="handleDelete(scope.row.id)"
+            v-else
+            v-hasPermi="['promotion:seckill-activity:delete']"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
   <SeckillActivityForm ref="formRef" @success="getList" />
 </template>
-<script lang="ts" setup>
-import { allSchemas } from './seckillActivity.data'
-import { getSimpleSeckillConfigList } from '@/api/mall/promotion/seckill/seckillConfig'
+
+<script setup lang="ts">
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { dateFormatter } from '@/utils/formatTime'
 import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
+import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
 import SeckillActivityForm from './SeckillActivityForm.vue'
-import { createImageViewer } from '@/components/ImageViewer'
-import { sortTableColumns } from '@/hooks/web/useCrudSchemas'
+import { formatDate } from '@/utils/formatTime'
+import { fenToYuanFormat } from '@/utils/formatter'
+import { fenToYuan } from '@/utils'
 
-defineOptions({ name: 'PromotionSeckillActivity' })
+defineOptions({ name: 'SeckillActivity' })
 
-// tableObject:表格的属性对象,可获得分页大小、条数等属性
-// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
-// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
-const { tableObject, tableMethods } = useTable({
-  getListApi: SeckillActivityApi.getSeckillActivityPage, // 分页接口
-  delListApi: SeckillActivityApi.deleteSeckillActivity // 删除接口
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: null,
+  status: null
 })
-// 获得表格的各种操作
-const { getList, setSearchParams } = tableMethods
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await SeckillActivityApi.getSeckillActivityPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
 
 /** 添加/修改操作 */
 const formRef = ref()
@@ -96,37 +207,47 @@ const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
 
-/** 删除按钮操作 */
-const handleDelete = (id: number) => {
-  tableMethods.delList(id, false)
+/** 关闭按钮操作 */
+const handleClose = async (id: number) => {
+  try {
+    // 关闭的二次确认
+    await message.confirm('确认关闭该秒杀活动吗?')
+    // 发起关闭
+    await SeckillActivityApi.closeSeckillActivity(id)
+    message.success('关闭成功')
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
-/** 商品图预览 */
-const imagePreview = (imgUrl: string) => {
-  createImageViewer({
-    urlList: [imgUrl]
-  })
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await SeckillActivityApi.deleteSeckillActivity(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
 }
 
 const configList = ref([]) // 时段配置精简列表
-const convertSeckillConfigNames = computed(
-  () => (row) =>
-    configList.value
-      ?.filter((item) => row.configIds.includes(item.id))
-      ?.map((config) => config.name)
-)
+const formatConfigNames = (configId) => {
+  const config = configList.value.find((item) => item.id === configId)
+  return config != null ? `${config.name}[${config.startTime} ~ ${config.endTime}]` : ''
+}
 
-const expandChange = (row, expandedRows) => {
-  // TODO puhui:等 CRUD 完事后弄
-  console.log(row, expandedRows)
+const formatSeckillPrice = (products) => {
+  const seckillPrice = Math.min(...products.map((item) => item.seckillPrice))
+  return `¥${fenToYuan(seckillPrice)}`
 }
 
 /** 初始化 **/
 onMounted(async () => {
-  // 获得活动列表
-  sortTableColumns(allSchemas.tableColumns, 'spuId')
   await getList()
   // 获得秒杀时间段
-  configList.value = await getSimpleSeckillConfigList()
+  configList.value = await SeckillConfigApi.getSimpleSeckillConfigList()
 })
 </script>

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov