!185 master-vxe分支合并master分支1.8.0版本的修改
Merge pull request !185 from clockdotnet/master-vxe
This commit is contained in:
commit
fd46445147
32
.eslintrc.js
32
.eslintrc.js
@ -1,6 +1,4 @@
|
||||
// @ts-check
|
||||
const { defineConfig } = require('eslint-define-config')
|
||||
module.exports = defineConfig({
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
@ -8,6 +6,7 @@ module.exports = defineConfig({
|
||||
es6: true
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
plugins: ['vue'],
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 2020,
|
||||
@ -17,16 +16,9 @@ module.exports = defineConfig({
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'plugin:prettier/recommended',
|
||||
'./.eslintrc-auto-import.json'
|
||||
],
|
||||
extends: ['plugin:vue/vue3-recommended', 'prettier', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
|
||||
rules: {
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
@ -39,8 +31,20 @@ module.exports = defineConfig({
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'no-unused-vars': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'vue/attributes-order': 'off',
|
||||
@ -66,4 +70,4 @@ module.exports = defineConfig({
|
||||
],
|
||||
'vue/multi-word-component-names': 'off'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
159
.vscode/settings.json
vendored
159
.vscode/settings.json
vendored
@ -1,36 +1,98 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"prettier.enable": true,
|
||||
"editor.formatOnType": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"volar.tsPlugin": true,
|
||||
"volar.tsPluginStatus": false,
|
||||
"npm.packageManager": "pnpm",
|
||||
"editor.tabSize": 2,
|
||||
"prettier.printWidth": 100, // 超过最大值换行
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.eol": "\n",
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/*.log": true,
|
||||
"**/*.log*": true,
|
||||
"**/bower_components": true,
|
||||
"**/dist": true,
|
||||
"**/elehukouben": true,
|
||||
"**/.git": true,
|
||||
"**/.gitignore": true,
|
||||
"**/.svn": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/.idea": true,
|
||||
"**/.vscode": false,
|
||||
"**/yarn.lock": true,
|
||||
"**/tmp": true,
|
||||
"out": true,
|
||||
"dist": true,
|
||||
"node_modules": true,
|
||||
"CHANGELOG.md": true,
|
||||
"examples": true,
|
||||
"res": true,
|
||||
"screenshots": true,
|
||||
"yarn-error.log": true,
|
||||
"**/.yarn": true
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "Vue.volar"
|
||||
"files.exclude": {
|
||||
"**/.cache": true,
|
||||
"**/.editorconfig": true,
|
||||
"**/.eslintcache": true,
|
||||
"**/bower_components": true,
|
||||
"**/.idea": true,
|
||||
"**/tmp": true,
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true
|
||||
},
|
||||
"[javascript]": {
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/.vscode/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/tmp/**": true,
|
||||
"**/bower_components/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/yarn.lock": true
|
||||
},
|
||||
"stylelint.enable": true,
|
||||
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
|
||||
"path-intellisense.mappings": {
|
||||
"@/": "${workspaceRoot}/src"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
},
|
||||
"[less]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.fixAll.stylelint": true
|
||||
}
|
||||
},
|
||||
"i18n-ally.localesPaths": ["src/locales"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
@ -39,16 +101,69 @@
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"god.tsconfig": "./tsconfig.json",
|
||||
"vue-i18n.i18nPaths": "src/locales",
|
||||
"cSpell.words": [
|
||||
"vben",
|
||||
"windicss",
|
||||
"tailwind",
|
||||
"browserslist",
|
||||
"tailwindcss",
|
||||
"esnext",
|
||||
"antv",
|
||||
"tinymce",
|
||||
"qrcode",
|
||||
"sider",
|
||||
"pinia",
|
||||
"sider",
|
||||
"nprogress",
|
||||
"INTLIFY",
|
||||
"stylelint",
|
||||
"esno",
|
||||
"vitejs",
|
||||
"sortablejs",
|
||||
"codemirror",
|
||||
"iconify",
|
||||
"commitlint",
|
||||
"vditor",
|
||||
"echarts",
|
||||
"cropperjs",
|
||||
"logicflow",
|
||||
"vueuse",
|
||||
"zxcvbn",
|
||||
"lintstagedrc",
|
||||
"brotli",
|
||||
"sider",
|
||||
"pnpm",
|
||||
"antd"
|
||||
],
|
||||
"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
|
||||
}
|
||||
],
|
||||
// 控制相关文件嵌套展示
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
|
||||
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
|
||||
"*.env": "$(capture).env.*",
|
||||
"CHANGELOG.md": "CHANGELOG*",
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc,vite.config.*,windi.*,tailwind.*,tsconfig.*,postcss*",
|
||||
".eslintrc.js": ".eslintignore,.eslintrc-*,.prettierignore,.stylelintignore,.commitlintrc.js,.prettierrc.js,.stylelint*,stylelint*,prettier.*,.editorconfig"
|
||||
}
|
||||
"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"
|
||||
},
|
||||
"terminal.integrated.scrollback": 10000
|
||||
}
|
||||
|
12
README.md
12
README.md
@ -9,7 +9,7 @@
|
||||
|
||||
## 🐶 新手必读
|
||||
|
||||
* nodejs > 16.0.0 && pnpm > 7.30.0
|
||||
* nodejs > 16.0.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>
|
||||
@ -39,11 +39,11 @@
|
||||
| 框架 | 说明 | 版本 |
|
||||
|----------------------------------------------------------------------|------------------|--------|
|
||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.4 |
|
||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.3.8 |
|
||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.4 |
|
||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.3.9 |
|
||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.7 |
|
||||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.0.4 |
|
||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.3 |
|
||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.1.2 |
|
||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.4 |
|
||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.2.0 |
|
||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
|
||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.1 |
|
||||
| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
|
||||
@ -136,7 +136,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
||||
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
|
||||
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
|
||||
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
|
||||
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
|
||||
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
|
||||
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
|
||||
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
|
||||
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
|
||||
|
@ -12,7 +12,6 @@ import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus'
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
@ -28,7 +27,6 @@ export function createVitePlugins() {
|
||||
WindiCSS(),
|
||||
progress(),
|
||||
PurgeIcons(),
|
||||
vueSetupExtend(),
|
||||
ElementPlus({}),
|
||||
AutoImport({
|
||||
include: [
|
||||
|
106
package.json
106
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "1.7.3-snapshot",
|
||||
"version": "1.8.0-snapshot",
|
||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||
"author": "xingyu",
|
||||
"private": false,
|
||||
@ -9,12 +9,12 @@
|
||||
"dev": "vite --mode base",
|
||||
"front": "vite --mode front",
|
||||
"ts:check": "vue-tsc --noEmit",
|
||||
"build:pro": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode pro",
|
||||
"build:dev": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode dev",
|
||||
"build:stage": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode stage",
|
||||
"build:test": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode test",
|
||||
"build:static": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode static",
|
||||
"build:front": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode front",
|
||||
"build:pro": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode pro",
|
||||
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev",
|
||||
"build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
|
||||
"build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
|
||||
"build:static": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode static",
|
||||
"build:front": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode front",
|
||||
"serve:pro": "vite preview --mode pro",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
"serve:test": "vite preview --mode test",
|
||||
@ -32,12 +32,12 @@
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@form-create/designer": "^3.1.3",
|
||||
"@form-create/element-ui": "^3.1.18",
|
||||
"@iconify/iconify": "^3.1.0",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^3.0.1",
|
||||
"@zxcvbn-ts/core": "^3.0.2",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"benz-amr-recorder": "^1.1.5",
|
||||
@ -45,29 +45,30 @@
|
||||
"camunda-bpmn-moddle": "^7.0.1",
|
||||
"cropperjs": "^1.5.13",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"dayjs": "^1.11.9",
|
||||
"diagram-js": "^11.6.0",
|
||||
"echarts": "^5.4.2",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "2.3.4",
|
||||
"fast-xml-parser": "^4.2.2",
|
||||
"element-plus": "2.3.7",
|
||||
"fast-xml-parser": "^4.2.5",
|
||||
"highlight.js": "^11.8.0",
|
||||
"intro.js": "^7.0.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"min-dash": "^4.1.1",
|
||||
"mitt": "^3.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.3",
|
||||
"pinia": "^2.1.4",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"steady-xml": "^0.1.0",
|
||||
"url": "^0.11.0",
|
||||
"url": "^0.11.1",
|
||||
"video.js": "^8.3.0",
|
||||
"vue": "3.3.4",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.2.1",
|
||||
"vue-types": "^5.0.3",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^5.1.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-table": "^4.3.11",
|
||||
"web-storage-cache": "^1.1.1",
|
||||
@ -75,65 +76,61 @@
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.3",
|
||||
"@commitlint/config-conventional": "^17.6.3",
|
||||
"@iconify/json": "^2.2.67",
|
||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||
"@commitlint/cli": "^17.6.6",
|
||||
"@commitlint/config-conventional": "^17.6.6",
|
||||
"@iconify/json": "^2.2.87",
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.1",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/node": "^20.4.1",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
||||
"@typescript-eslint/parser": "^5.59.6",
|
||||
"@vitejs/plugin-legacy": "^4.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
||||
"@typescript-eslint/parser": "^5.61.0",
|
||||
"@vitejs/plugin-legacy": "^4.1.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"bpmn-js": "^8.9.0",
|
||||
"bpmn-js-properties-panel": "^0.46.0",
|
||||
"consola": "^3.1.0",
|
||||
"eslint": "^8.40.0",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-define-config": "^1.20.0",
|
||||
"eslint-define-config": "^1.21.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.13.0",
|
||||
"lint-staged": "^13.2.2",
|
||||
"postcss": "^8.4.23",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"lint-staged": "^13.2.3",
|
||||
"postcss": "^8.4.25",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.8",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup": "^3.22.0",
|
||||
"sass": "^1.62.1",
|
||||
"stylelint": "^15.6.2",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recommended": "^12.0.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"rollup": "^3.26.2",
|
||||
"sass": "^1.63.6",
|
||||
"stylelint": "^15.10.1",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.4.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"terser": "^5.17.4",
|
||||
"typescript": "5.0.4",
|
||||
"unplugin-auto-import": "^0.16.0",
|
||||
"unplugin-element-plus": "^0.7.1",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"vite": "4.3.8",
|
||||
"terser": "^5.18.2",
|
||||
"typescript": "5.1.6",
|
||||
"unplugin-auto-import": "^0.16.6",
|
||||
"unplugin-element-plus": "^0.7.2",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "4.4.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-ejs": "^1.6.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-progress": "^0.0.7",
|
||||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-top-level-await": "^1.3.0",
|
||||
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
|
||||
"vite-plugin-top-level-await": "^1.3.1",
|
||||
"vite-plugin-windicss": "^1.9.0",
|
||||
"vue-tsc": "^1.6.5",
|
||||
"vue-tsc": "^1.8.4",
|
||||
"windicss": "^3.5.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -142,5 +139,10 @@
|
||||
"bugs": {
|
||||
"url": "https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues"
|
||||
},
|
||||
"homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3"
|
||||
"homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3",
|
||||
"packageManager": "pnpm@8.6.0",
|
||||
"engines": {
|
||||
"node": ">= 16.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,11 @@ $prefix-cls: #{$namespace}-app;
|
||||
|
||||
html,
|
||||
body {
|
||||
@extend .size;
|
||||
|
||||
padding: 0 !important;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
@extend .size;
|
||||
|
||||
#app {
|
||||
@extend .size;
|
||||
|
@ -7,8 +7,7 @@ export interface Property {
|
||||
valueName?: string // 属性值名称
|
||||
}
|
||||
|
||||
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
|
||||
export interface SkuType {
|
||||
export interface Sku {
|
||||
id?: number // 商品 SKU 编号
|
||||
spuId?: number // SPU 编号
|
||||
properties?: Property[] // 属性数组
|
||||
@ -25,8 +24,7 @@ export interface SkuType {
|
||||
salesCount?: number // 商品销量
|
||||
}
|
||||
|
||||
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
|
||||
export interface SpuType {
|
||||
export interface Spu {
|
||||
id?: number
|
||||
name?: string // 商品名称
|
||||
categoryId?: number | null // 商品分类
|
||||
@ -39,9 +37,9 @@ export interface SpuType {
|
||||
brandId?: number | null // 商品品牌编号
|
||||
specType?: boolean // 商品规格
|
||||
subCommissionType?: boolean // 分销类型
|
||||
skus: SkuType[] // sku数组
|
||||
skus?: Sku[] // sku数组
|
||||
description?: string // 商品详情
|
||||
sort?: string // 商品排序
|
||||
sort?: number // 商品排序
|
||||
giveIntegral?: number // 赠送积分
|
||||
virtualSalesCount?: number // 虚拟销量
|
||||
recommendHot?: boolean // 是否热卖
|
||||
@ -49,6 +47,13 @@ export interface SpuType {
|
||||
recommendBest?: boolean // 是否精品
|
||||
recommendNew?: boolean // 是否新品
|
||||
recommendGood?: boolean // 是否优品
|
||||
price?: number // 商品价格
|
||||
salesCount?: number // 商品销量
|
||||
marketPrice?: number // 市场价
|
||||
costPrice?: number // 成本价
|
||||
stock?: number // 商品库存
|
||||
createTime?: Date // 商品创建时间
|
||||
status?: number // 商品状态
|
||||
}
|
||||
|
||||
// 获得 Spu 列表
|
||||
@ -62,12 +67,12 @@ export const getTabsCount = () => {
|
||||
}
|
||||
|
||||
// 创建商品 Spu
|
||||
export const createSpu = (data: SpuType) => {
|
||||
export const createSpu = (data: Spu) => {
|
||||
return request.post({ url: '/product/spu/create', data })
|
||||
}
|
||||
|
||||
// 更新商品 Spu
|
||||
export const updateSpu = (data: SpuType) => {
|
||||
export const updateSpu = (data: Spu) => {
|
||||
return request.put({ url: '/product/spu/update', data })
|
||||
}
|
||||
|
||||
@ -81,6 +86,11 @@ export const getSpu = (id: number) => {
|
||||
return request.get({ url: `/product/spu/get-detail?id=${id}` })
|
||||
}
|
||||
|
||||
// 获得商品 Spu 详情列表
|
||||
export const getSpuDetailList = (ids: number[]) => {
|
||||
return request.get({ url: `/product/spu/list?spuIds=${ids}` })
|
||||
}
|
||||
|
||||
// 删除商品 Spu
|
||||
export const deleteSpu = (id: number) => {
|
||||
return request.delete({ url: `/product/spu/delete?id=${id}` })
|
||||
@ -90,3 +100,8 @@ export const deleteSpu = (id: number) => {
|
||||
export const exportSpu = async (params) => {
|
||||
return await request.download({ url: '/product/spu/export', params })
|
||||
}
|
||||
|
||||
// 获得商品 SPU 精简列表
|
||||
export const getSpuSimpleList = async () => {
|
||||
return request.get({ url: '/product/spu/get-simple-list' })
|
||||
}
|
||||
|
63
src/api/mall/promotion/combination/combinationactivity.ts
Normal file
63
src/api/mall/promotion/combination/combinationactivity.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import request from '@/config/axios'
|
||||
import { Sku, Spu } from '@/api/mall/product/spu'
|
||||
|
||||
// TODO @puhui999: combinationActivity.ts
|
||||
|
||||
export interface CombinationActivityVO {
|
||||
id?: number
|
||||
name?: string
|
||||
spuId?: number
|
||||
totalLimitCount?: number
|
||||
singleLimitCount?: number
|
||||
startTime?: Date
|
||||
endTime?: Date
|
||||
userSize?: number
|
||||
totalNum?: number
|
||||
successNum?: number
|
||||
orderUserCount?: number
|
||||
virtualGroup?: number
|
||||
status?: number
|
||||
limitDuration?: number
|
||||
products: CombinationProductVO[]
|
||||
}
|
||||
|
||||
// 拼团活动所需属性
|
||||
export interface CombinationProductVO {
|
||||
spuId: number
|
||||
skuId: number
|
||||
activePrice: number // 拼团价格
|
||||
}
|
||||
|
||||
// 扩展 Sku 配置
|
||||
export type SkuExtension = Sku & {
|
||||
productConfig: CombinationProductVO
|
||||
}
|
||||
|
||||
export interface SpuExtension extends Spu {
|
||||
skus: SkuExtension[] // 重写类型
|
||||
}
|
||||
|
||||
// 查询拼团活动列表
|
||||
export const getCombinationActivityPage = async (params) => {
|
||||
return await request.get({ url: '/promotion/combination-activity/page', params })
|
||||
}
|
||||
|
||||
// 查询拼团活动详情
|
||||
export const getCombinationActivity = async (id: number) => {
|
||||
return await request.get({ url: '/promotion/combination-activity/get?id=' + id })
|
||||
}
|
||||
|
||||
// 新增拼团活动
|
||||
export const createCombinationActivity = async (data: CombinationActivityVO) => {
|
||||
return await request.post({ url: '/promotion/combination-activity/create', data })
|
||||
}
|
||||
|
||||
// 修改拼团活动
|
||||
export const updateCombinationActivity = async (data: CombinationActivityVO) => {
|
||||
return await request.put({ url: '/promotion/combination-activity/update', data })
|
||||
}
|
||||
|
||||
// 删除拼团活动
|
||||
export const deleteCombinationActivity = async (id: number) => {
|
||||
return await request.delete({ url: '/promotion/combination-activity/delete?id=' + id })
|
||||
}
|
18
src/api/mall/promotion/coupon.ts
Executable file
18
src/api/mall/promotion/coupon.ts
Executable file
@ -0,0 +1,18 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// TODO @dhb52:vo 缺少
|
||||
|
||||
// 删除优惠劵
|
||||
export const deleteCoupon = async (id: number) => {
|
||||
return request.delete({
|
||||
url: `/promotion/coupon/delete?id=${id}`
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵分页
|
||||
export const getCouponPage = async (params: PageParam) => {
|
||||
return request.get({
|
||||
url: '/promotion/coupon/page',
|
||||
params: params
|
||||
})
|
||||
}
|
83
src/api/mall/promotion/couponTemplate.ts
Executable file
83
src/api/mall/promotion/couponTemplate.ts
Executable file
@ -0,0 +1,83 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface CouponTemplateVO {
|
||||
id: number
|
||||
name: string
|
||||
status: number
|
||||
totalCount: number
|
||||
takeLimitCount: number
|
||||
takeType: number
|
||||
usePrice: number
|
||||
productScope: number
|
||||
productSpuIds: string
|
||||
validityType: number
|
||||
validStartTime: Date
|
||||
validEndTime: Date
|
||||
fixedStartTerm: number
|
||||
fixedEndTerm: number
|
||||
discountType: number
|
||||
discountPercent: number
|
||||
discountPrice: number
|
||||
discountLimitPrice: number
|
||||
takeCount: number
|
||||
useCount: number
|
||||
}
|
||||
|
||||
// 创建优惠劵模板
|
||||
export function createCouponTemplate(data: CouponTemplateVO) {
|
||||
return request.post({
|
||||
url: '/promotion/coupon-template/create',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新优惠劵模板
|
||||
export function updateCouponTemplate(data: CouponTemplateVO) {
|
||||
return request.put({
|
||||
url: '/promotion/coupon-template/update',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新优惠劵模板的状态
|
||||
export function updateCouponTemplateStatus(id: number, status: [0, 1]) {
|
||||
const data = {
|
||||
id,
|
||||
status
|
||||
}
|
||||
return request.put({
|
||||
url: '/promotion/coupon-template/update-status',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除优惠劵模板
|
||||
export function deleteCouponTemplate(id: number) {
|
||||
return request.delete({
|
||||
url: '/promotion/coupon-template/delete?id=' + id
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵模板
|
||||
export function getCouponTemplate(id: number) {
|
||||
return request.get({
|
||||
url: '/promotion/coupon-template/get?id=' + id
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵模板分页
|
||||
export function getCouponTemplatePage(params: PageParam) {
|
||||
return request.get({
|
||||
url: '/promotion/coupon-template/page',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 导出优惠劵模板 Excel
|
||||
export function exportCouponTemplateExcel(params: PageParam) {
|
||||
return request.get({
|
||||
url: '/promotion/coupon-template/export-excel',
|
||||
params: params,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
63
src/api/mall/promotion/seckill/seckillActivity.ts
Normal file
63
src/api/mall/promotion/seckill/seckillActivity.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import request from '@/config/axios'
|
||||
import { Sku, Spu } from '@/api/mall/product/spu'
|
||||
|
||||
export interface SeckillActivityVO {
|
||||
id?: number
|
||||
spuId?: number
|
||||
name?: string
|
||||
status?: number
|
||||
remark?: string
|
||||
startTime?: Date
|
||||
endTime?: Date
|
||||
sort?: number
|
||||
configIds?: string
|
||||
orderCount?: number
|
||||
userCount?: number
|
||||
totalPrice?: number
|
||||
totalLimitCount?: number
|
||||
singleLimitCount?: number
|
||||
stock?: number
|
||||
totalStock?: number
|
||||
products?: SeckillProductVO[]
|
||||
}
|
||||
|
||||
// 秒杀活动所需属性
|
||||
export interface SeckillProductVO {
|
||||
skuId: number
|
||||
seckillPrice: number
|
||||
stock: number
|
||||
}
|
||||
|
||||
// 扩展 Sku 配置
|
||||
export type SkuExtension = Sku & {
|
||||
productConfig: SeckillProductVO
|
||||
}
|
||||
|
||||
export interface SpuExtension extends Spu {
|
||||
skus: SkuExtension[] // 重写类型
|
||||
}
|
||||
|
||||
// 查询秒杀活动列表
|
||||
export const getSeckillActivityPage = async (params) => {
|
||||
return await request.get({ url: '/promotion/seckill-activity/page', params })
|
||||
}
|
||||
|
||||
// 查询秒杀活动详情
|
||||
export const getSeckillActivity = async (id: number) => {
|
||||
return await request.get({ url: '/promotion/seckill-activity/get?id=' + id })
|
||||
}
|
||||
|
||||
// 新增秒杀活动
|
||||
export const createSeckillActivity = async (data: SeckillActivityVO) => {
|
||||
return await request.post({ url: '/promotion/seckill-activity/create', data })
|
||||
}
|
||||
|
||||
// 修改秒杀活动
|
||||
export const updateSeckillActivity = async (data: SeckillActivityVO) => {
|
||||
return await request.put({ url: '/promotion/seckill-activity/update', data })
|
||||
}
|
||||
|
||||
// 删除秒杀活动
|
||||
export const deleteSeckillActivity = async (id: number) => {
|
||||
return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id })
|
||||
}
|
49
src/api/mall/promotion/seckill/seckillConfig.ts
Normal file
49
src/api/mall/promotion/seckill/seckillConfig.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface SeckillConfigVO {
|
||||
id: number
|
||||
name: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
sliderPicUrls: string[]
|
||||
status: number
|
||||
}
|
||||
|
||||
// 查询秒杀时段配置列表
|
||||
export const getSeckillConfigPage = async (params) => {
|
||||
return await request.get({ url: '/promotion/seckill-config/page', params })
|
||||
}
|
||||
|
||||
// 查询秒杀时段配置详情
|
||||
export const getSeckillConfig = async (id: number) => {
|
||||
return await request.get({ url: '/promotion/seckill-config/get?id=' + id })
|
||||
}
|
||||
|
||||
// 获得所有开启状态的秒杀时段精简列表
|
||||
export const getListAllSimple = async () => {
|
||||
return await request.get({ url: '/promotion/seckill-config/list-all-simple' })
|
||||
}
|
||||
|
||||
// 新增秒杀时段配置
|
||||
export const createSeckillConfig = async (data: SeckillConfigVO) => {
|
||||
return await request.post({ url: '/promotion/seckill-config/create', data })
|
||||
}
|
||||
|
||||
// 修改秒杀时段配置
|
||||
export const updateSeckillConfig = async (data: SeckillConfigVO) => {
|
||||
return await request.put({ url: '/promotion/seckill-config/update', data })
|
||||
}
|
||||
|
||||
// 修改时段配置状态
|
||||
export const updateSeckillConfigStatus = (id: number, status: number) => {
|
||||
const data = {
|
||||
id,
|
||||
status
|
||||
}
|
||||
return request.put({ url: '/promotion/seckill-config/update-status', data: data })
|
||||
}
|
||||
|
||||
// 删除秒杀时段配置
|
||||
export const deleteSeckillConfig = async (id: number) => {
|
||||
return await request.delete({ url: '/promotion/seckill-config/delete?id=' + id })
|
||||
}
|
@ -33,6 +33,11 @@ export const getDeliveryExpressTemplate = async (id: number) => {
|
||||
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
|
||||
}
|
||||
|
||||
// 查询快递运费模板详情
|
||||
export const getSimpleTemplateList = async () => {
|
||||
return await request.get({ url: '/trade/delivery/express-template/list-all-simple' })
|
||||
}
|
||||
|
||||
// 新增快递运费模板
|
||||
export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
|
||||
return await request.post({ url: '/trade/delivery/express-template/create', data })
|
||||
@ -47,8 +52,3 @@ export const updateDeliveryExpressTemplate = async (data: DeliveryExpressTemplat
|
||||
export const deleteDeliveryExpressTemplate = async (id: number) => {
|
||||
return await request.delete({ url: '/trade/delivery/express-template/delete?id=' + id })
|
||||
}
|
||||
|
||||
// 导出快递运费模板 Excel
|
||||
export const exportDeliveryExpressTemplateApi = async (params) => {
|
||||
return await request.download({ url: '/trade/delivery/express-template/export-excel', params })
|
||||
}
|
||||
|
46
src/api/mall/trade/delivery/pickUpStore/index.ts
Normal file
46
src/api/mall/trade/delivery/pickUpStore/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface DeliveryPickUpStoreVO {
|
||||
id: number
|
||||
name: string
|
||||
introduction: string
|
||||
phone: string
|
||||
areaId: number
|
||||
detailAddress: string
|
||||
logo: string
|
||||
openingTime: string
|
||||
closingTime: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
status: number
|
||||
}
|
||||
|
||||
// 查询自提门店列表
|
||||
export const getDeliveryPickUpStorePage = async (params: DeliveryPickUpStorePageReqVO) => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/page', params })
|
||||
}
|
||||
|
||||
// 查询自提门店详情
|
||||
export const getDeliveryPickUpStore = async (id: number) => {
|
||||
return await request.get({ url: '/trade/delivery/pick-up-store/get?id=' + id })
|
||||
}
|
||||
|
||||
// 新增自提门店
|
||||
export const createDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) => {
|
||||
return await request.post({ url: '/trade/delivery/pick-up-store/create', data })
|
||||
}
|
||||
|
||||
// 修改自提门店
|
||||
export const updateDeliveryPickUpStore = async (data: DeliveryPickUpStoreVO) => {
|
||||
return await request.put({ url: '/trade/delivery/pick-up-store/update', data })
|
||||
}
|
||||
|
||||
// 删除自提门店
|
||||
export const deleteDeliveryPickUpStore = async (id: number) => {
|
||||
return await request.delete({ url: '/trade/delivery/pick-up-store/delete?id=' + id })
|
||||
}
|
||||
|
||||
// 导出自提门店 Excel
|
||||
export const exportDeliveryPickUpStoreApi = async (params) => {
|
||||
return await request.download({ url: '/trade/delivery/pick-up-store/export-excel', params })
|
||||
}
|
12
src/api/mall/trade/order/index.ts
Normal file
12
src/api/mall/trade/order/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
// 获得交易订单分页
|
||||
// TODO @xiaobai:改成 getOrderPage
|
||||
export const getOrderList = (params: PageParam) => {
|
||||
return request.get({ url: '/trade/order/page', params })
|
||||
}
|
||||
|
||||
// 获得交易订单详情
|
||||
export const getOrderDetail = (id: number) => {
|
||||
return request.get({ url: '/trade/order/get-detail?id=' + id })
|
||||
}
|
228
src/api/mall/trade/order/type/orderType.ts
Normal file
228
src/api/mall/trade/order/type/orderType.ts
Normal file
@ -0,0 +1,228 @@
|
||||
// TODO @xiaobai:这个放到 order/index.ts 里哈
|
||||
// TODO @xiaobai:注释放到变量后面,这样简洁一点
|
||||
// TODO @xiaobai:这个改成 TradeOrderRespVO
|
||||
export interface TradeOrderPageItemRespVO {
|
||||
// 订单编号
|
||||
id?: number
|
||||
// 订单流水号
|
||||
no?: string
|
||||
// 下单时间
|
||||
createTime?: Date
|
||||
// 订单类型
|
||||
type?: number
|
||||
// 订单来源
|
||||
terminal?: number
|
||||
// 用户编号
|
||||
userId?: number
|
||||
// 用户 IP
|
||||
userIp?: string
|
||||
// 用户备注
|
||||
userRemark?: string
|
||||
// 订单状态
|
||||
status?: number
|
||||
// 购买的商品数量
|
||||
productCount?: number
|
||||
// 订单完成时间
|
||||
finishTime?: Date
|
||||
// 订单取消时间
|
||||
cancelTime?: Date
|
||||
// 取消类型
|
||||
cancelType?: number
|
||||
// 商家备注
|
||||
remark?: string
|
||||
// 支付订单编号
|
||||
payOrderId: number
|
||||
// 是否已支付
|
||||
payed?: boolean
|
||||
// 付款时间
|
||||
payTime?: Date
|
||||
// 支付渠道
|
||||
payChannelCode?: string
|
||||
// 商品原价(总)
|
||||
originalPrice?: number
|
||||
// 订单原价(总)
|
||||
orderPrice?: number
|
||||
// 订单优惠(总)
|
||||
discountPrice?: number
|
||||
// 运费金额
|
||||
deliveryPrice?: number
|
||||
// 订单调价(总)
|
||||
adjustPrice?: number
|
||||
// 应付金额(总)
|
||||
payPrice?: number
|
||||
// 配送模板编号
|
||||
deliveryTemplateId?: number
|
||||
// 发货物流公司编号
|
||||
logisticsId?: number
|
||||
// 发货物流单号
|
||||
logisticsNo?: string
|
||||
// 发货状态
|
||||
deliveryStatus?: number
|
||||
// 发货时间
|
||||
deliveryTime?: Date
|
||||
// 收货时间
|
||||
receiveTime?: Date
|
||||
// 收件人名称
|
||||
receiverName?: string
|
||||
// 收件人手机
|
||||
receiverMobile?: string
|
||||
// 收件人地区编号
|
||||
receiverAreaId?: number
|
||||
// 收件人邮编
|
||||
receiverPostCode?: number
|
||||
// 收件人详细地址
|
||||
receiverDetailAddress?: string
|
||||
// 售后状态
|
||||
afterSaleStatus?: number
|
||||
// 退款金额
|
||||
refundPrice?: number
|
||||
// 优惠劵编号
|
||||
couponId?: number
|
||||
// 优惠劵减免金额
|
||||
couponPrice?: number
|
||||
// 积分抵扣的金额
|
||||
pointPrice?: number
|
||||
//收件人地区名字
|
||||
receiverAreaName?: string
|
||||
// 订单项列表
|
||||
items?: TradeOrderItemBaseVO[]
|
||||
//用户信息
|
||||
user?: MemberUserRespDTO
|
||||
}
|
||||
|
||||
// TODO @xiaobai:这个改成 TradeOrderItemRespVO
|
||||
/**
|
||||
* 交易订单项 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
export interface TradeOrderItemBaseVO {
|
||||
// ========== 订单项基本信息 ==========
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
id?: number
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
userId?: number
|
||||
/**
|
||||
* 订单编号
|
||||
*/
|
||||
orderId?: number
|
||||
// ========== 商品基本信息 ==========
|
||||
/**
|
||||
* 商品 SPU 编号
|
||||
*/
|
||||
spuId?: number
|
||||
/**
|
||||
* 商品 SPU 名称
|
||||
*/
|
||||
spuName?: string
|
||||
/**
|
||||
* 商品 SKU 编号
|
||||
*/
|
||||
skuId?: number
|
||||
/**
|
||||
* 商品图片
|
||||
*/
|
||||
picUrl?: string
|
||||
/**
|
||||
* 购买数量
|
||||
*/
|
||||
count?: number
|
||||
// ========== 价格 + 支付基本信息 ==========
|
||||
/**
|
||||
* 商品原价(总)
|
||||
*/
|
||||
originalPrice?: number
|
||||
/**
|
||||
* 商品原价(单)
|
||||
*/
|
||||
originalUnitPrice?: number
|
||||
/**
|
||||
* 商品优惠(总)
|
||||
*/
|
||||
discountPrice?: number
|
||||
/**
|
||||
* 商品实付金额(总)
|
||||
*/
|
||||
payPrice?: number
|
||||
/**
|
||||
* 子订单分摊金额(总)
|
||||
*/
|
||||
orderPartPrice?: number
|
||||
/**
|
||||
* 分摊后子订单实付金额(总)
|
||||
*/
|
||||
orderDividePrice?: number
|
||||
// ========== 营销基本信息 ==========
|
||||
// TODO 芋艿:在捉摸一下
|
||||
// ========== 售后基本信息 ==========
|
||||
/**
|
||||
* 售后状态
|
||||
*/
|
||||
afterSaleStatus?: number
|
||||
//属性数组
|
||||
properties?: ProductPropertyValueDetailRespVO[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理后台 - 商品属性值的明细 Response VO
|
||||
*/
|
||||
export interface ProductPropertyValueDetailRespVO {
|
||||
/**
|
||||
* 属性的编号
|
||||
*/
|
||||
propertyId?: number
|
||||
/**
|
||||
* 属性的名称
|
||||
*/
|
||||
propertyName?: string
|
||||
/**
|
||||
* 属性值的编号
|
||||
*/
|
||||
valueId?: number
|
||||
/**
|
||||
* 属性值的名称
|
||||
*/
|
||||
valueName?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单详情查询 请求
|
||||
*/
|
||||
export interface TradeOrderPageReqVO {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
no?: string
|
||||
userId?: string
|
||||
userNickname?: string
|
||||
userMobile?: string
|
||||
receiverName?: string
|
||||
receiverMobile?: string
|
||||
terminal?: string
|
||||
type?: number
|
||||
status?: number
|
||||
payChannelCode?: string
|
||||
createTime?: [Date, Date]
|
||||
spuName?: string
|
||||
itemCount?: string
|
||||
all?: string
|
||||
}
|
||||
|
||||
//用户信息
|
||||
export interface MemberUserRespDTO {
|
||||
id?: number
|
||||
nickname?: string
|
||||
status?: number
|
||||
avatar?: string
|
||||
mobile?: string
|
||||
}
|
||||
//订单详情选中type
|
||||
export interface SelectType {
|
||||
queryParams: TradeOrderPageReqVO
|
||||
selectTotal: number //选中的数量
|
||||
selectAllFlag: boolean //全选标识
|
||||
selectData: Map<number, Set<string>> //存放涉及选中得页面以及每页选中得数据订单号 全选时根据条件查询 排除取消的list订单
|
||||
unSelectList: Set<string> //登记取消的list 全选标识为true 时登记单独取消的list,再次选中时排除, 全选标识为false 时清空list
|
||||
}
|
19
src/api/point/config/index.ts
Normal file
19
src/api/point/config/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
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: `/point/config/get` })
|
||||
}
|
||||
|
||||
// 新增修改积分设置
|
||||
export const saveConfig = async (data: ConfigVO) => {
|
||||
return await request.put({ url: `/point/config/save`, data })
|
||||
}
|
47
src/api/point/record/index.ts
Normal file
47
src/api/point/record/index.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface RecordVO {
|
||||
id: number
|
||||
bizId: string
|
||||
bizType: string
|
||||
type: string
|
||||
title: string
|
||||
description: string
|
||||
point: number
|
||||
totalPoint: number
|
||||
status: number
|
||||
userId: number
|
||||
freezingTime: Date
|
||||
thawingTime: Date
|
||||
createDate: Date
|
||||
}
|
||||
|
||||
// 查询用户积分记录列表
|
||||
export const getRecordPage = async (params) => {
|
||||
return await request.get({ url: `/point/record/page`, params })
|
||||
}
|
||||
|
||||
// 查询用户积分记录详情
|
||||
export const getRecord = async (id: number) => {
|
||||
return await request.get({ url: `/point/record/get?id=` + id })
|
||||
}
|
||||
|
||||
// 新增用户积分记录
|
||||
export const createRecord = async (data: RecordVO) => {
|
||||
return await request.post({ url: `/point/record/create`, data })
|
||||
}
|
||||
|
||||
// 修改用户积分记录
|
||||
export const updateRecord = async (data: RecordVO) => {
|
||||
return await request.put({ url: `/point/record/update`, data })
|
||||
}
|
||||
|
||||
// 删除用户积分记录
|
||||
export const deleteRecord = async (id: number) => {
|
||||
return await request.delete({ url: `/point/record/delete?id=` + id })
|
||||
}
|
||||
|
||||
// 导出用户积分记录 Excel
|
||||
export const exportRecord = async (params) => {
|
||||
return await request.download({ url: `/point/record/export-excel`, params })
|
||||
}
|
37
src/api/point/signInConfig/index.ts
Normal file
37
src/api/point/signInConfig/index.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface SignInConfigVO {
|
||||
id: number
|
||||
day: number
|
||||
point: number
|
||||
}
|
||||
|
||||
// 查询积分签到规则列表
|
||||
export const getSignInConfigPage = async (params) => {
|
||||
return await request.get({ url: `/point/sign-in-config/page`, params })
|
||||
}
|
||||
|
||||
// 查询积分签到规则详情
|
||||
export const getSignInConfig = async (id: number) => {
|
||||
return await request.get({ url: `/point/sign-in-config/get?id=` + id })
|
||||
}
|
||||
|
||||
// 新增积分签到规则
|
||||
export const createSignInConfig = async (data: SignInConfigVO) => {
|
||||
return await request.post({ url: `/point/sign-in-config/create`, data })
|
||||
}
|
||||
|
||||
// 修改积分签到规则
|
||||
export const updateSignInConfig = async (data: SignInConfigVO) => {
|
||||
return await request.put({ url: `/point/sign-in-config/update`, data })
|
||||
}
|
||||
|
||||
// 删除积分签到规则
|
||||
export const deleteSignInConfig = async (id: number) => {
|
||||
return await request.delete({ url: `/point/sign-in-config/delete?id=` + id })
|
||||
}
|
||||
|
||||
// 导出积分签到规则 Excel
|
||||
export const exportSignInConfig = async (params) => {
|
||||
return await request.download({ url: `/point/sign-in-config/export-excel`, params })
|
||||
}
|
38
src/api/point/signInRecord/index.ts
Normal file
38
src/api/point/signInRecord/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export interface SignInRecordVO {
|
||||
id: number
|
||||
userId: number
|
||||
day: number
|
||||
point: number
|
||||
}
|
||||
|
||||
// 查询用户签到积分列表
|
||||
export const getSignInRecordPage = async (params) => {
|
||||
return await request.get({ url: `/point/sign-in-record/page`, params })
|
||||
}
|
||||
|
||||
// 查询用户签到积分详情
|
||||
export const getSignInRecord = async (id: number) => {
|
||||
return await request.get({ url: `/point/sign-in-record/get?id=` + id })
|
||||
}
|
||||
|
||||
// 新增用户签到积分
|
||||
export const createSignInRecord = async (data: SignInRecordVO) => {
|
||||
return await request.post({ url: `/point/sign-in-record/create`, data })
|
||||
}
|
||||
|
||||
// 修改用户签到积分
|
||||
export const updateSignInRecord = async (data: SignInRecordVO) => {
|
||||
return await request.put({ url: `/point/sign-in-record/update`, data })
|
||||
}
|
||||
|
||||
// 删除用户签到积分
|
||||
export const deleteSignInRecord = async (id: number) => {
|
||||
return await request.delete({ url: `/point/sign-in-record/delete?id=` + id })
|
||||
}
|
||||
|
||||
// 导出用户签到积分 Excel
|
||||
export const exportSignInRecord = async (params) => {
|
||||
return await request.download({ url: `/point/sign-in-record/export-excel`, params })
|
||||
}
|
@ -14,7 +14,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
const delArr: string[] = ['fullscreen', 'title', 'maxHeight']
|
||||
const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']
|
||||
const attrs = useAttrs()
|
||||
const obj = { ...attrs, ...props }
|
||||
for (const key in obj) {
|
||||
|
@ -1,16 +1,16 @@
|
||||
<script lang="tsx">
|
||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
||||
import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
|
||||
import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
|
||||
import { componentMap } from './componentMap'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import {
|
||||
setTextPlaceholder,
|
||||
setGridProp,
|
||||
setComponentProps,
|
||||
setItemComponentSlots,
|
||||
initModel,
|
||||
setFormItemSlots
|
||||
setComponentProps,
|
||||
setFormItemSlots,
|
||||
setGridProp,
|
||||
setItemComponentSlots,
|
||||
setTextPlaceholder
|
||||
} from './helper'
|
||||
import { useRenderSelect } from './components/useRenderSelect'
|
||||
import { useRenderRadio } from './components/useRenderRadio'
|
||||
@ -27,6 +27,7 @@ const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('form')
|
||||
|
||||
export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
name: 'Form',
|
||||
props: {
|
||||
// 生成Form的布局结构数组
|
||||
@ -196,7 +197,7 @@ export default defineComponent({
|
||||
<span>{item.label}</span>
|
||||
<ElTooltip placement="right" raw-content>
|
||||
{{
|
||||
content: () => <span v-html={item.labelMessage}></span>,
|
||||
content: () => <span v-dompurify-html={item.labelMessage}></span>,
|
||||
default: () => (
|
||||
<Icon
|
||||
icon="ep:warning"
|
||||
|
@ -9,6 +9,7 @@ import { set } from 'lodash-es'
|
||||
import { Pagination, TableColumn, TableSetPropsType, TableSlotDefault } from '@/types/table'
|
||||
|
||||
export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
name: 'Table',
|
||||
props: {
|
||||
pageSize: propTypes.number.def(10),
|
||||
@ -302,6 +303,7 @@ export default defineComponent({
|
||||
margin-left: 0;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
:deep(.el-button.is-link) {
|
||||
margin-left: 0;
|
||||
padding: 8px 4px;
|
||||
|
@ -10,6 +10,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'
|
||||
const props = defineProps({
|
||||
value: {
|
||||
// BPMN XML 字符串
|
||||
@ -403,6 +404,7 @@ watch(
|
||||
stroke-dasharray: 4px !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
.highlight-todo.djs-shape .djs-visual > :nth-child(1) {
|
||||
fill: #1890ff !important;
|
||||
stroke: #1890ff !important;
|
||||
@ -414,8 +416,9 @@ watch(
|
||||
stroke: #1890ff !important;
|
||||
stroke-dasharray: 4px !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
marker-end: url(#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr);
|
||||
marker-end: url('#sequenceflow-end-_E7DFDF-_E7DFDF-803g1kf6zwzmcig1y2ulm5egr');
|
||||
}
|
||||
|
||||
:deep(.highlight-todo.djs-shape .djs-visual > :nth-child(1)) {
|
||||
fill: #1890ff !important;
|
||||
stroke: #1890ff !important;
|
||||
@ -429,14 +432,17 @@ watch(
|
||||
stroke: green !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
.highlight.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: green !important;
|
||||
}
|
||||
|
||||
.highlight.djs-shape .djs-visual > path {
|
||||
fill: green !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: green !important;
|
||||
}
|
||||
|
||||
.highlight.djs-connection > .djs-visual > path {
|
||||
stroke: green !important;
|
||||
}
|
||||
@ -450,14 +456,17 @@ watch(
|
||||
stroke: green !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
:deep(.highlight.djs-shape .djs-visual > :nth-child(2)) {
|
||||
fill: green !important;
|
||||
}
|
||||
|
||||
:deep(.highlight.djs-shape .djs-visual > path) {
|
||||
fill: green !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: green !important;
|
||||
}
|
||||
|
||||
:deep(.highlight.djs-connection > .djs-visual > path) {
|
||||
stroke: green !important;
|
||||
}
|
||||
@ -468,14 +477,17 @@ watch(
|
||||
stroke: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
.highlight-reject.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: red !important;
|
||||
}
|
||||
|
||||
.highlight-reject.djs-shape .djs-visual > path {
|
||||
fill: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: red !important;
|
||||
}
|
||||
|
||||
.highlight-reject.djs-connection > .djs-visual > path {
|
||||
stroke: red !important;
|
||||
}
|
||||
@ -489,14 +501,17 @@ watch(
|
||||
stroke: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
:deep(.highlight-reject.djs-shape .djs-visual > :nth-child(2)) {
|
||||
fill: red !important;
|
||||
}
|
||||
|
||||
:deep(.highlight-reject.djs-shape .djs-visual > path) {
|
||||
fill: red !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: red !important;
|
||||
}
|
||||
|
||||
:deep(.highlight-reject.djs-connection > .djs-visual > path) {
|
||||
stroke: red !important;
|
||||
}
|
||||
@ -507,14 +522,17 @@ watch(
|
||||
stroke: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
.highlight-cancel.djs-shape .djs-visual > :nth-child(2) {
|
||||
fill: grey !important;
|
||||
}
|
||||
|
||||
.highlight-cancel.djs-shape .djs-visual > path {
|
||||
fill: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: grey !important;
|
||||
}
|
||||
|
||||
.highlight-cancel.djs-connection > .djs-visual > path {
|
||||
stroke: grey !important;
|
||||
}
|
||||
@ -528,14 +546,17 @@ watch(
|
||||
stroke: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
}
|
||||
|
||||
:deep(.highlight-cancel.djs-shape .djs-visual > :nth-child(2)) {
|
||||
fill: grey !important;
|
||||
}
|
||||
|
||||
:deep(.highlight-cancel.djs-shape .djs-visual > path) {
|
||||
fill: grey !important;
|
||||
fill-opacity: 0.2 !important;
|
||||
stroke: grey !important;
|
||||
}
|
||||
|
||||
:deep(.highlight-cancel.djs-connection > .djs-visual > path) {
|
||||
stroke: grey !important;
|
||||
}
|
||||
@ -543,7 +564,7 @@ watch(
|
||||
.element-overlays {
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
background: rgb(0 0 0 / 60%);
|
||||
border-radius: 4px;
|
||||
color: #fafafa;
|
||||
width: 200px;
|
||||
|
@ -30,12 +30,13 @@ const addTask = (event, options: any = {}) => {
|
||||
<style scoped lang="scss">
|
||||
.my-process-palette {
|
||||
box-sizing: border-box;
|
||||
padding: 80px 20px 20px 20px;
|
||||
padding: 80px 20px 20px;
|
||||
|
||||
.test-button {
|
||||
box-sizing: border-box;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(24, 144, 255, 0.8);
|
||||
border: 1px solid rgb(24 144 255 / 80%);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -106,22 +106,22 @@ const elementBusinessObject = ref<any>({}) // 元素 businessObject 镜像,提
|
||||
const conditionFormVisible = ref(false) // 流转条件设置
|
||||
const formVisible = ref(false) // 表单配置
|
||||
const bpmnElement = ref()
|
||||
const timer = ref()
|
||||
|
||||
provide('prefix', props.prefix)
|
||||
provide('width', props.width)
|
||||
const bpmnInstances = () => (window as any)?.bpmnInstances
|
||||
const initModels = () => {
|
||||
// console.log(props, 'props')
|
||||
// console.log(props.bpmnModeler, 'sakdjjaskdsajdkasdjkadsjk')
|
||||
// 初始化 modeler 以及其他 moddle
|
||||
// nextTick(() => {
|
||||
if (!props.bpmnModeler) {
|
||||
|
||||
// 监听 props.bpmnModeler 然后 initModels
|
||||
const unwatchBpmn = watch(
|
||||
() => props.bpmnModeler,
|
||||
() => {
|
||||
// 避免加载时 流程图 并未加载完成
|
||||
timer.value = setTimeout(() => initModels(), 10)
|
||||
return
|
||||
}
|
||||
if (timer.value) {
|
||||
clearTimeout(timer.value)
|
||||
if (!props.bpmnModeler) {
|
||||
console.log('缺少props.bpmnModeler')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('props.bpmnModeler 有值了!!!')
|
||||
const w = window as any
|
||||
w.bpmnInstances = {
|
||||
modeler: props.bpmnModeler,
|
||||
@ -134,12 +134,16 @@ const initModels = () => {
|
||||
replace: props.bpmnModeler.get('replace'),
|
||||
selection: props.bpmnModeler.get('selection')
|
||||
}
|
||||
}
|
||||
|
||||
console.log(bpmnInstances(), 'window.bpmnInstances')
|
||||
getActiveElement()
|
||||
// })
|
||||
}
|
||||
console.log(bpmnInstances(), 'window.bpmnInstances')
|
||||
getActiveElement()
|
||||
unwatchBpmn()
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
const getActiveElement = () => {
|
||||
// 初始第一个选中元素 bpmn:Process
|
||||
initFormOnChanged(null)
|
||||
@ -187,11 +191,7 @@ const initFormOnChanged = (element) => {
|
||||
)
|
||||
formVisible.value = elementType.value === 'UserTask' || elementType.value === 'StartEvent'
|
||||
}
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
initModels()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const w = window as any
|
||||
w.bpmnInstances = null
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { reactive } from 'vue'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { findIndex } from '@/utils'
|
||||
import { eachTree, treeMap, filter } from '@/utils/tree'
|
||||
import { eachTree, filter, treeMap } from '@/utils/tree'
|
||||
import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
|
||||
import { FormSchema } from '@/types/form'
|
||||
@ -36,8 +36,11 @@ type CrudSearchParams = {
|
||||
type CrudTableParams = {
|
||||
// 是否显示表头
|
||||
show?: boolean
|
||||
// 列宽配置
|
||||
width?: number | string
|
||||
// 列是否固定在左侧或者右侧
|
||||
fixed?: 'left' | 'right'
|
||||
} & Omit<FormSchema, 'field'>
|
||||
|
||||
type CrudFormParams = {
|
||||
// 是否显示表单项
|
||||
show?: boolean
|
||||
|
@ -136,9 +136,7 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
|
||||
})
|
||||
if (res) {
|
||||
tableObject.tableList = (res as unknown as ResponseType).list
|
||||
if ((res as unknown as ResponseType).total) {
|
||||
tableObject.total = (res as unknown as ResponseType).total as unknown as number
|
||||
}
|
||||
tableObject.total = (res as unknown as ResponseType).total ?? 0
|
||||
}
|
||||
},
|
||||
setProps: async (props: TableProps = {}) => {
|
||||
|
@ -2,6 +2,9 @@
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
defineOptions({ name: 'Footer' })
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('footer')
|
||||
|
@ -13,6 +13,7 @@ const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('menu')
|
||||
|
||||
export default defineComponent({
|
||||
// eslint-disable-next-line vue/no-reserved-component-names
|
||||
name: 'Menu',
|
||||
props: {
|
||||
menuSelect: {
|
||||
|
@ -217,7 +217,7 @@ $prefix-cls: #{$namespace}-tab-menu;
|
||||
.#{$prefix-cls} {
|
||||
transition: all var(--transition-time-02);
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
@ -41,9 +41,10 @@ import App from './App.vue'
|
||||
import './permission'
|
||||
|
||||
import '@/plugins/tongji' // 百度统计
|
||||
|
||||
import Logger from '@/utils/Logger'
|
||||
|
||||
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
|
||||
|
||||
// 创建实例
|
||||
const setupAll = async () => {
|
||||
const app = createApp(App)
|
||||
@ -66,6 +67,8 @@ const setupAll = async () => {
|
||||
|
||||
await router.isReady()
|
||||
|
||||
app.use(VueDOMPurifyHTML)
|
||||
|
||||
app.mount('#app')
|
||||
}
|
||||
|
||||
|
@ -195,6 +195,22 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
noTagsView: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/trade/order',
|
||||
component: Layout,
|
||||
name: 'order',
|
||||
meta: {
|
||||
hidden: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'detail',
|
||||
name: 'TradeOrderDetail',
|
||||
component: () => import('@/views/mall/trade/order/tradeOrderDetail.vue'),
|
||||
meta: { title: '订单详情', hidden: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
component: () => import('@/views/Error/403.vue'),
|
||||
@ -355,7 +371,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'productSpuAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 fix
|
||||
path: 'spu/add',
|
||||
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||
name: 'ProductSpuAdd',
|
||||
meta: {
|
||||
@ -368,7 +384,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'productSpuEdit/:spuId(\\d+)',
|
||||
path: 'spu/edit/:spuId(\\d+)',
|
||||
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||
name: 'ProductSpuEdit',
|
||||
meta: {
|
||||
@ -379,6 +395,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
||||
title: '编辑商品',
|
||||
activeMenu: '/product/product-spu'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'spu/detail/:spuId(\\d+)',
|
||||
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||
name: 'ProductSpuDetail',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
icon: 'ep:view',
|
||||
title: '商品详情',
|
||||
activeMenu: '/product/product-spu'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
2
src/types/table.d.ts
vendored
2
src/types/table.d.ts
vendored
@ -1,6 +1,8 @@
|
||||
export type TableColumn = {
|
||||
field: string
|
||||
label?: string
|
||||
width?: number | string
|
||||
fixed?: 'left' | 'right'
|
||||
children?: TableColumn[]
|
||||
} & Recordable
|
||||
|
||||
|
@ -114,6 +114,10 @@ export const PayChannelEnum = {
|
||||
ALIPAY_QR: {
|
||||
code: 'alipay_qr',
|
||||
name: '支付宝扫码支付'
|
||||
},
|
||||
ALIPAY_BAR: {
|
||||
code: 'alipay_bar',
|
||||
name: '支付宝条码支付'
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +222,7 @@ export const PayRefundStatusEnum = {
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品SPU枚举类
|
||||
* 商品 SPU 状态
|
||||
*/
|
||||
export const ProductSpuStatusEnum = {
|
||||
RECYCLE: {
|
||||
@ -234,3 +238,59 @@ export const ProductSpuStatusEnum = {
|
||||
name: '上架'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠劵模板的有限期类型的枚举
|
||||
*/
|
||||
export const CouponTemplateValidityTypeEnum = {
|
||||
DATE: {
|
||||
type: 1,
|
||||
name: '固定日期可用'
|
||||
},
|
||||
TERM: {
|
||||
type: 2,
|
||||
name: '领取之后可用'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 营销的商品范围枚举
|
||||
*/
|
||||
export const PromotionProductScopeEnum = {
|
||||
ALL: {
|
||||
scope: 1,
|
||||
name: '全部商品参与'
|
||||
},
|
||||
SPU: {
|
||||
scope: 2,
|
||||
name: '指定商品参与'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 营销的条件类型枚举
|
||||
*/
|
||||
export const PromotionConditionTypeEnum = {
|
||||
PRICE: {
|
||||
type: 10,
|
||||
name: '满 N 元'
|
||||
},
|
||||
COUNT: {
|
||||
type: 20,
|
||||
name: '满 N 件'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优惠类型枚举
|
||||
*/
|
||||
export const PromotionDiscountTypeEnum = {
|
||||
PRICE: {
|
||||
type: 1,
|
||||
name: '满减'
|
||||
},
|
||||
PERCENT: {
|
||||
type: 2,
|
||||
name: '折扣'
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ export const getIntDictOptions = (dictType: string) => {
|
||||
value: parseInt(dict.value + '')
|
||||
})
|
||||
})
|
||||
|
||||
return dictOption
|
||||
}
|
||||
|
||||
@ -147,9 +146,30 @@ export enum DICT_TYPE {
|
||||
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
||||
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
|
||||
|
||||
// ========== MALL 模块 ==========
|
||||
// ========== MALL - 会员模块 ==========
|
||||
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
|
||||
MEMBER_POINT_STATUS = 'member_point_status', // 积分的状态
|
||||
|
||||
// ========== MALL - 商品模块 ==========
|
||||
PRODUCT_UNIT = 'product_unit', // 商品单位
|
||||
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
|
||||
// ========== MALL 交易模块 ==========
|
||||
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode' //快递的计费方式
|
||||
|
||||
// ========== MALL - 交易模块 ==========
|
||||
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
|
||||
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
|
||||
TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式
|
||||
TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型
|
||||
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
|
||||
TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态
|
||||
TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态
|
||||
TERMINAL = 'terminal', // 终端
|
||||
|
||||
// ========== MALL - 营销模块 ==========
|
||||
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
|
||||
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
|
||||
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_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' // 营销的条件类型枚举
|
||||
}
|
||||
|
@ -70,6 +70,13 @@ export function parseTime(time: any, pattern?: string) {
|
||||
return time_str
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的日期+时间
|
||||
*/
|
||||
export function getNowDateTime() {
|
||||
return dayjs()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期是第几周
|
||||
* @param dateTime 当前传入的日期值
|
||||
@ -196,13 +203,28 @@ export const dateFormatter = (row, column, cellValue) => {
|
||||
return formatDate(cellValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* element plus 的时间 Formatter 实现,使用 YYYY-MM-DD 格式
|
||||
*
|
||||
* @param row 行数据
|
||||
* @param column 字段
|
||||
* @param cellValue 字段值
|
||||
*/
|
||||
// @ts-ignore
|
||||
export const dateFormatter2 = (row, column, cellValue) => {
|
||||
if (!cellValue) {
|
||||
return
|
||||
}
|
||||
return formatDate(cellValue, 'YYYY-MM-DD')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置起始日期,时间为00:00:00
|
||||
* @param param 传入日期
|
||||
* @returns 带时间00:00:00的日期
|
||||
*/
|
||||
export function beginOfDay(param: Date) {
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0, 0)
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -211,7 +233,7 @@ export function beginOfDay(param: Date) {
|
||||
* @returns 带时间23:59:59的日期
|
||||
*/
|
||||
export function endOfDay(param: Date) {
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59, 999)
|
||||
return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@ interface TreeHelperConfig {
|
||||
children: string
|
||||
pid: string
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: TreeHelperConfig = {
|
||||
id: 'id',
|
||||
children: 'children',
|
||||
@ -133,6 +134,7 @@ export const filter = <T = any>(
|
||||
): T[] => {
|
||||
config = getConfig(config)
|
||||
const children = config.children as string
|
||||
|
||||
function listFilter(list: T[]) {
|
||||
return list
|
||||
.map((node: any) => ({ ...node }))
|
||||
@ -141,6 +143,7 @@ export const filter = <T = any>(
|
||||
return func(node) || (node[children] && node[children].length)
|
||||
})
|
||||
}
|
||||
|
||||
return listFilter(tree)
|
||||
}
|
||||
|
||||
@ -264,6 +267,7 @@ export const handleTree = (data: any[], id?: string, parentId?: string, children
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
||||
/**
|
||||
@ -300,3 +304,94 @@ export const handleTree2 = (data, id, parentId, children, rootId) => {
|
||||
})
|
||||
return treeData !== '' ? treeData : data
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验选中的节点,是否为指定 level
|
||||
*
|
||||
* @param tree 要操作的树结构数据
|
||||
* @param nodeId 需要判断在什么层级的数据
|
||||
* @param level 检查的级别, 默认检查到二级
|
||||
* @return true 是;false 否
|
||||
*/
|
||||
export const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {
|
||||
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
|
||||
console.warn('tree must be an array')
|
||||
return false
|
||||
}
|
||||
|
||||
// 校验是否是一级节点
|
||||
if (tree.some((item) => item.id === nodeId)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 递归计数
|
||||
let count = 1
|
||||
|
||||
// 深层次校验
|
||||
function performAThoroughValidation(arr: any[]): boolean {
|
||||
count += 1
|
||||
for (const item of arr) {
|
||||
if (item.id === nodeId) {
|
||||
return true
|
||||
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (const item of tree) {
|
||||
count = 1
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
// 找到后对比是否是期望的层级
|
||||
if (count >= level) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点的完整结构
|
||||
* @param tree 树数据
|
||||
* @param nodeId 节点 id
|
||||
*/
|
||||
export const treeToString = (tree: any[], nodeId) => {
|
||||
if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {
|
||||
console.warn('tree must be an array')
|
||||
return ''
|
||||
}
|
||||
// 校验是否是一级节点
|
||||
const node = tree.find((item) => item.id === nodeId)
|
||||
if (typeof node !== 'undefined') {
|
||||
return node.name
|
||||
}
|
||||
let str = ''
|
||||
|
||||
function performAThoroughValidation(arr) {
|
||||
for (const item of arr) {
|
||||
if (item.id === nodeId) {
|
||||
str += `/${item.name}`
|
||||
return true
|
||||
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
|
||||
str += `/${item.name}`
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (const item of tree) {
|
||||
str = `${item.name}`
|
||||
if (performAThoroughValidation(item.children)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
label-width="120px"
|
||||
size="large"
|
||||
>
|
||||
<el-row style="maring-left: -10px; maring-right: -10px">
|
||||
<el-row style="margin-left: -10px; margin-right: -10px">
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<el-form-item>
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
|
@ -183,12 +183,17 @@ const signIn = async () => {
|
||||
await getTenantId()
|
||||
const data = await validForm()
|
||||
if (!data) return
|
||||
ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在加载系统中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
loginLoading.value = true
|
||||
smsVO.loginSms.mobile = loginData.loginForm.mobileNumber
|
||||
smsVO.loginSms.code = loginData.loginForm.code
|
||||
await smsLoginApi(smsVO.loginSms)
|
||||
.then(async (res) => {
|
||||
setToken(res?.token)
|
||||
setToken(res)
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
@ -197,6 +202,10 @@ const signIn = async () => {
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loginLoading.value = false
|
||||
setTimeout(() => {
|
||||
const loadingInstance = ElLoading.service()
|
||||
loadingInstance.close()
|
||||
}, 400)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-row v-show="getShow" style="maring-left: -10px; maring-right: -10px">
|
||||
<el-row v-show="getShow" style="margin-left: -10px; margin-right: -10px">
|
||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
</el-col>
|
||||
|
@ -52,7 +52,14 @@ const client = ref({
|
||||
name: '',
|
||||
logo: ''
|
||||
})
|
||||
const queryParams = reactive({
|
||||
interface queryType {
|
||||
responseType: string
|
||||
clientId: string
|
||||
redirectUri: string
|
||||
state: string
|
||||
scopes: string[]
|
||||
}
|
||||
const queryParams = reactive<queryType>({
|
||||
// URL 上的 client_id、scope 等参数
|
||||
responseType: '',
|
||||
clientId: '',
|
||||
@ -61,7 +68,10 @@ const queryParams = reactive({
|
||||
scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取
|
||||
})
|
||||
const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单
|
||||
const formData = reactive({
|
||||
interface formType {
|
||||
scopes: string[]
|
||||
}
|
||||
const formData = reactive<formType>({
|
||||
scopes: [] // 已选中的 scope 数组
|
||||
})
|
||||
const formLoading = ref(false) // 表单是否提交中
|
||||
|
@ -39,21 +39,25 @@ const activeName = ref('basicInfo')
|
||||
<style scoped>
|
||||
.user {
|
||||
max-height: 960px;
|
||||
padding: 15px 20px 20px 20px;
|
||||
padding: 15px 20px 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-card .el-card__header, .el-card .el-card__body) {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
.profile-tabs > .el-tabs__content {
|
||||
padding: 32px;
|
||||
color: #6b778c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.el-tabs--left .el-tabs__content {
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -3,8 +3,6 @@
|
||||
<!-- 表单设计器 -->
|
||||
<FcDesigner ref="designer" height="780px">
|
||||
<template #handle>
|
||||
<XButton type="primary" title="生成JSON" @click="showJson" />
|
||||
<XButton type="primary" title="生成Options" @click="showOption" />
|
||||
<XButton type="primary" :title="t('action.save')" @click="handleSave" />
|
||||
</template>
|
||||
</FcDesigner>
|
||||
@ -13,7 +11,7 @@
|
||||
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" />
|
||||
<el-scrollbar height="580">
|
||||
<div>
|
||||
<pre><code class="hljs" v-html="highlightedCode(formValue)"></code></pre>
|
||||
<pre><code v-dompurify-html="highlightedCode(formValue)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
@ -68,7 +66,6 @@ const message = useMessage() // 消息
|
||||
const { query } = useRoute() // 路由
|
||||
|
||||
const designer = ref() // 表单设计器
|
||||
const type = ref(-1)
|
||||
const formValue = ref('')
|
||||
const dialogTitle = ref('')
|
||||
const dialogVisible = ref(false) // 弹窗是否展示
|
||||
@ -116,20 +113,7 @@ const submitForm = async () => {
|
||||
dialogLoading.value = false
|
||||
}
|
||||
}
|
||||
const showJson = () => {
|
||||
openModel('生成JSON')
|
||||
type.value = 0
|
||||
formValue.value = designer.value.getRule()
|
||||
}
|
||||
const showOption = () => {
|
||||
openModel('生成Options')
|
||||
type.value = 1
|
||||
formValue.value = designer.value.getOption()
|
||||
}
|
||||
const openModel = (title: string) => {
|
||||
dialogVisible1.value = true
|
||||
dialogTitle.value = title
|
||||
}
|
||||
|
||||
/** 复制 **/
|
||||
const copy = async (text: string) => {
|
||||
// const { copy, copied, isSupported } = useClipboard({ source: JSON.stringify(text) })
|
||||
@ -152,22 +136,6 @@ const copy = async (text: string) => {
|
||||
oInput.remove()
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
import hljs from 'highlight.js' // 导入代码高亮文件
|
||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import xml from 'highlight.js/lib/languages/java'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import sql from 'highlight.js/lib/languages/sql'
|
||||
import typescript from 'highlight.js/lib/languages/typescript'
|
||||
const highlightedCode = (item) => {
|
||||
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
|
||||
const result = hljs.highlight(language, item.code || '', true)
|
||||
return result.value || ' '
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 场景一:新增表单
|
||||
@ -180,13 +148,5 @@ onMounted(async () => {
|
||||
formValues.value = data
|
||||
setConfAndFields(designer, data.conf, data.fields)
|
||||
})
|
||||
// 注册代码高亮的各种语言
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('html', xml)
|
||||
hljs.registerLanguage('vue', xml)
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('sql', sql)
|
||||
hljs.registerLanguage('typescript', typescript)
|
||||
})
|
||||
</script>
|
||||
|
@ -203,6 +203,7 @@ import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
// import { OptionAttrs } from '@form-create/element-ui/types/config'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
|
||||
|
||||
const { query } = useRoute() // 查询参数
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
@ -3,8 +3,6 @@
|
||||
<el-row>
|
||||
<el-col>
|
||||
<div class="mb-2 float-right">
|
||||
<el-button size="small" @click="setJson"> 导入JSON</el-button>
|
||||
<el-button size="small" @click="setOption"> 导入Options</el-button>
|
||||
<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>
|
||||
@ -18,18 +16,18 @@
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 弹窗:表单预览 -->
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600">
|
||||
<div ref="editor" v-if="dialogVisible">
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
|
||||
<div v-if="dialogVisible" ref="editor">
|
||||
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formData)" />
|
||||
<el-scrollbar height="580">
|
||||
<div>
|
||||
<pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre>
|
||||
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="InfraBuild">
|
||||
<script lang="ts" name="InfraBuild" setup>
|
||||
import FcDesigner from '@form-create/designer'
|
||||
// import { useClipboard } from '@vueuse/core'
|
||||
import { isString } from '@/utils/is'
|
||||
@ -54,12 +52,7 @@ const openModel = (title: string) => {
|
||||
dialogTitle.value = title
|
||||
}
|
||||
|
||||
const setJson = () => {
|
||||
openModel('导入JSON--未实现')
|
||||
}
|
||||
const setOption = () => {
|
||||
openModel('导入Options--未实现')
|
||||
}
|
||||
/** 生成 JSON */
|
||||
const showJson = () => {
|
||||
openModel('生成 JSON')
|
||||
formType.value = 0
|
||||
|
@ -1,67 +1,83 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<ContentDetailWrap :title="title" @back="push('/infra/codegen')">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基本信息" name="basicInfo">
|
||||
<BasicInfoForm ref="basicInfoRef" :basicInfo="tableCurrentRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段信息" name="cloum">
|
||||
<CloumInfoForm ref="cloumInfoRef" :info="cloumCurrentRow" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #right>
|
||||
<XButton
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="loading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
</template>
|
||||
</ContentDetailWrap>
|
||||
<ContentWrap v-loading="formLoading">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基本信息" name="basicInfo">
|
||||
<basic-info-form ref="basicInfoRef" :table="formData.table" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段信息" name="column">
|
||||
<column-info-form ref="columnInfoRef" :columns="formData.columns" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生成信息" name="generateInfo">
|
||||
<generate-info-form ref="generateInfoRef" :table="formData.table" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form>
|
||||
<el-form-item style="float: right">
|
||||
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button @click="close">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { BasicInfoForm, CloumInfoForm } from './components'
|
||||
import { getCodegenTableApi, updateCodegenTableApi } from '@/api/infra/codegen'
|
||||
import { CodegenTableVO, CodegenColumnVO, CodegenUpdateReqVO } from '@/api/infra/codegen/types'
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { BasicInfoForm, ColumnInfoForm, GenerateInfoForm } from './components'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenEditTable' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push } = useRouter()
|
||||
const { query } = useRoute()
|
||||
const loading = ref(false)
|
||||
const title = ref('代码生成')
|
||||
const activeName = ref('basicInfo')
|
||||
const cloumInfoRef = ref(null)
|
||||
const tableCurrentRow = ref<CodegenTableVO>()
|
||||
const cloumCurrentRow = ref<CodegenColumnVO[]>([])
|
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const { query } = useRoute() // 查询参数
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const getList = async () => {
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const activeName = ref('column') // Tag 激活的窗口
|
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
|
||||
const columnInfoRef = ref<ComponentRef<typeof ColumnInfoForm>>()
|
||||
const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>()
|
||||
const formData = ref<CodegenApi.CodegenUpdateReqVO>({
|
||||
table: {},
|
||||
columns: []
|
||||
})
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = async () => {
|
||||
const id = query.id as unknown as number
|
||||
if (id) {
|
||||
// 获取表详细信息
|
||||
const res = await getCodegenTableApi(id)
|
||||
title.value = '修改[ ' + res.table.tableName + ' ]生成配置'
|
||||
tableCurrentRow.value = res.table
|
||||
cloumCurrentRow.value = res.columns
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await CodegenApi.getCodegenTableApi(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
const basicInfo = unref(basicInfoRef)
|
||||
const basicForm = await basicInfo?.elFormRef?.validate()?.catch(() => {})
|
||||
if (basicForm) {
|
||||
const basicInfoData = (await basicInfo?.getFormData()) as CodegenTableVO
|
||||
const genTable: CodegenUpdateReqVO = {
|
||||
table: basicInfoData,
|
||||
columns: cloumCurrentRow.value
|
||||
}
|
||||
await updateCodegenTableApi(genTable)
|
||||
// 参数校验
|
||||
if (!unref(formData)) return
|
||||
await unref(basicInfoRef)?.validate()
|
||||
await unref(generateInfoRef)?.validate()
|
||||
try {
|
||||
// 提交请求
|
||||
await CodegenApi.updateCodegenTableApi(formData.value)
|
||||
message.success(t('common.updateSuccess'))
|
||||
push('/infra/codegen')
|
||||
}
|
||||
close()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 关闭按钮 */
|
||||
const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
push('/infra/codegen')
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,183 +1,87 @@
|
||||
<template>
|
||||
<Form :rules="rules" @register="register" />
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input v-model="formData.tableName" placeholder="请输入仓库名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input v-model="formData.tableComment" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="className">
|
||||
<template #label>
|
||||
<span>
|
||||
实体类名称
|
||||
<el-tooltip
|
||||
content="默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。"
|
||||
placement="top"
|
||||
>
|
||||
<Icon class="" icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.className" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input v-model="formData.author" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" :rows="3" type="textarea" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { CodegenTableVO } from '@/api/infra/codegen/types'
|
||||
import { getIntDictOptions } from '@/utils/dict'
|
||||
import { listSimpleMenusApi } from '@/api/system/menu'
|
||||
import { handleTree, defaultProps } from '@/utils/tree'
|
||||
<script lang="ts" setup>
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenBasicInfoForm' })
|
||||
|
||||
const props = defineProps({
|
||||
basicInfo: {
|
||||
type: Object as PropType<Nullable<CodegenTableVO>>,
|
||||
table: {
|
||||
type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)
|
||||
const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)
|
||||
const menuOptions = ref<any>([]) // 树形结构
|
||||
const getTree = async () => {
|
||||
const res = await listSimpleMenusApi()
|
||||
menuOptions.value = handleTree(res)
|
||||
}
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
tableName: '',
|
||||
tableComment: '',
|
||||
className: '',
|
||||
author: '',
|
||||
remark: ''
|
||||
})
|
||||
const rules = reactive({
|
||||
tableName: [required],
|
||||
tableComment: [required],
|
||||
className: [required],
|
||||
author: [required],
|
||||
templateType: [required],
|
||||
scene: [required],
|
||||
moduleName: [required],
|
||||
businessName: [required],
|
||||
businessPackage: [required],
|
||||
classComment: [required]
|
||||
})
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
label: '上级菜单',
|
||||
field: 'parentMenuId',
|
||||
component: 'TreeSelect',
|
||||
componentProps: {
|
||||
data: menuOptions,
|
||||
props: defaultProps,
|
||||
checkStrictly: true,
|
||||
nodeKey: 'id'
|
||||
},
|
||||
labelMessage: '分配到指定菜单下,例如 系统管理',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '表名称',
|
||||
field: 'tableName',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '表描述',
|
||||
field: 'tableComment',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '实体类名称',
|
||||
field: 'className',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '类名称',
|
||||
field: 'className',
|
||||
component: 'Input',
|
||||
labelMessage: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '生成模板',
|
||||
field: 'templateType',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: templateTypeOptions
|
||||
},
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '生成场景',
|
||||
field: 'scene',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: sceneOptions
|
||||
},
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '模块名',
|
||||
field: 'moduleName',
|
||||
component: 'Input',
|
||||
labelMessage: '模块名,即一级目录,例如 system、infra、tool 等等',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '业务名',
|
||||
field: 'businessName',
|
||||
component: 'Input',
|
||||
labelMessage: '业务名,即二级目录,例如 user、permission、dict 等等',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '类描述',
|
||||
field: 'classComment',
|
||||
component: 'Input',
|
||||
labelMessage: '用作类描述,例如 用户',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '作者',
|
||||
field: 'author',
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 12
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'remark',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
])
|
||||
const { register, methods, elFormRef } = useForm({
|
||||
schema
|
||||
author: [required]
|
||||
})
|
||||
|
||||
/** 监听 table 属性,复制给 formData 属性 */
|
||||
watch(
|
||||
() => props.basicInfo,
|
||||
(basicInfo) => {
|
||||
if (!basicInfo) return
|
||||
const { setValues } = methods
|
||||
setValues(basicInfo)
|
||||
() => props.table,
|
||||
(table) => {
|
||||
if (!table) return
|
||||
formData.value = table
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getTree()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
elFormRef,
|
||||
getFormData: methods.getFormData
|
||||
validate: async () => unref(formRef)?.validate()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,137 +0,0 @@
|
||||
<template>
|
||||
<vxe-table
|
||||
ref="dragTable"
|
||||
border
|
||||
:data="info"
|
||||
max-height="600"
|
||||
stripe
|
||||
class="xtable-scrollbar"
|
||||
:column-config="{ resizable: true }"
|
||||
>
|
||||
<vxe-column title="字段列名" field="columnName" fixed="left" width="10%" />
|
||||
<vxe-colgroup title="基础属性">
|
||||
<vxe-column title="字段描述" field="columnComment" width="10%">
|
||||
<template #default="{ row }">
|
||||
<vxe-input v-model="row.columnComment" placeholder="请输入字段描述" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="物理类型" field="dataType" width="10%" />
|
||||
<vxe-column title="Java类型" width="10%" field="javaType">
|
||||
<template #default="{ row }">
|
||||
<vxe-select v-model="row.javaType" placeholder="请选择Java类型">
|
||||
<vxe-option label="Long" value="Long" />
|
||||
<vxe-option label="String" value="String" />
|
||||
<vxe-option label="Integer" value="Integer" />
|
||||
<vxe-option label="Double" value="Double" />
|
||||
<vxe-option label="BigDecimal" value="BigDecimal" />
|
||||
<vxe-option label="LocalDateTime" value="LocalDateTime" />
|
||||
<vxe-option label="Boolean" value="Boolean" />
|
||||
</vxe-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="java属性" width="8%" field="javaField">
|
||||
<template #default="{ row }">
|
||||
<vxe-input v-model="row.javaField" placeholder="请输入java属性" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-colgroup>
|
||||
<vxe-colgroup title="增删改查">
|
||||
<vxe-column title="插入" width="40px" field="createOperation">
|
||||
<template #default="{ row }">
|
||||
<vxe-checkbox true-label="true" false-label="false" v-model="row.createOperation" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="编辑" width="40px" field="updateOperation">
|
||||
<template #default="{ row }">
|
||||
<vxe-checkbox true-label="true" false-label="false" v-model="row.updateOperation" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="列表" width="40px" field="listOperationResult">
|
||||
<template #default="{ row }">
|
||||
<vxe-checkbox true-label="true" false-label="false" v-model="row.listOperationResult" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="查询" width="40px" field="listOperation">
|
||||
<template #default="{ row }">
|
||||
<vxe-checkbox true-label="true" false-label="false" v-model="row.listOperation" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="允许空" width="40px" field="nullable">
|
||||
<template #default="{ row }">
|
||||
<vxe-checkbox true-label="true" false-label="false" v-model="row.nullable" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="查询方式" width="60px" field="listOperationCondition">
|
||||
<template #default="{ row }">
|
||||
<vxe-select v-model="row.listOperationCondition" placeholder="请选择查询方式">
|
||||
<vxe-option label="=" value="=" />
|
||||
<vxe-option label="!=" value="!=" />
|
||||
<vxe-option label=">" value=">" />
|
||||
<vxe-option label=">=" value=">=" />
|
||||
<vxe-option label="<" value="<>" />
|
||||
<vxe-option label="<=" value="<=" />
|
||||
<vxe-option label="LIKE" value="LIKE" />
|
||||
<vxe-option label="BETWEEN" value="BETWEEN" />
|
||||
</vxe-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-colgroup>
|
||||
<vxe-column title="显示类型" width="10%" field="htmlType">
|
||||
<template #default="{ row }">
|
||||
<vxe-select v-model="row.htmlType" placeholder="请选择显示类型">
|
||||
<vxe-option label="文本框" value="input" />
|
||||
<vxe-option label="文本域" value="textarea" />
|
||||
<vxe-option label="下拉框" value="select" />
|
||||
<vxe-option label="单选框" value="radio" />
|
||||
<vxe-option label="复选框" value="checkbox" />
|
||||
<vxe-option label="日期控件" value="datetime" />
|
||||
<vxe-option label="图片上传" value="imageUpload" />
|
||||
<vxe-option label="文件上传" value="fileUpload" />
|
||||
<vxe-option label="富文本控件" value="editor" />
|
||||
</vxe-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="字典类型" width="10%" field="dictType">
|
||||
<template #default="{ row }">
|
||||
<vxe-select v-model="row.dictType" clearable filterable placeholder="请选择字典类型">
|
||||
<vxe-option
|
||||
v-for="dict in dictOptions"
|
||||
:key="dict.id"
|
||||
:label="dict.name"
|
||||
:value="dict.type"
|
||||
/>
|
||||
</vxe-select>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column title="示例" field="example">
|
||||
<template #default="{ row }">
|
||||
<vxe-input v-model="row.example" placeholder="请输入示例" />
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { DictTypeVO } from '@/api/system/dict/types'
|
||||
import { CodegenColumnVO } from '@/api/infra/codegen/types'
|
||||
import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type'
|
||||
|
||||
const props = defineProps({
|
||||
info: {
|
||||
type: Array as unknown as PropType<CodegenColumnVO[]>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
/** 查询字典下拉列表 */
|
||||
const dictOptions = ref<DictTypeVO[]>()
|
||||
const getDictOptions = async () => {
|
||||
const res = await listSimpleDictTypeApi()
|
||||
dictOptions.value = res
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getDictOptions()
|
||||
})
|
||||
defineExpose({
|
||||
info: props.info
|
||||
})
|
||||
</script>
|
153
src/views/infra/codegen/components/ColumnInfoForm.vue
Normal file
153
src/views/infra/codegen/components/ColumnInfoForm.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<el-table ref="dragTable" :data="formData" :max-height="tableHeight" row-key="columnId">
|
||||
<el-table-column
|
||||
:show-overflow-tooltip="true"
|
||||
label="字段列名"
|
||||
min-width="10%"
|
||||
prop="columnName"
|
||||
/>
|
||||
<el-table-column label="字段描述" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.columnComment" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:show-overflow-tooltip="true"
|
||||
label="物理类型"
|
||||
min-width="10%"
|
||||
prop="dataType"
|
||||
/>
|
||||
<el-table-column label="Java类型" min-width="11%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.javaType">
|
||||
<el-option label="Long" value="Long" />
|
||||
<el-option label="String" value="String" />
|
||||
<el-option label="Integer" value="Integer" />
|
||||
<el-option label="Double" value="Double" />
|
||||
<el-option label="BigDecimal" value="BigDecimal" />
|
||||
<el-option label="LocalDateTime" value="LocalDateTime" />
|
||||
<el-option label="Boolean" value="Boolean" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="java属性" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.javaField" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="插入" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.createOperation" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.updateOperation" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="列表" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox
|
||||
v-model="scope.row.listOperationResult"
|
||||
false-label="false"
|
||||
true-label="true"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询" min-width="4%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.listOperation" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询方式" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.listOperationCondition">
|
||||
<el-option label="=" value="=" />
|
||||
<el-option label="!=" value="!=" />
|
||||
<el-option label=">" value=">" />
|
||||
<el-option label=">=" value=">=" />
|
||||
<el-option label="<" value="<>" />
|
||||
<el-option label="<=" value="<=" />
|
||||
<el-option label="LIKE" value="LIKE" />
|
||||
<el-option label="BETWEEN" value="BETWEEN" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="允许空" min-width="5%">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.nullable" false-label="false" true-label="true" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示类型" min-width="12%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.htmlType">
|
||||
<el-option label="文本框" value="input" />
|
||||
<el-option label="文本域" value="textarea" />
|
||||
<el-option label="下拉框" value="select" />
|
||||
<el-option label="单选框" value="radio" />
|
||||
<el-option label="复选框" value="checkbox" />
|
||||
<el-option label="日期控件" value="datetime" />
|
||||
<el-option label="图片上传" value="imageUpload" />
|
||||
<el-option label="文件上传" value="fileUpload" />
|
||||
<el-option label="富文本控件" value="editor" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典类型" min-width="12%">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in dictOptions"
|
||||
:key="dict.id"
|
||||
:label="dict.name"
|
||||
:value="dict.type"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="示例" min-width="10%">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.example" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import * as DictDataApi from '@/api/system/dict/dict.type'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenColumInfoForm' })
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const formData = ref<CodegenApi.CodegenColumnVO[]>([])
|
||||
const tableHeight = document.documentElement.scrollHeight - 350 + 'px'
|
||||
|
||||
/** 查询字典下拉列表 */
|
||||
const dictOptions = ref<DictDataApi.DictTypeVO[]>()
|
||||
const getDictOptions = async () => {
|
||||
dictOptions.value = await DictDataApi.getSimpleDictTypeList()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.columns,
|
||||
(columns) => {
|
||||
if (!columns) return
|
||||
formData.value = columns
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await getDictOptions()
|
||||
})
|
||||
</script>
|
391
src/views/infra/codegen/components/GenerateInfoForm.vue
Normal file
391
src/views/infra/codegen/components/GenerateInfoForm.vue
Normal file
@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生成模板" prop="templateType">
|
||||
<el-select v-model="formData.templateType" @change="tplSelectChange">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="前端类型" prop="frontType">
|
||||
<el-select v-model="formData.frontType">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生成场景" prop="scene">
|
||||
<el-select v-model="formData.scene">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
上级菜单
|
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-tree-select
|
||||
v-model="formData.parentMenuId"
|
||||
:data="menus"
|
||||
:props="menuTreeProps"
|
||||
check-strictly
|
||||
node-key="id"
|
||||
placeholder="请选择系统菜单"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item prop="packageName">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 生成包路径-->
|
||||
<!-- <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input v-model="formData.packageName" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="moduleName">
|
||||
<template #label>
|
||||
<span>
|
||||
模块名
|
||||
<el-tooltip
|
||||
content="模块名,即一级目录,例如 system、infra、tool 等等"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.moduleName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="businessName">
|
||||
<template #label>
|
||||
<span>
|
||||
业务名
|
||||
<el-tooltip
|
||||
content="业务名,即二级目录,例如 user、permission、dict 等等"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.businessName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item prop="businessPackage">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 业务包-->
|
||||
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input v-model="formData.businessPackage" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="className">
|
||||
<template #label>
|
||||
<span>
|
||||
类名称
|
||||
<el-tooltip
|
||||
content="类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.className" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="classComment">
|
||||
<template #label>
|
||||
<span>
|
||||
类描述
|
||||
<el-tooltip content="用作类描述,例如 用户" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.classComment" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="formData.genType === '1'" :span="24">
|
||||
<el-form-item prop="genPath">
|
||||
<template #label>
|
||||
<span>
|
||||
自定义路径
|
||||
<el-tooltip
|
||||
content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下"
|
||||
placement="top"
|
||||
>
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="formData.genPath">
|
||||
<template #append>
|
||||
<el-dropdown>
|
||||
<el-button type="primary">
|
||||
最近路径快速选择
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="formData.genPath = '/'">
|
||||
恢复默认的生成基础路径
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row v-show="formData.tplCategory === 'tree'">
|
||||
<h4 class="form-header">其他信息</h4>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
树编码字段
|
||||
<el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.treeCode" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in formData.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
树父编码字段
|
||||
<el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.treeParentCode" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in formData.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
树名称字段
|
||||
<el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<el-select v-model="formData.treeName" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in formData.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="formData.tplCategory === 'sub'">
|
||||
<h4 class="form-header">关联信息</h4>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
关联子表的表名
|
||||
<el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.subTableName" placeholder="请选择" @change="subSelectChange">
|
||||
<el-option
|
||||
v-for="(table0, index) in tables"
|
||||
:key="index"
|
||||
:label="table0.tableName + ':' + table0.tableComment"
|
||||
:value="table0.tableName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
子表关联的外键名
|
||||
<el-tooltip content="子表关联的外键名, 如:user_id" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-select v-model="formData.subTableFkName" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in subColumns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import { PropType } from 'vue'
|
||||
|
||||
defineOptions({ name: 'InfraCodegenGenerateInfoForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const props = defineProps({
|
||||
table: {
|
||||
type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>,
|
||||
default: () => null
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const formData = ref({
|
||||
templateType: null,
|
||||
frontType: null,
|
||||
scene: null,
|
||||
moduleName: '',
|
||||
businessName: '',
|
||||
className: '',
|
||||
classComment: '',
|
||||
parentMenuId: null,
|
||||
genPath: '',
|
||||
treeCode: '',
|
||||
treeParentCode: '',
|
||||
treeName: '',
|
||||
tplCategory: '',
|
||||
subTableName: '',
|
||||
subTableFkName: '',
|
||||
genType: ''
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
templateType: [required],
|
||||
frontType: [required],
|
||||
scene: [required],
|
||||
moduleName: [required],
|
||||
businessName: [required],
|
||||
businessPackage: [required],
|
||||
className: [required],
|
||||
classComment: [required]
|
||||
})
|
||||
|
||||
const tables = ref([])
|
||||
const subColumns = ref([])
|
||||
const menus = ref<any[]>([])
|
||||
const menuTreeProps = {
|
||||
label: 'name'
|
||||
}
|
||||
|
||||
/** 选择子表名触发 */
|
||||
const subSelectChange = () => {
|
||||
formData.value.subTableFkName = ''
|
||||
}
|
||||
|
||||
/** 选择生成模板触发 */
|
||||
const tplSelectChange = (value) => {
|
||||
if (value !== 1) {
|
||||
// TODO 芋艿:暂时不考虑支持树形结构
|
||||
message.error(
|
||||
'暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发'
|
||||
)
|
||||
return false
|
||||
}
|
||||
if (value !== 'sub') {
|
||||
formData.value.subTableName = ''
|
||||
formData.value.subTableFkName = ''
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.table,
|
||||
(table) => {
|
||||
if (!table) return
|
||||
formData.value = table as any
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const resp = await MenuApi.getSimpleMenusList()
|
||||
menus.value = handleTree(resp)
|
||||
} catch {}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate: async () => unref(formRef)?.validate()
|
||||
})
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<XModal title="预览" v-model="preview.open" height="99%">
|
||||
<XModal title="预览" v-model="preview.open" width="80%" height="99%">
|
||||
<div class="flex">
|
||||
<el-card class="w-1/4" :gutter="12" shadow="hover">
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||
@ -22,7 +22,7 @@
|
||||
:key="item.filePath"
|
||||
>
|
||||
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" />
|
||||
<pre>{{ item.code }}</pre>
|
||||
<pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
@ -35,6 +35,14 @@ import { handleTree2 } from '@/utils/tree'
|
||||
import { previewCodegenApi } from '@/api/infra/codegen'
|
||||
import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
|
||||
|
||||
import hljs from 'highlight.js' // 导入代码高亮文件
|
||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式
|
||||
import java from 'highlight.js/lib/languages/java'
|
||||
import xml from 'highlight.js/lib/languages/java'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import sql from 'highlight.js/lib/languages/sql'
|
||||
import typescript from 'highlight.js/lib/languages/typescript'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// ======== 显示页面 ========
|
||||
@ -148,6 +156,28 @@ const copy = async (text: string) => {
|
||||
message.success(t('common.copySuccess'))
|
||||
oInput.remove()
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码高亮
|
||||
*/
|
||||
const highlightedCode = (item) => {
|
||||
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
|
||||
const result = hljs.highlight(language, item.code || '', true)
|
||||
return result.value || ' '
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
// 注册代码高亮的各种语言
|
||||
hljs.registerLanguage('java', java)
|
||||
hljs.registerLanguage('xml', xml)
|
||||
hljs.registerLanguage('html', xml)
|
||||
hljs.registerLanguage('vue', xml)
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('sql', sql)
|
||||
hljs.registerLanguage('typescript', typescript)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
import BasicInfoForm from './BasicInfoForm.vue'
|
||||
import CloumInfoForm from './CloumInfoForm.vue'
|
||||
import ColumnInfoForm from './ColumnInfoForm.vue'
|
||||
import GenerateInfoForm from './GenerateInfoForm.vue'
|
||||
import ImportTable from './ImportTable.vue'
|
||||
import Preview from './Preview.vue'
|
||||
export { BasicInfoForm, CloumInfoForm, ImportTable, Preview }
|
||||
export { BasicInfoForm, ColumnInfoForm, GenerateInfoForm, ImportTable, Preview }
|
||||
|
@ -5,6 +5,7 @@
|
||||
<BasicInfoForm
|
||||
ref="basicInfoRef"
|
||||
v-model:activeName="activeName"
|
||||
:is-detail="isDetail"
|
||||
:propFormData="formData"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
@ -12,6 +13,7 @@
|
||||
<DescriptionForm
|
||||
ref="descriptionRef"
|
||||
v-model:activeName="activeName"
|
||||
:is-detail="isDetail"
|
||||
:propFormData="formData"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
@ -19,19 +21,22 @@
|
||||
<OtherSettingsForm
|
||||
ref="otherSettingsRef"
|
||||
v-model:activeName="activeName"
|
||||
:is-detail="isDetail"
|
||||
:propFormData="formData"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form>
|
||||
<el-form-item style="float: right">
|
||||
<el-button :loading="formLoading" type="primary" @click="submitForm">保存</el-button>
|
||||
<el-button v-if="!isDetail" :loading="formLoading" type="primary" @click="submitForm">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="close">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" name="ProductSpuForm" setup>
|
||||
<script lang="ts" setup>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
|
||||
@ -39,19 +44,22 @@ import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { convertToInteger, formatToFraction } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'ProductSpuForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push, currentRoute } = useRouter() // 路由
|
||||
const { params } = useRoute() // 查询参数
|
||||
const { params, name } = useRoute() // 查询参数
|
||||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const activeName = ref('basicInfo') // Tag 激活的窗口
|
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>() // 商品信息Ref
|
||||
const descriptionRef = ref<ComponentRef<typeof DescriptionForm>>() // 商品详情Ref
|
||||
const otherSettingsRef = ref<ComponentRef<typeof OtherSettingsForm>>() // 其他设置Ref
|
||||
const isDetail = ref(false) // 是否查看详情
|
||||
const basicInfoRef = ref() // 商品信息Ref
|
||||
const descriptionRef = ref() // 商品详情Ref
|
||||
const otherSettingsRef = ref() // 其他设置Ref
|
||||
// spu 表单数据
|
||||
const formData = ref<ProductSpuApi.SpuType>({
|
||||
const formData = ref<ProductSpuApi.Spu>({
|
||||
name: '', // 商品名称
|
||||
categoryId: null, // 商品分类
|
||||
keyword: '', // 关键字
|
||||
@ -59,7 +67,7 @@ const formData = ref<ProductSpuApi.SpuType>({
|
||||
picUrl: '', // 商品封面图
|
||||
sliderPicUrls: [], // 商品轮播图
|
||||
introduction: '', // 商品简介
|
||||
deliveryTemplateId: 1, // 运费模版
|
||||
deliveryTemplateId: null, // 运费模版
|
||||
brandId: null, // 商品品牌
|
||||
specType: false, // 商品规格
|
||||
subCommissionType: false, // 分销类型
|
||||
@ -90,12 +98,15 @@ const formData = ref<ProductSpuApi.SpuType>({
|
||||
|
||||
/** 获得详情 */
|
||||
const getDetail = async () => {
|
||||
if ('ProductSpuDetail' === name) {
|
||||
isDetail.value = true
|
||||
}
|
||||
const id = params.spuId as number
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.SpuType
|
||||
res.skus.forEach((item) => {
|
||||
const res = (await ProductSpuApi.getSpu(id)) as ProductSpuApi.Spu
|
||||
res.skus?.forEach((item) => {
|
||||
// 回显价格分转元
|
||||
item.price = formatToFraction(item.price)
|
||||
item.marketPrice = formatToFraction(item.marketPrice)
|
||||
@ -120,9 +131,10 @@ const submitForm = async () => {
|
||||
await unref(basicInfoRef)?.validate()
|
||||
await unref(descriptionRef)?.validate()
|
||||
await unref(otherSettingsRef)?.validate()
|
||||
const deepCopyFormData = cloneDeep(unref(formData.value)) // 深拷贝一份 fix:这样最终 server 端不满足,不需要恢复,
|
||||
// TODO 兜底处理 sku 空数据
|
||||
formData.value.skus.forEach((sku) => {
|
||||
// 深拷贝一份, 这样最终 server 端不满足,不需要恢复,
|
||||
const deepCopyFormData = cloneDeep(unref(formData.value))
|
||||
// 兜底处理 sku 空数据
|
||||
formData.value.skus!.forEach((sku) => {
|
||||
// 因为是空数据这里判断一下商品条码是否为空就行
|
||||
if (sku.barCode === '') {
|
||||
const index = deepCopyFormData.skus.findIndex(
|
||||
@ -150,7 +162,7 @@ const submitForm = async () => {
|
||||
})
|
||||
deepCopyFormData.sliderPicUrls = newSliderPicUrls
|
||||
// 校验都通过后提交表单
|
||||
const data = deepCopyFormData as ProductSpuApi.SpuType
|
||||
const data = deepCopyFormData as ProductSpuApi.Spu
|
||||
const id = params.spuId as number
|
||||
if (!id) {
|
||||
await ProductSpuApi.createSpu(data)
|
||||
@ -170,7 +182,6 @@ const close = () => {
|
||||
delView(unref(currentRoute))
|
||||
push('/product/product-spu')
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await getDetail()
|
||||
|
@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-form
|
||||
v-if="!isDetail"
|
||||
ref="productSpuBasicInfoRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
@ -7,7 +14,6 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- TODO @puhui999:只能选根节点 -->
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<el-tree-select
|
||||
v-model="formData.categoryId"
|
||||
@ -17,6 +23,7 @@
|
||||
class="w-1/1"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
@change="categoryNodeClick"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -60,9 +67,15 @@
|
||||
<el-col :span="12">
|
||||
<el-form-item label="运费模板" prop="deliveryTemplateId">
|
||||
<el-select v-model="formData.deliveryTemplateId" placeholder="请选择">
|
||||
<el-option v-for="item in []" :key="item.id" :label="item.name" :value="item.id" />
|
||||
<el-option
|
||||
v-for="item in deliveryTemplateList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button class="ml-20px">运费模板</el-button>
|
||||
<!-- TODO 可能情况:善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
|
||||
<!-- <el-button class="ml-20px">运费模板</el-button>-->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@ -95,6 +108,9 @@
|
||||
</el-col>
|
||||
<!-- 多规格添加-->
|
||||
<el-col :span="24">
|
||||
<el-form-item v-if="!formData.specType">
|
||||
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="formData.specType" label="商品属性">
|
||||
<el-button class="mr-15px mb-10px" @click="attributesAddFormRef.open">添加规格</el-button>
|
||||
<ProductAttributes :propertyList="propertyList" @success="generateSkus" />
|
||||
@ -107,34 +123,94 @@
|
||||
<SkuList ref="skuListRef" :prop-form-data="formData" :propertyList="propertyList" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item v-if="!formData.specType">
|
||||
<SkuList :prop-form-data="formData" :propertyList="propertyList" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<ProductAttributesAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
|
||||
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template>
|
||||
<template #brandId="{ row }">
|
||||
{{ brandList.find((item) => item.id === row.brandId)?.name }}
|
||||
</template>
|
||||
<template #deliveryTemplateId="{ row }">
|
||||
{{ deliveryTemplateList.find((item) => item.id === row.deliveryTemplateId)?.name }}
|
||||
</template>
|
||||
<template #specType="{ row }">
|
||||
{{ row.specType ? '多规格' : '单规格' }}
|
||||
</template>
|
||||
<template #subCommissionType="{ row }">
|
||||
{{ row.subCommissionType ? '自行设置' : '默认设置' }}
|
||||
</template>
|
||||
<template #picUrl="{ row }">
|
||||
<el-image :src="row.picUrl" class="w-60px h-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"
|
||||
@click="imagePreview(row.sliderPicUrls)"
|
||||
/>
|
||||
</template>
|
||||
<template #skus>
|
||||
<SkuList
|
||||
ref="skuDetailListRef"
|
||||
:is-detail="isDetail"
|
||||
:prop-form-data="formData"
|
||||
:propertyList="propertyList"
|
||||
/>
|
||||
</template>
|
||||
</Descriptions>
|
||||
|
||||
<!-- 商品属性添加 Form 表单 -->
|
||||
<ProductPropertyAddForm ref="attributesAddFormRef" :propertyList="propertyList" />
|
||||
</template>
|
||||
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
import { isArray } from '@/utils/is'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import type { SpuType } from '@/api/mall/product/spu'
|
||||
import { UploadImg, UploadImgs } from '@/components/UploadFile'
|
||||
import { ProductAttributes, ProductAttributesAddForm, SkuList } from './index'
|
||||
import { getPropertyList, ProductAttributes, ProductPropertyAddForm, SkuList } from './index'
|
||||
import { basicInfoSchema } from './spu.data'
|
||||
import type { Spu } from '@/api/mall/product/spu'
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
import { getSimpleBrandList } from '@/api/mall/product/brand'
|
||||
import { getSimpleTemplateList } from '@/api/mall/trade/delivery/expressTemplate/index'
|
||||
// ====== 商品详情相关操作 ======
|
||||
const { allSchemas } = useCrudSchemas(basicInfoSchema)
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (args) => {
|
||||
const urlList = []
|
||||
if (isArray(args)) {
|
||||
args.forEach((item) => {
|
||||
urlList.push(item.url)
|
||||
})
|
||||
} else {
|
||||
urlList.push(args)
|
||||
}
|
||||
createImageViewer({
|
||||
urlList
|
||||
})
|
||||
}
|
||||
// ====== end ======
|
||||
|
||||
defineOptions({ name: 'ProductSpuBasicInfoForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
activeName: propTypes.string.def('')
|
||||
activeName: propTypes.string.def(''),
|
||||
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||
})
|
||||
const attributesAddFormRef = ref() // 添加商品属性表单
|
||||
const productSpuBasicInfoRef = ref() // 表单 Ref
|
||||
@ -144,15 +220,15 @@ const skuListRef = ref() // 商品属性列表Ref
|
||||
const generateSkus = (propertyList) => {
|
||||
skuListRef.value.generateTableData(propertyList)
|
||||
}
|
||||
const formData = reactive<SpuType>({
|
||||
const formData = reactive<Spu>({
|
||||
name: '', // 商品名称
|
||||
categoryId: null, // 商品分类
|
||||
keyword: '', // 关键字
|
||||
unit: '', // 单位
|
||||
unit: null, // 单位
|
||||
picUrl: '', // 商品封面图
|
||||
sliderPicUrls: [], // 商品轮播图
|
||||
introduction: '', // 商品简介
|
||||
deliveryTemplateId: 1, // 运费模版
|
||||
deliveryTemplateId: null, // 运费模版
|
||||
brandId: null, // 商品品牌
|
||||
specType: false, // 商品规格
|
||||
subCommissionType: false, // 分销类型
|
||||
@ -166,7 +242,7 @@ const rules = reactive({
|
||||
introduction: [required],
|
||||
picUrl: [required],
|
||||
sliderPicUrls: [required],
|
||||
// deliveryTemplateId: [required],
|
||||
deliveryTemplateId: [required],
|
||||
brandId: [required],
|
||||
specType: [required],
|
||||
subCommissionType: [required]
|
||||
@ -182,29 +258,10 @@ watch(
|
||||
return
|
||||
}
|
||||
copyValueToTarget(formData, data)
|
||||
formData.sliderPicUrls = data['sliderPicUrls'].map((item) => ({
|
||||
formData.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({
|
||||
url: item
|
||||
}))
|
||||
// TODO @puhui999:if return,减少嵌套层级
|
||||
// 只有是多规格才处理
|
||||
if (formData.specType) {
|
||||
// 直接拿返回的 skus 属性逆向生成出 propertyList
|
||||
const properties = []
|
||||
formData.skus.forEach((sku) => {
|
||||
sku.properties.forEach(({ propertyId, propertyName, valueId, valueName }) => {
|
||||
// 添加属性
|
||||
if (!properties.some((item) => item.id === propertyId)) {
|
||||
properties.push({ id: propertyId, name: propertyName, values: [] })
|
||||
}
|
||||
// 添加属性值
|
||||
const index = properties.findIndex((item) => item.id === propertyId)
|
||||
if (!properties[index].values.some((value) => value.id === valueId)) {
|
||||
properties[index].values.push({ id: valueId, name: valueName })
|
||||
}
|
||||
})
|
||||
})
|
||||
propertyList.value = properties
|
||||
}
|
||||
propertyList.value = getPropertyList(data)
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
@ -216,6 +273,8 @@ watch(
|
||||
*/
|
||||
const emit = defineEmits(['update:activeName'])
|
||||
const validate = async () => {
|
||||
// 校验 sku
|
||||
skuListRef.value.validateSku()
|
||||
// 校验表单
|
||||
if (!productSpuBasicInfoRef) return
|
||||
return await unref(productSpuBasicInfoRef).validate((valid) => {
|
||||
@ -263,12 +322,32 @@ const onChangeSpec = () => {
|
||||
}
|
||||
|
||||
const categoryList = ref([]) // 分类树
|
||||
/**
|
||||
* 选择分类时触发校验
|
||||
*/
|
||||
const categoryNodeClick = () => {
|
||||
if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
|
||||
formData.categoryId = null
|
||||
message.warning('必须选择二级及以下节点!!')
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取分类的节点的完整结构
|
||||
*
|
||||
* @param categoryId 分类id
|
||||
*/
|
||||
const categoryString = (categoryId) => {
|
||||
return treeToString(categoryList.value, categoryId)
|
||||
}
|
||||
const brandList = ref([]) // 精简商品品牌列表
|
||||
const deliveryTemplateList = ref([]) // 运费模版
|
||||
onMounted(async () => {
|
||||
// 获得分类树
|
||||
const data = await ProductCategoryApi.getCategoryList({})
|
||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||
// 获取商品品牌列表
|
||||
brandList.value = await getSimpleBrandList()
|
||||
// 获取运费模版
|
||||
deliveryTemplateList.value = await getSimpleTemplateList()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,28 +1,54 @@
|
||||
<template>
|
||||
<el-form ref="descriptionFormRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-form
|
||||
v-if="!isDetail"
|
||||
ref="descriptionFormRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<!--富文本编辑器组件-->
|
||||
<el-form-item label="商品详情" prop="description">
|
||||
<Editor v-model:modelValue="formData.description" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<Descriptions
|
||||
v-if="isDetail"
|
||||
:data="formData"
|
||||
:schema="allSchemas.detailSchema"
|
||||
class="descriptionFormDescriptions"
|
||||
>
|
||||
<!-- 展示 HTML 内容 -->
|
||||
<template #description="{ row }">
|
||||
<div v-dompurify-html="row.description" style="width: 600px"></div>
|
||||
</template>
|
||||
</Descriptions>
|
||||
</template>
|
||||
<script lang="ts" name="DescriptionForm" setup>
|
||||
import type { SpuType } from '@/api/mall/product/spu'
|
||||
<script lang="ts" setup>
|
||||
import type { Spu } from '@/api/mall/product/spu'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import { PropType } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { descriptionSchema } from './spu.data'
|
||||
|
||||
defineOptions({ name: 'DescriptionForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const { allSchemas } = useCrudSchemas(descriptionSchema)
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
activeName: propTypes.string.def('')
|
||||
activeName: propTypes.string.def(''),
|
||||
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||
})
|
||||
const descriptionFormRef = ref() // 表单Ref
|
||||
const formData = ref<SpuType>({
|
||||
const formData = ref<Spu>({
|
||||
description: '' // 商品详情
|
||||
})
|
||||
// 表单规则
|
||||
|
@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-form
|
||||
v-if="!isDetail"
|
||||
ref="otherSettingsFormRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-row :gutter="20">
|
||||
@ -50,26 +57,57 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
|
||||
<template #recommendHot="{ row }">
|
||||
{{ row.recommendHot ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendBenefit="{ row }">
|
||||
{{ row.recommendBenefit ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendBest="{ row }">
|
||||
{{ row.recommendBest ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendNew="{ row }">
|
||||
{{ row.recommendNew ? '是' : '否' }}
|
||||
</template>
|
||||
<template #recommendGood="{ row }">
|
||||
{{ row.recommendGood ? '是' : '否' }}
|
||||
</template>
|
||||
<template #activityOrders>
|
||||
<el-tag>默认</el-tag>
|
||||
<el-tag class="ml-2" type="success">秒杀</el-tag>
|
||||
<el-tag class="ml-2" type="info">砍价</el-tag>
|
||||
<el-tag class="ml-2" type="warning">拼团</el-tag>
|
||||
</template>
|
||||
</Descriptions>
|
||||
</template>
|
||||
<script lang="ts" name="OtherSettingsForm" setup>
|
||||
import type { SpuType } from '@/api/mall/product/spu'
|
||||
<script lang="ts" setup>
|
||||
import type { Spu } from '@/api/mall/product/spu'
|
||||
import { PropType } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { otherSettingsSchema } from './spu.data'
|
||||
|
||||
defineOptions({ name: 'OtherSettingsForm' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const { allSchemas } = useCrudSchemas(otherSettingsSchema)
|
||||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
activeName: propTypes.string.def('')
|
||||
activeName: propTypes.string.def(''),
|
||||
isDetail: propTypes.bool.def(false) // 是否作为详情组件
|
||||
})
|
||||
|
||||
const otherSettingsFormRef = ref() // 表单Ref
|
||||
// 表单数据
|
||||
const formData = ref<SpuType>({
|
||||
const formData = ref<Spu>({
|
||||
sort: 1, // 商品排序
|
||||
giveIntegral: 1, // 赠送积分
|
||||
virtualSalesCount: 1, // 虚拟销量
|
||||
|
@ -17,9 +17,11 @@
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" name="ProductPropertyForm" setup>
|
||||
<script lang="ts" setup>
|
||||
import * as PropertyApi from '@/api/mall/product/property'
|
||||
|
||||
defineOptions({ name: 'ProductPropertyForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
@ -90,8 +92,7 @@ const submitForm = async () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
name: '',
|
||||
remark: ''
|
||||
name: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<!-- 情况一:添加/修改 -->
|
||||
<el-table
|
||||
:data="isBatch ? skuList : formData.skus"
|
||||
v-if="!isDetail && !isActivityComponent"
|
||||
:data="isBatch ? skuList : formData!.skus!"
|
||||
border
|
||||
class="tabNumWidth"
|
||||
max-height="500"
|
||||
@ -11,7 +13,7 @@
|
||||
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData.specType && !isBatch">
|
||||
<template v-if="formData!.specType && !isBatch">
|
||||
<!-- 根据商品属性动态添加 -->
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableHeaders"
|
||||
@ -21,8 +23,9 @@
|
||||
min-width="120"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
|
||||
{{ row.properties[index]?.valueName }}
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
@ -73,7 +76,7 @@
|
||||
<el-input-number v-model="row.volume" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData.subCommissionType">
|
||||
<template v-if="formData!.subCommissionType">
|
||||
<el-table-column align="center" label="一级返佣(元)" min-width="168">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
@ -97,7 +100,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column v-if="formData.specType" align="center" fixed="right" label="操作" width="80">
|
||||
<el-table-column v-if="formData?.specType" align="center" fixed="right" label="操作" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="isBatch" link size="small" type="primary" @click="batchAdd">
|
||||
批量添加
|
||||
@ -106,27 +109,183 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 情况二:详情 -->
|
||||
<el-table
|
||||
v-if="isDetail"
|
||||
ref="activitySkuListRef"
|
||||
:data="formData!.skus!"
|
||||
border
|
||||
max-height="500"
|
||||
size="small"
|
||||
style="width: 99%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<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)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData!.specType && !isBatch">
|
||||
<!-- 根据商品属性动态添加 -->
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableHeaders"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
align="center"
|
||||
min-width="80"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column align="center" label="商品条码" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.barCode }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销售价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.price }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="市场价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.marketPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成本价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.costPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="库存" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.stock }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="重量(kg)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.weight }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="体积(m^3)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.volume }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData!.subCommissionType">
|
||||
<el-table-column align="center" label="一级返佣(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.subCommissionFirstPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="二级返佣(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.subCommissionSecondPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<!-- 情况三:作为活动组件 -->
|
||||
<el-table
|
||||
v-if="isActivityComponent"
|
||||
:data="formData!.skus!"
|
||||
border
|
||||
max-height="500"
|
||||
size="small"
|
||||
style="width: 99%"
|
||||
>
|
||||
<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)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template v-if="formData!.specType">
|
||||
<!-- 根据商品属性动态添加 -->
|
||||
<el-table-column
|
||||
v-for="(item, index) in tableHeaders"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
align="center"
|
||||
min-width="80"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span style="font-weight: bold; color: #40aaff">
|
||||
{{ row.properties[index]?.valueName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column align="center" label="商品条码" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ row.barCode }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="销售价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.price }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="市场价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.marketPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="成本价(元)" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.costPrice }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="库存" min-width="80">
|
||||
<template #default="{ row }">
|
||||
{{ row.stock }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 方便扩展每个活动配置的属性不一样 -->
|
||||
<slot name="extension"></slot>
|
||||
</el-table>
|
||||
</template>
|
||||
<script lang="ts" name="SkuList" setup>
|
||||
import { PropType } from 'vue'
|
||||
<script lang="ts" setup>
|
||||
import { PropType, Ref } from 'vue'
|
||||
import { copyValueToTarget } from '@/utils'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { UploadImg } from '@/components/UploadFile'
|
||||
import type { Property, SkuType, SpuType } from '@/api/mall/product/spu'
|
||||
import type { Property, Sku, Spu } from '@/api/mall/product/spu'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { RuleConfig } from '@/views/mall/product/spu/components/index'
|
||||
import { PropertyAndValues } from './index'
|
||||
import { ElTable } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'SkuList' })
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps({
|
||||
propFormData: {
|
||||
type: Object as PropType<SpuType>,
|
||||
type: Object as PropType<Spu>,
|
||||
default: () => {}
|
||||
},
|
||||
propertyList: {
|
||||
type: Array,
|
||||
type: Array as PropType<PropertyAndValues[]>,
|
||||
default: () => []
|
||||
},
|
||||
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
|
||||
ruleConfig: {
|
||||
type: Array as PropType<RuleConfig[]>,
|
||||
default: () => []
|
||||
},
|
||||
isBatch: propTypes.bool.def(false), // 是否作为批量操作组件
|
||||
isDetail: propTypes.bool.def(false), // 是否作为 sku 详情组件
|
||||
isComponent: propTypes.bool.def(false), // 是否作为 sku 选择组件
|
||||
isActivityComponent: propTypes.bool.def(false) // 是否作为 sku 活动配置组件
|
||||
})
|
||||
const formData = ref<SpuType>() // 表单数据
|
||||
const skuList = ref<SkuType[]>([
|
||||
const formData: Ref<Spu | undefined> = ref<Spu>() // 表单数据
|
||||
const skuList = ref<Sku[]>([
|
||||
{
|
||||
price: 0, // 商品价格
|
||||
marketPrice: 0, // 市场价
|
||||
@ -140,24 +299,87 @@ const skuList = ref<SkuType[]>([
|
||||
subCommissionSecondPrice: 0 // 二级分销的佣金
|
||||
}
|
||||
]) // 批量添加时的临时数据
|
||||
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 9999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/** 批量添加 */
|
||||
const batchAdd = () => {
|
||||
formData.value.skus.forEach((item) => {
|
||||
formData.value!.skus!.forEach((item) => {
|
||||
copyValueToTarget(item, skuList.value[0])
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除 sku */
|
||||
const deleteSku = (row) => {
|
||||
const index = formData.value.skus.findIndex(
|
||||
const index = formData.value!.skus!.findIndex(
|
||||
// 直接把列表转成字符串比较
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
formData.value.skus.splice(index, 1)
|
||||
formData.value!.skus!.splice(index, 1)
|
||||
}
|
||||
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
|
||||
/**
|
||||
* 保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||
*/
|
||||
const validateSku = () => {
|
||||
const checks = ['price', 'marketPrice', 'costPrice']
|
||||
let warningInfo = '请检查商品各行相关属性配置,'
|
||||
let validate = true // 默认通过
|
||||
for (const sku of formData.value!.skus!) {
|
||||
// 作为活动组件的校验
|
||||
if (props.isActivityComponent) {
|
||||
for (const rule of props.ruleConfig) {
|
||||
const arg = getValue(sku, rule.name)
|
||||
if (!rule.rule(arg)) {
|
||||
validate = false // 只要有一个不通过则直接不通过
|
||||
warningInfo += rule.message
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (checks.some((check) => sku[check] < 0.01)) {
|
||||
validate = false // 只要有一个不通过则直接不通过
|
||||
warningInfo = '商品相关价格不能低于 0.01 元!!'
|
||||
break
|
||||
}
|
||||
}
|
||||
// 只要有一个不通过则结束后续的校验
|
||||
if (!validate) {
|
||||
message.warning(warningInfo)
|
||||
throw new Error(warningInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
const getValue = (obj, arg) => {
|
||||
const keys = arg.split('.')
|
||||
let value = obj
|
||||
for (const key of keys) {
|
||||
if (value && typeof value === 'object' && key in value) {
|
||||
value = value[key]
|
||||
} else {
|
||||
value = undefined
|
||||
break
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'selectionChange', value: Sku[]): void
|
||||
}>()
|
||||
/**
|
||||
* 选择时触发
|
||||
* @param Sku 传递过来的选中的 sku 是一个数组
|
||||
*/
|
||||
const handleSelectionChange = (val: Sku[]) => {
|
||||
emit('selectionChange', val)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
@ -185,14 +407,13 @@ const generateTableData = (propertyList: any[]) => {
|
||||
valueName: v.name
|
||||
}))
|
||||
)
|
||||
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
|
||||
const buildList = build(propertyValues)
|
||||
const buildSkuList = build(propertyValues)
|
||||
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
||||
if (!validateData(propertyList)) {
|
||||
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
||||
formData.value!.skus = []
|
||||
}
|
||||
for (const item of buildList) {
|
||||
for (const item of buildSkuList) {
|
||||
const row = {
|
||||
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||
price: 0,
|
||||
@ -207,13 +428,13 @@ const generateTableData = (propertyList: any[]) => {
|
||||
subCommissionSecondPrice: 0
|
||||
}
|
||||
// 如果存在属性相同的 sku 则不做处理
|
||||
const index = formData.value!.skus.findIndex(
|
||||
const index = formData.value!.skus!.findIndex(
|
||||
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||
)
|
||||
if (index !== -1) {
|
||||
continue
|
||||
}
|
||||
formData.value.skus.push(row)
|
||||
formData.value!.skus!.push(row)
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,13 +442,13 @@ const generateTableData = (propertyList: any[]) => {
|
||||
* 生成 skus 前置校验
|
||||
*/
|
||||
const validateData = (propertyList: any[]) => {
|
||||
const skuPropertyIds = []
|
||||
formData.value.skus.forEach((sku) =>
|
||||
const skuPropertyIds: number[] = []
|
||||
formData.value!.skus!.forEach((sku) =>
|
||||
sku.properties
|
||||
?.map((property) => property.propertyId)
|
||||
.forEach((propertyId) => {
|
||||
if (skuPropertyIds.indexOf(propertyId) === -1) {
|
||||
skuPropertyIds.push(propertyId)
|
||||
?.forEach((propertyId) => {
|
||||
if (skuPropertyIds.indexOf(propertyId!) === -1) {
|
||||
skuPropertyIds.push(propertyId!)
|
||||
}
|
||||
})
|
||||
)
|
||||
@ -261,9 +482,9 @@ const build = (propertyValuesList: Property[][]) => {
|
||||
/** 监听属性列表,生成相关参数和表头 */
|
||||
watch(
|
||||
() => props.propertyList,
|
||||
(propertyList) => {
|
||||
(propertyList: PropertyAndValues[]) => {
|
||||
// 如果不是多规格则结束
|
||||
if (!formData.value.specType) {
|
||||
if (!formData.value!.specType) {
|
||||
return
|
||||
}
|
||||
// 如果当前组件作为批量添加数据使用,则重置表数据
|
||||
@ -295,13 +516,12 @@ watch(
|
||||
// name加属性项index区分属性值
|
||||
tableHeaders.value.push({ prop: `name${index}`, label: item.name })
|
||||
})
|
||||
|
||||
// 如果回显的 sku 属性和添加的属性一致则不处理
|
||||
if (validateData(propertyList)) {
|
||||
return
|
||||
}
|
||||
// 添加新属性没有属性值也不做处理
|
||||
if (propertyList.some((item) => item.values.length === 0)) {
|
||||
if (propertyList.some((item) => item.values!.length === 0)) {
|
||||
return
|
||||
}
|
||||
// 生成 table 数据,即 sku 列表
|
||||
@ -312,6 +532,10 @@ watch(
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
const activitySkuListRef = ref<InstanceType<typeof ElTable>>()
|
||||
const clearSelection = () => {
|
||||
activitySkuListRef.value.clearSelection()
|
||||
}
|
||||
// 暴露出生成 sku 方法,给添加属性成功时调用
|
||||
defineExpose({ generateTableData })
|
||||
defineExpose({ generateTableData, validateSku, clearSelection })
|
||||
</script>
|
||||
|
@ -2,14 +2,70 @@ import BasicInfoForm from './BasicInfoForm.vue'
|
||||
import DescriptionForm from './DescriptionForm.vue'
|
||||
import OtherSettingsForm from './OtherSettingsForm.vue'
|
||||
import ProductAttributes from './ProductAttributes.vue'
|
||||
import ProductAttributesAddForm from './ProductAttributesAddForm.vue'
|
||||
import ProductPropertyAddForm from './ProductPropertyAddForm.vue'
|
||||
import SkuList from './SkuList.vue'
|
||||
|
||||
import { Spu } from '@/api/mall/product/spu'
|
||||
|
||||
// TODO @puhui999:Properties 改成 Property 更合适?Property 在 Spu 中已存在避免冲突 PropertyAndValues
|
||||
interface PropertyAndValues {
|
||||
id: number
|
||||
name: string
|
||||
values?: PropertyAndValues[]
|
||||
}
|
||||
|
||||
interface RuleConfig {
|
||||
// 需要校验的字段
|
||||
// 例:name: 'name' 则表示校验 sku.name 的值
|
||||
// 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性
|
||||
name: string
|
||||
// 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。
|
||||
// 例:需要校验价格必须大于0.01
|
||||
// {
|
||||
// name:'price',
|
||||
// rule:(arg: number) => arg > 0.01
|
||||
// }
|
||||
rule: (arg: any) => boolean
|
||||
// 校验不通过时的消息提示
|
||||
message: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得商品的规格列表
|
||||
*
|
||||
* @param spu
|
||||
* @return PropertyAndValues 规格列表
|
||||
*/
|
||||
const getPropertyList = (spu: Spu): PropertyAndValues[] => {
|
||||
// 直接拿返回的 skus 属性逆向生成出 propertyList
|
||||
const properties: PropertyAndValues[] = []
|
||||
// 只有是多规格才处理
|
||||
if (spu.specType) {
|
||||
spu.skus?.forEach((sku) => {
|
||||
sku.properties?.forEach(({ propertyId, propertyName, valueId, valueName }) => {
|
||||
// 添加属性
|
||||
if (!properties?.some((item) => item.id === propertyId)) {
|
||||
properties.push({ id: propertyId!, name: propertyName!, values: [] })
|
||||
}
|
||||
// 添加属性值
|
||||
const index = properties?.findIndex((item) => item.id === propertyId)
|
||||
if (!properties[index].values?.some((value) => value.id === valueId)) {
|
||||
properties[index].values?.push({ id: valueId!, name: valueName! })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
export {
|
||||
BasicInfoForm,
|
||||
DescriptionForm,
|
||||
OtherSettingsForm,
|
||||
ProductAttributes,
|
||||
ProductAttributesAddForm,
|
||||
SkuList
|
||||
ProductPropertyAddForm,
|
||||
SkuList,
|
||||
getPropertyList,
|
||||
PropertyAndValues,
|
||||
RuleConfig
|
||||
}
|
||||
|
105
src/views/mall/product/spu/components/spu.data.ts
Normal file
105
src/views/mall/product/spu/components/spu.data.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
|
||||
export const basicInfoSchema = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '商品名称',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
label: '关键字',
|
||||
field: 'keyword'
|
||||
},
|
||||
{
|
||||
label: '商品简介',
|
||||
field: 'introduction'
|
||||
},
|
||||
{
|
||||
label: '商品分类',
|
||||
field: 'categoryId'
|
||||
},
|
||||
{
|
||||
label: '商品品牌',
|
||||
field: 'brandId'
|
||||
},
|
||||
{
|
||||
label: '商品封面图',
|
||||
field: 'picUrl'
|
||||
},
|
||||
{
|
||||
label: '商品轮播图',
|
||||
field: 'sliderPicUrls'
|
||||
},
|
||||
{
|
||||
label: '商品视频',
|
||||
field: 'videoUrl'
|
||||
},
|
||||
{
|
||||
label: '单位',
|
||||
field: 'unit',
|
||||
dictType: DICT_TYPE.PRODUCT_UNIT
|
||||
},
|
||||
{
|
||||
label: '规格类型',
|
||||
field: 'specType'
|
||||
},
|
||||
{
|
||||
label: '分销类型',
|
||||
field: 'subCommissionType'
|
||||
},
|
||||
{
|
||||
label: '物流模版',
|
||||
field: 'deliveryTemplateId'
|
||||
},
|
||||
{
|
||||
label: '商品属性列表',
|
||||
field: 'skus'
|
||||
}
|
||||
])
|
||||
export const descriptionSchema = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '商品详情',
|
||||
field: 'description'
|
||||
}
|
||||
])
|
||||
export const otherSettingsSchema = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '商品排序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
label: '赠送积分',
|
||||
field: 'giveIntegral'
|
||||
},
|
||||
{
|
||||
label: '虚拟销量',
|
||||
field: 'virtualSalesCount'
|
||||
},
|
||||
{
|
||||
label: '是否热卖推荐',
|
||||
field: 'recommendHot'
|
||||
},
|
||||
{
|
||||
label: '是否优惠推荐',
|
||||
field: 'recommendBenefit'
|
||||
},
|
||||
{
|
||||
label: '是否精品推荐',
|
||||
field: 'recommendBest'
|
||||
},
|
||||
{
|
||||
label: '是否新品推荐',
|
||||
field: 'recommendNew'
|
||||
},
|
||||
{
|
||||
label: '是否优品推荐',
|
||||
field: 'recommendGood'
|
||||
},
|
||||
{
|
||||
label: '赠送的优惠劵',
|
||||
field: 'giveCouponTemplateIds'
|
||||
},
|
||||
{
|
||||
label: '活动显示排序',
|
||||
field: 'activityOrders'
|
||||
}
|
||||
])
|
@ -8,18 +8,15 @@
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<!-- TODO @puhui999:品牌应该是数据下拉哈 -->
|
||||
<el-form-item label="品牌名称" prop="name">
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入品牌名称"
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 -->
|
||||
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? -->
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.categoryId"
|
||||
@ -29,6 +26,7 @@
|
||||
class="w-1/1"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
@change="nodeClick"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
@ -80,31 +78,54 @@
|
||||
/>
|
||||
</el-tabs>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<!-- TODO puhui:这几个属性哈,一行三个
|
||||
商品分类:服装鞋包/箱包
|
||||
商品市场价格:100.00
|
||||
成本价:0.00
|
||||
收藏:5
|
||||
虚拟销量:999 -->
|
||||
<el-table-column type="expand" width="30">
|
||||
<template #default="{ row }">
|
||||
<el-form class="demo-table-expand" inline label-position="left">
|
||||
<el-form-item label="市场价:">
|
||||
<span>{{ formatToFraction(row.marketPrice) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="成本价:">
|
||||
<span>{{ formatToFraction(row.costPrice) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="虚拟销量:">
|
||||
<span>{{ row.virtualSalesCount }}</span>
|
||||
</el-form-item>
|
||||
<el-form class="demo-table-expand" label-position="left">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="商品分类:">
|
||||
<span>{{ categoryString(row.categoryId) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="市场价:">
|
||||
<span>{{ formatToFraction(row.marketPrice) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成本价:">
|
||||
<span>{{ formatToFraction(row.costPrice) }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="收藏:">
|
||||
<!-- TODO 没有这个属性,暂时写死 5 个 -->
|
||||
<span>5</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="虚拟销量:">
|
||||
<span>{{ row.virtualSalesCount }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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" @click="imagePreview(row.picUrl)" class="w-30px h-30px" />
|
||||
<el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
|
||||
@ -143,8 +164,12 @@
|
||||
</el-table-column>
|
||||
<el-table-column align="center" fixed="right" label="操作" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<!-- TODO @puhui999:【详情】,可以后面点做哈 -->
|
||||
<el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openDetail">
|
||||
<el-button
|
||||
v-hasPermi="['product:spu:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openDetail(row.id)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<template v-if="queryParams.tabType === 4">
|
||||
@ -197,18 +222,20 @@
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" name="ProductSpu" setup>
|
||||
<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 { defaultProps, handleTree } from '@/utils/tree'
|
||||
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree'
|
||||
import { ProductSpuStatusEnum } from '@/utils/constants'
|
||||
import { formatToFraction } from '@/utils'
|
||||
import download from '@/utils/download'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
|
||||
defineOptions({ name: 'ProductSpu' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { currentRoute, push } = useRouter() // 路由跳转
|
||||
@ -256,12 +283,15 @@ const getTabsCount = async () => {
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0
|
||||
tabType: 0,
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}) // 查询参数
|
||||
const queryFormRef = ref() // 搜索的表单Ref
|
||||
|
||||
const handleTabClick = (tab: TabsPaneContext) => {
|
||||
queryParams.value.tabType = tab.paneName
|
||||
queryParams.value.tabType = tab.paneName as number
|
||||
getList()
|
||||
}
|
||||
|
||||
@ -362,18 +392,18 @@ const resetQuery = () => {
|
||||
const openForm = (id?: number) => {
|
||||
// 修改
|
||||
if (typeof id === 'number') {
|
||||
push('/product/productSpuEdit/' + id)
|
||||
push('/product/spu/edit/' + id)
|
||||
return
|
||||
}
|
||||
// 新增
|
||||
push('/product/productSpuAdd')
|
||||
push({ name: 'ProductSpuAdd' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看商品详情
|
||||
*/
|
||||
const openDetail = () => {
|
||||
message.alert('查看详情未完善!!!')
|
||||
const openDetail = (id?: number) => {
|
||||
push('/product/spu/detail/' + id)
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
@ -391,7 +421,7 @@ const handleExport = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
|
||||
// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
() => {
|
||||
@ -400,6 +430,24 @@ 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()
|
||||
|
@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="65%">
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:is-col="true"
|
||||
:rules="rules"
|
||||
:schema="allSchemas.formSchema"
|
||||
>
|
||||
<template #spuId>
|
||||
<el-button @click="spuSelectRef.open()">选择商品</el-button>
|
||||
<SpuAndSkuList
|
||||
ref="spuAndSkuListRef"
|
||||
:rule-config="ruleConfig"
|
||||
:spu-list="spuList"
|
||||
:spu-property-list-p="spuPropertyList"
|
||||
>
|
||||
<el-table-column align="center" label="拼团价格(元)" min-width="168">
|
||||
<template #default="{ row: sku }">
|
||||
<el-input-number
|
||||
v-model="sku.productConfig.activePrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</SpuAndSkuList>
|
||||
</template>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<SpuSelect ref="spuSelectRef" :isSelectSku="true" @confirm="selectSpu" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationactivity'
|
||||
import { CombinationProductVO } from '@/api/mall/promotion/combination/combinationactivity'
|
||||
import { allSchemas, rules } from './combinationActivity.data'
|
||||
import { SpuAndSkuList, SpuProperty, SpuSelect } from '@/views/mall/promotion/components'
|
||||
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { convertToInteger, formatToFraction } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'PromotionCombinationActivityForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
// ================= 商品选择相关 =================
|
||||
|
||||
const spuSelectRef = ref() // 商品和属性选择 Ref
|
||||
const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
|
||||
const spuList = ref<CombinationActivityApi.SpuExtension[]>([]) // 选择的 spu
|
||||
const spuPropertyList = ref<SpuProperty<CombinationActivityApi.SpuExtension>[]>([])
|
||||
const ruleConfig: RuleConfig[] = [
|
||||
{
|
||||
name: 'productConfig.activePrice',
|
||||
rule: (arg) => arg > 0.01,
|
||||
message: '商品拼团价格不能小于0.01 !!!'
|
||||
}
|
||||
]
|
||||
const selectSpu = (spuId: number, skuIds: number[]) => {
|
||||
formRef.value.setValues({ spuId })
|
||||
getSpuDetails(spuId, skuIds)
|
||||
}
|
||||
/**
|
||||
* 获取 SPU 详情
|
||||
*/
|
||||
const getSpuDetails = async (
|
||||
spuId: number,
|
||||
skuIds: number[] | undefined,
|
||||
products?: CombinationProductVO[]
|
||||
) => {
|
||||
const spuProperties: SpuProperty<CombinationActivityApi.SpuExtension>[] = []
|
||||
const res = (await ProductSpuApi.getSpuDetailList([
|
||||
spuId
|
||||
])) as CombinationActivityApi.SpuExtension[]
|
||||
if (res.length == 0) {
|
||||
return
|
||||
}
|
||||
spuList.value = []
|
||||
// 因为只能选择一个
|
||||
const spu = res[0]
|
||||
const selectSkus =
|
||||
typeof skuIds === 'undefined' ? spu?.skus : spu?.skus?.filter((sku) => skuIds.includes(sku.id!))
|
||||
selectSkus?.forEach((sku) => {
|
||||
let config: CombinationProductVO = {
|
||||
spuId: spu.id!,
|
||||
skuId: sku.id!,
|
||||
activePrice: 0
|
||||
}
|
||||
if (typeof products !== 'undefined') {
|
||||
const product = products.find((item) => item.skuId === sku.id)
|
||||
if (product) {
|
||||
// 分转元
|
||||
product.activePrice = formatToFraction(product.activePrice)
|
||||
}
|
||||
config = product || config
|
||||
}
|
||||
sku.productConfig = config
|
||||
})
|
||||
spu.skus = selectSkus as CombinationActivityApi.SkuExtension[]
|
||||
spuProperties.push({
|
||||
spuId: spu.id!,
|
||||
spuDetail: spu,
|
||||
propertyList: getPropertyList(spu)
|
||||
})
|
||||
spuList.value.push(spu)
|
||||
spuPropertyList.value = spuProperties
|
||||
}
|
||||
|
||||
// ================= end =================
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
await resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = (await CombinationActivityApi.getCombinationActivity(
|
||||
id
|
||||
)) as CombinationActivityApi.CombinationActivityVO
|
||||
await getSpuDetails(
|
||||
data.spuId!,
|
||||
data.products?.map((sku) => sku.skuId),
|
||||
data.products
|
||||
)
|
||||
formRef.value.setValues(data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = async () => {
|
||||
spuList.value = []
|
||||
spuPropertyList.value = []
|
||||
await nextTick()
|
||||
formRef.value.getElFormRef().resetFields()
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.getElFormRef().validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formRef.value.formModel as CombinationActivityApi.CombinationActivityVO
|
||||
const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
|
||||
products.forEach((item: CombinationProductVO) => {
|
||||
// 拼团价格元转分
|
||||
item.activePrice = convertToInteger(item.activePrice)
|
||||
})
|
||||
data.products = products
|
||||
if (formType.value === 'create') {
|
||||
await CombinationActivityApi.createCombinationActivity(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await CombinationActivityApi.updateCombinationActivity(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,151 @@
|
||||
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
import { dateFormatter, getNowDateTime } from '@/utils/formatTime'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
totalLimitCount: [required],
|
||||
singleLimitCount: [required],
|
||||
startTime: [required],
|
||||
endTime: [required],
|
||||
userSize: [required],
|
||||
totalNum: [required],
|
||||
successNum: [required],
|
||||
orderUserCount: [required],
|
||||
virtualGroup: [required],
|
||||
status: [required],
|
||||
limitDuration: [required]
|
||||
})
|
||||
|
||||
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '拼团名称',
|
||||
field: 'name',
|
||||
isSearch: true,
|
||||
isTable: false,
|
||||
form: {
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '活动时间',
|
||||
field: 'activityTime',
|
||||
formatter: dateFormatter,
|
||||
search: {
|
||||
show: true,
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'x',
|
||||
type: 'datetimerange',
|
||||
rangeSeparator: '至'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'x',
|
||||
type: 'datetimerange',
|
||||
rangeSeparator: '至'
|
||||
},
|
||||
value: [getNowDateTime().valueOf(), getNowDateTime().valueOf()],
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '参与人数',
|
||||
field: 'orderUserCount',
|
||||
isSearch: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
labelMessage: '参与人数不能少于两人',
|
||||
value: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '限制时长',
|
||||
field: 'limitDuration',
|
||||
isSearch: false,
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
labelMessage: '限制时长(小时)',
|
||||
componentProps: {
|
||||
placeholder: '请输入限制时长(小时)'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '总限购数量',
|
||||
field: 'totalLimitCount',
|
||||
isSearch: false,
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '单次限购数量',
|
||||
field: 'singleLimitCount',
|
||||
isSearch: false,
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '购买人数',
|
||||
field: 'userSize',
|
||||
isSearch: false,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
label: '开团组数',
|
||||
field: 'totalNum',
|
||||
isSearch: false,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
label: '成团组数',
|
||||
field: 'successNum',
|
||||
isSearch: false,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
label: '虚拟成团',
|
||||
field: 'virtualGroup',
|
||||
isSearch: false,
|
||||
isTable: false,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
label: '活动状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
label: '拼团商品',
|
||||
field: 'spuId',
|
||||
isSearch: false,
|
||||
form: {
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
field: 'action',
|
||||
isForm: false
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
117
src/views/mall/promotion/combination/activity/index.vue
Normal file
117
src/views/mall/promotion/combination/activity/index.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
|
||||
<!-- 新增等操作按钮 -->
|
||||
<template #actionMore>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:combination-activity:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
</Search>
|
||||
</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:combination-activity:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:combination-activity:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CombinationActivityForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { allSchemas } from './combinationActivity.data'
|
||||
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationactivity'
|
||||
import CombinationActivityForm from './CombinationActivityForm.vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
|
||||
defineOptions({ name: 'PromotionCombinationActivity' })
|
||||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: CombinationActivityApi.getCombinationActivityPage, // 分页接口
|
||||
delListApi: CombinationActivityApi.deleteCombinationActivity // 删除接口
|
||||
})
|
||||
// 获得表格的各种操作
|
||||
const { getList, setSearchParams } = tableMethods
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (id: number) => {
|
||||
tableMethods.delList(id, false)
|
||||
}
|
||||
|
||||
// TODO @puhui999:要不还是使用原生的 element plus 做。感觉 crud schema 复杂界面,做起来麻烦
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
/**
|
||||
TODO
|
||||
后面准备封装成一个函数来操作 tableColumns 重新排列:比如说需求是表单上商品选择是在后面的而列表展示的时候需要调到位置。
|
||||
封装效果支持批量操作,给出 field 和需要插入的位置,例:[{field:'spuId',index: 1}] 效果为把 field 为 spuId 的 column 移动到第一个位置
|
||||
*/
|
||||
// 处理一下表格列让商品往前
|
||||
const index = allSchemas.tableColumns.findIndex((item) => item.field === 'spuId')
|
||||
const column = cloneDeep(allSchemas.tableColumns[index])
|
||||
allSchemas.tableColumns.splice(index, 1)
|
||||
// 添加到开头
|
||||
allSchemas.tableColumns.unshift(column)
|
||||
getList()
|
||||
})
|
||||
</script>
|
112
src/views/mall/promotion/components/SpuAndSkuList.vue
Normal file
112
src/views/mall/promotion/components/SpuAndSkuList.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<el-table :data="spuData" :expand-row-keys="expandRowKeys" row-key="id">
|
||||
<el-table-column type="expand" width="30">
|
||||
<template #default="{ row }">
|
||||
<SkuList
|
||||
ref="skuListRef"
|
||||
:is-activity-component="true"
|
||||
:prop-form-data="spuPropertyList.find((item) => item.spuId === row.id)?.spuDetail"
|
||||
:property-list="spuPropertyList.find((item) => item.spuId === row.id)?.propertyList"
|
||||
:rule-config="ruleConfig"
|
||||
>
|
||||
<template #extension>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</SkuList>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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)" />
|
||||
</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 }">
|
||||
{{ formatToFraction(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" />
|
||||
</el-table>
|
||||
</template>
|
||||
<script generic="T extends Spu" lang="ts" setup>
|
||||
import { formatToFraction } from '@/utils'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { Spu } from '@/api/mall/product/spu'
|
||||
import { RuleConfig, SkuList } from '@/views/mall/product/spu/components'
|
||||
import { SpuProperty } from '@/views/mall/promotion/components/index'
|
||||
|
||||
defineOptions({ name: 'PromotionSpuAndSkuList' })
|
||||
|
||||
const props = defineProps<{
|
||||
spuList: T[] // TODO 为了方便兼容后续可能有需要展示多个 spu 的情况暂时保持,如果后续都是只操作一个 spu 的话则可更改为接受一个 spu 或保持
|
||||
ruleConfig: RuleConfig[]
|
||||
spuPropertyListP: SpuProperty<T>[]
|
||||
}>()
|
||||
|
||||
const spuData = ref<Spu[]>([]) // spu 详情数据列表
|
||||
const skuListRef = ref() // 商品属性列表Ref
|
||||
const spuPropertyList = ref<SpuProperty<T>[]>([]) // spuId 对应的 sku 的属性列表
|
||||
const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
|
||||
|
||||
/**
|
||||
* 获取所有 sku 活动配置
|
||||
*
|
||||
* @param extendedAttribute 在 sku 上扩展的属性,例:秒杀活动 sku 扩展属性 productConfig 请参考 seckillActivity.ts
|
||||
*/
|
||||
const getSkuConfigs = (extendedAttribute: string) => {
|
||||
skuListRef.value.validateSku()
|
||||
const seckillProducts = []
|
||||
spuPropertyList.value.forEach((item) => {
|
||||
item.spuDetail.skus.forEach((sku) => {
|
||||
seckillProducts.push(sku[extendedAttribute])
|
||||
})
|
||||
})
|
||||
return seckillProducts
|
||||
}
|
||||
// 暴露出给表单提交时使用
|
||||
defineExpose({ getSkuConfigs })
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 99999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
*/
|
||||
watch(
|
||||
() => props.spuList,
|
||||
(data) => {
|
||||
if (!data) return
|
||||
spuData.value = data as Spu[]
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 将传进来的值赋值给 skuList
|
||||
*/
|
||||
watch(
|
||||
() => props.spuPropertyListP,
|
||||
(data) => {
|
||||
if (!data) return
|
||||
spuPropertyList.value = data as SpuProperty<T>[]
|
||||
// 解决如果之前选择的是单规格 spu 的话后面选择多规格 sku 多规格属性信息不展示的问题。解决方法:让 SkuList 组件重新渲染(行折叠会干掉包含的组件展开时会重新加载)
|
||||
setTimeout(() => {
|
||||
expandRowKeys.value = data.map((item) => item.spuId)
|
||||
}, 200)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
298
src/views/mall/promotion/components/SpuSelect.vue
Normal file
298
src/views/mall/promotion/components/SpuSelect.vue
Normal file
@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :appendToBody="true" :title="dialogTitle" width="70%">
|
||||
<ContentWrap>
|
||||
<el-row :gutter="20" class="mb-10px">
|
||||
<el-col :span="6">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-tree-select
|
||||
v-model="queryParams.categoryId"
|
||||
:data="categoryList"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
class="w-1/1"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
end-placeholder="结束日期"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-table
|
||||
ref="spuListRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:expand-row-keys="expandRowKeys"
|
||||
row-key="id"
|
||||
@expand-change="expandChange"
|
||||
@selection-change="selectSpu"
|
||||
>
|
||||
<el-table-column v-if="isSelectSku" type="expand" width="30">
|
||||
<template #default>
|
||||
<SkuList
|
||||
v-if="isExpand"
|
||||
ref="skuListRef"
|
||||
:isComponent="true"
|
||||
:isDetail="true"
|
||||
:prop-form-data="spuData"
|
||||
:property-list="propertyList"
|
||||
@selection-change="selectSku"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<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)" />
|
||||
</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 }">
|
||||
{{ formatToFraction(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" />
|
||||
<el-table-column align="center" label="排序" min-width="70" prop="sort" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
/>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="confirm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getPropertyList, PropertyAndValues, SkuList } from '@/views/mall/product/spu/components'
|
||||
import { ElTable } from 'element-plus'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { formatToFraction } from '@/utils'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
defineOptions({ name: 'PromotionSpuSelect' })
|
||||
|
||||
const props = defineProps({
|
||||
// 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
|
||||
// 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
|
||||
isSelectSku: propTypes.bool.def(false) // 是否需要选择 sku 属性
|
||||
})
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0, // 默认获取上架的商品
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}) // 查询参数
|
||||
const propertyList = ref<PropertyAndValues[]>([]) // 商品属性列表
|
||||
const spuListRef = ref<InstanceType<typeof ElTable>>()
|
||||
const skuListRef = ref() // 商品属性选择 Ref
|
||||
const spuData = ref<ProductSpuApi.Spu>() // 商品详情
|
||||
const isExpand = ref(false) // 控制 SKU 列表显示
|
||||
const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
|
||||
|
||||
//============ 商品选择相关 ============
|
||||
const selectedSpuId = ref<number>(0) // 选中的商品 spuId
|
||||
const selectedSkuIds = ref<number[]>([]) // 选中的商品 skuIds
|
||||
const selectSku = (val: ProductSpuApi.Sku[]) => {
|
||||
if (selectedSpuId.value === 0) {
|
||||
message.warning('请先选择商品再选择相应的规格!!!')
|
||||
skuListRef.value.clearSelection()
|
||||
return
|
||||
}
|
||||
selectedSkuIds.value = val.map((sku) => sku.id!)
|
||||
}
|
||||
const selectSpu = (val: ProductSpuApi.Spu[]) => {
|
||||
if (val.length === 0) {
|
||||
selectedSpuId.value = 0
|
||||
return
|
||||
}
|
||||
// 只选择一个
|
||||
selectedSpuId.value = val.map((spu) => spu.id!)[0]
|
||||
// 切换选择 spu 如果有选择的 sku 则清空,确保选择的 sku 是对应的 spu 下面的
|
||||
if (selectedSkuIds.value.length > 0) {
|
||||
selectedSkuIds.value = []
|
||||
}
|
||||
// 如果大于1个
|
||||
if (val.length > 1) {
|
||||
// 清空选择
|
||||
spuListRef.value.clearSelection()
|
||||
// 变更为最后一次选择的
|
||||
spuListRef.value.toggleRowSelection(val.pop(), true)
|
||||
return
|
||||
}
|
||||
expandChange(val[0], val)
|
||||
}
|
||||
|
||||
// 计算商品属性
|
||||
const expandChange = async (row: ProductSpuApi.Spu, expandedRows?: ProductSpuApi.Spu[]) => {
|
||||
// 判断需要展开的 spuId === 选择的 spuId。如果选择了 A 就展开 A 的 skuList。如果选择了 A 手动展开 B 则阻断
|
||||
// 目的防止误选 sku
|
||||
if (selectedSpuId.value !== 0) {
|
||||
if (row.id !== selectedSpuId.value) {
|
||||
message.warning('你已选择商品请先取消')
|
||||
expandRowKeys.value = [selectedSpuId.value]
|
||||
return
|
||||
}
|
||||
// 如果以展开 skuList 则选择此对应的 spu 不需要重新获取渲染 skuList
|
||||
if (isExpand.value && spuData.value?.id === row.id) {
|
||||
return
|
||||
}
|
||||
}
|
||||
spuData.value = {}
|
||||
propertyList.value = []
|
||||
isExpand.value = false
|
||||
if (expandedRows?.length === 0) {
|
||||
// 如果展开个数为 0
|
||||
expandRowKeys.value = []
|
||||
return
|
||||
}
|
||||
// 获取 SPU 详情
|
||||
const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.Spu
|
||||
propertyList.value = getPropertyList(res)
|
||||
spuData.value = res
|
||||
isExpand.value = true
|
||||
expandRowKeys.value = [row.id!]
|
||||
}
|
||||
|
||||
// 确认选择时的触发事件
|
||||
const emits = defineEmits<{
|
||||
(e: 'confirm', spuId: number, skuIds?: number[]): void
|
||||
}>()
|
||||
/**
|
||||
* 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
|
||||
*/
|
||||
const confirm = () => {
|
||||
if (selectedSpuId.value === 0) {
|
||||
message.warning('没有选择任何商品')
|
||||
return
|
||||
}
|
||||
if (props.isSelectSku && selectedSkuIds.value.length === 0) {
|
||||
message.warning('没有选择任何商品属性')
|
||||
return
|
||||
}
|
||||
// 返回各自 id 列表
|
||||
props.isSelectSku
|
||||
? emits('confirm', selectedSpuId.value, selectedSkuIds.value)
|
||||
: emits('confirm', selectedSpuId.value)
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
selectedSpuId.value = 0
|
||||
selectedSkuIds.value = []
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = () => {
|
||||
dialogTitle.value = '商品选择'
|
||||
dialogVisible.value = true
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProductSpuApi.getSpuPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryParams.value = {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0, // 默认获取上架的商品
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
zIndex: 99999999,
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
|
||||
const categoryList = ref() // 分类树
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 获得分类树
|
||||
const data = await ProductCategoryApi.getCategoryList({})
|
||||
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||
})
|
||||
</script>
|
14
src/views/mall/promotion/components/index.ts
Normal file
14
src/views/mall/promotion/components/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import SpuSelect from './SpuSelect.vue'
|
||||
import SpuAndSkuList from './SpuAndSkuList.vue'
|
||||
import { PropertyAndValues } from '@/views/mall/product/spu/components'
|
||||
|
||||
type SpuProperty<T> = {
|
||||
spuId: number
|
||||
spuDetail: T
|
||||
propertyList: PropertyAndValues[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供商品活动商品选择通用组件
|
||||
*/
|
||||
export { SpuSelect, SpuAndSkuList, SpuProperty }
|
200
src/views/mall/promotion/coupon/index.vue
Executable file
200
src/views/mall/promotion/coupon/index.vue
Executable file
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="会员昵称" prop="nickname">
|
||||
<el-input
|
||||
v-model="queryParams.nickname"
|
||||
placeholder="请输入会员昵称"
|
||||
clearable
|
||||
@keyup="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
style="width: 240px"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @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-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<!-- <el-row :gutter="10" class="mb8">
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
|
||||
</el-row> -->
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<!-- Tab 选项:真正的内容在 Lab -->
|
||||
<el-tabs v-model="activeTab" type="card" @tab-change="onTabChange">
|
||||
<el-tab-pane
|
||||
v-for="tab in statusTabs"
|
||||
:key="tab.value"
|
||||
:label="tab.label"
|
||||
:name="tab.value"
|
||||
/>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 列表 -->
|
||||
<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">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="领取方式" align="center" 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="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="领取时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column
|
||||
label="使用时间"
|
||||
align="center"
|
||||
prop="useTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['promotion:coupon:delete']"
|
||||
><Icon icon="ep:delete" :size="12" class="mr-1px" />回收</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PromotionCoupon">
|
||||
import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { FormInstance } from 'element-plus'
|
||||
|
||||
// 消息弹窗
|
||||
const message = useMessage()
|
||||
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 优惠劵列表
|
||||
const list = ref([])
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
createTime: [],
|
||||
status: undefined
|
||||
})
|
||||
// Tab 筛选
|
||||
const activeTab = ref('all')
|
||||
|
||||
const statusTabs = reactive([
|
||||
{
|
||||
label: '全部',
|
||||
value: 'all'
|
||||
}
|
||||
])
|
||||
|
||||
const queryFormRef = ref<FormInstance | null>(null)
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
// 执行查询
|
||||
try {
|
||||
const data = await getCouponPage(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 handleDelete = async (row) => {
|
||||
const id = row.id
|
||||
|
||||
try {
|
||||
await message.confirm(
|
||||
'回收将会收回会员领取的待使用的优惠券,已使用的将无法回收,确定要回收所选优惠券吗?'
|
||||
)
|
||||
await deleteCoupon(id)
|
||||
getList()
|
||||
message.notifySuccess('回收成功')
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** tab 切换 */
|
||||
const onTabChange = (tabName) => {
|
||||
queryParams.status = tabName === 'all' ? undefined : tabName
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
// 设置 statuses 过滤
|
||||
for (const dict of getIntDictOptions(DICT_TYPE.PROMOTION_COUPON_STATUS)) {
|
||||
statusTabs.push({
|
||||
label: dict.label,
|
||||
value: dict.value as string
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
614
src/views/mall/promotion/couponTemplate/index.vue
Executable file
614
src/views/mall/promotion/couponTemplate/index.vue
Executable file
@ -0,0 +1,614 @@
|
||||
<template>
|
||||
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
v-show="showSearch"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-form-item label="优惠券名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入优惠劵名"
|
||||
clearable
|
||||
@keyup="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券类型" prop="discountType">
|
||||
<el-select v-model="queryParams.discountType" placeholder="请选择优惠券类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择优惠券状态" clearable>
|
||||
<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 label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
style="width: 240px"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @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-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['promotion:coupon-template:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" />新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
@click="$router.push('/promotion/coupon')"
|
||||
v-hasPermi="['promotion:coupon:query']"
|
||||
>
|
||||
<Icon icon="ep:operation" class="mr-5px" />会员优惠劵
|
||||
</el-button>
|
||||
</el-col>
|
||||
<!-- <right-toolbar v-model:showSearch="showSearch" @query-table="getList" /> -->
|
||||
</el-row>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="优惠券名称" align="center" prop="name" />
|
||||
<el-table-column label="优惠券类型" align="center" prop="discountType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="优惠金额 / 折扣"
|
||||
align="center"
|
||||
prop="discount"
|
||||
:formatter="discountFormat"
|
||||
/>
|
||||
<el-table-column label="发放数量" align="center" prop="totalCount" />
|
||||
<el-table-column
|
||||
label="剩余数量"
|
||||
align="center"
|
||||
prop="totalCount"
|
||||
:formatter="(row) => row.totalCount - row.takeCount"
|
||||
/>
|
||||
<el-table-column
|
||||
label="领取上限"
|
||||
align="center"
|
||||
prop="takeLimitCount"
|
||||
:formatter="takeLimitCountFormat"
|
||||
/>
|
||||
<el-table-column
|
||||
label="有效期限"
|
||||
align="center"
|
||||
prop="validityType"
|
||||
width="180"
|
||||
:formatter="validityTypeFormat"
|
||||
/>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['promotion:coupon-template:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" :size="12" class="mr-1px" />
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['promotion:coupon-template:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" :size="12" class="mr-1px" />
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<el-form-item label="优惠券名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入优惠券名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优惠券类型" prop="discountType">
|
||||
<el-radio-group v-model="form.discountType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="parseInt(dict.value)"
|
||||
>{{ dict.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.discountType === PromotionDiscountTypeEnum.PRICE.type"
|
||||
label="优惠券面额"
|
||||
prop="discountPrice"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.discountPrice"
|
||||
placeholder="请输入优惠金额,单位:元"
|
||||
style="width: 400px"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
/>
|
||||
元
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.discountType === PromotionDiscountTypeEnum.PERCENT.type"
|
||||
label="优惠券折扣"
|
||||
prop="discountPercent"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.discountPercent"
|
||||
placeholder="优惠券折扣不能小于 1 折,且不可大于 9.9 折"
|
||||
style="width: 400px"
|
||||
:precision="1"
|
||||
:min="1"
|
||||
:max="9.9"
|
||||
/>
|
||||
折
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.discountType === PromotionDiscountTypeEnum.PERCENT.type"
|
||||
label="最多优惠"
|
||||
prop="discountLimitPrice"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="form.discountLimitPrice"
|
||||
placeholder="请输入最多优惠"
|
||||
style="width: 400px"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
/>
|
||||
元
|
||||
</el-form-item>
|
||||
<el-form-item label="满多少元可以使用" prop="usePrice">
|
||||
<el-input-number
|
||||
v-model="form.usePrice"
|
||||
placeholder="无门槛请设为 0"
|
||||
style="width: 400px"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
/>
|
||||
元
|
||||
</el-form-item>
|
||||
<el-form-item label="领取方式" prop="takeType">
|
||||
<el-radio-group v-model="form.takeType">
|
||||
<el-radio :key="1" :label="1">直接领取</el-radio>
|
||||
<el-radio :key="2" :label="2">指定发放</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.takeType === 1" label="发放数量" prop="totalCount">
|
||||
<el-input-number
|
||||
v-model="form.totalCount"
|
||||
placeholder="发放数量,没有之后不能领取或发放,-1 为不限制"
|
||||
style="width: 400px"
|
||||
:precision="0"
|
||||
:min="-1"
|
||||
/>
|
||||
张
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.takeType === 1" label="每人限领个数" prop="takeLimitCount">
|
||||
<el-input-number
|
||||
v-model="form.takeLimitCount"
|
||||
placeholder="设置为 -1 时,可无限领取"
|
||||
style="width: 400px"
|
||||
:precision="0"
|
||||
:min="-1"
|
||||
/>
|
||||
张
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期类型" prop="validityType">
|
||||
<el-radio-group v-model="form.validityType">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="parseInt(dict.value)"
|
||||
>{{ dict.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.validityType === CouponTemplateValidityTypeEnum.DATE.type"
|
||||
label="固定日期"
|
||||
prop="validTimes"
|
||||
>
|
||||
<el-date-picker
|
||||
v-model="form.validTimes"
|
||||
style="width: 240px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
type="datetimerange"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.validityType === CouponTemplateValidityTypeEnum.TERM.type"
|
||||
label="领取日期"
|
||||
prop="fixedStartTerm"
|
||||
>
|
||||
第
|
||||
<el-input-number
|
||||
v-model="form.fixedStartTerm"
|
||||
placeholder="0 为今天生效"
|
||||
style="width: 165px"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
/>
|
||||
至
|
||||
<el-input-number
|
||||
v-model="form.fixedEndTerm"
|
||||
placeholder="请输入结束天数"
|
||||
style="width: 165px"
|
||||
:precision="0"
|
||||
:min="0"
|
||||
/>
|
||||
天有效
|
||||
</el-form-item>
|
||||
<el-form-item label="活动商品" prop="productScope">
|
||||
<el-radio-group v-model="form.productScope">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
|
||||
:key="dict.value"
|
||||
:label="parseInt(dict.value)"
|
||||
>{{ dict.label }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.productScope === PromotionProductScopeEnum.SPU.scope"
|
||||
prop="productSpuIds"
|
||||
>
|
||||
<el-select
|
||||
v-model="form.productSpuIds"
|
||||
placeholder="请选择活动商品"
|
||||
clearable
|
||||
size="small"
|
||||
multiple
|
||||
filterable
|
||||
style="width: 400px"
|
||||
>
|
||||
<el-option v-for="item in productSpus" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px"
|
||||
>¥{{ (item.minPrice / 100.0).toFixed(2) }}</span
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PromotionCouponTemplate">
|
||||
import {
|
||||
createCouponTemplate,
|
||||
updateCouponTemplate,
|
||||
deleteCouponTemplate,
|
||||
getCouponTemplate,
|
||||
getCouponTemplatePage,
|
||||
updateCouponTemplateStatus
|
||||
} from '@/api/mall/promotion/couponTemplate'
|
||||
import {
|
||||
CommonStatusEnum,
|
||||
CouponTemplateValidityTypeEnum,
|
||||
PromotionDiscountTypeEnum,
|
||||
PromotionProductScopeEnum
|
||||
} from '@/utils/constants'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { getSpuSimpleList } from '@/api/mall/product/spu'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import { FormInstance } from 'element-plus'
|
||||
|
||||
// 消息弹窗
|
||||
const message = useMessage()
|
||||
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
// 显示搜索条件
|
||||
const showSearch = ref(true)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 优惠劵列表
|
||||
const list = ref([])
|
||||
// 弹出层标题
|
||||
const title = ref('')
|
||||
// 是否显示弹出层
|
||||
const open = ref(false)
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
status: null,
|
||||
type: null,
|
||||
createTime: []
|
||||
})
|
||||
// 表单参数
|
||||
const form = ref<any>({})
|
||||
// 表单校验
|
||||
const rules = {
|
||||
name: [{ required: true, message: '优惠券名称不能为空', trigger: 'blur' }],
|
||||
discountType: [{ required: true, message: '优惠券类型不能为空', trigger: 'change' }],
|
||||
discountPrice: [{ required: true, message: '优惠券面额不能为空', trigger: 'blur' }],
|
||||
discountPercent: [{ required: true, message: '优惠券折扣不能为空', trigger: 'blur' }],
|
||||
discountLimitPrice: [{ required: true, message: '最多优惠不能为空', trigger: 'blur' }],
|
||||
usePrice: [{ required: true, message: '满多少元可以使用不能为空', trigger: 'blur' }],
|
||||
takeType: [{ required: true, message: '领取方式不能为空', trigger: 'change' }],
|
||||
totalCount: [{ required: true, message: '发放数量不能为空', trigger: 'blur' }],
|
||||
takeLimitCount: [{ required: true, message: '每人限领个数不能为空', trigger: 'blur' }],
|
||||
validityType: [{ required: true, message: '有效期类型不能为空', trigger: 'change' }],
|
||||
validTimes: [{ required: true, message: '固定日期不能为空', trigger: 'change' }],
|
||||
fixedStartTerm: [{ required: true, message: '开始领取天数不能为空', trigger: 'blur' }],
|
||||
fixedEndTerm: [{ required: true, message: '开始领取天数不能为空', trigger: 'blur' }],
|
||||
productScope: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }],
|
||||
productSpuIds: [{ required: true, message: '商品范围不能为空', trigger: 'blur' }]
|
||||
}
|
||||
// 商品列表
|
||||
const productSpus = ref([])
|
||||
const queryFormRef = ref<FormInstance | null>(null)
|
||||
const formRef = ref<FormInstance | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 执行查询
|
||||
const data = await getCouponTemplatePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
// 查询商品列表
|
||||
productSpus.value = await getSpuSimpleList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
discountType: PromotionDiscountTypeEnum.PRICE.type,
|
||||
discountPrice: undefined,
|
||||
discountPercent: undefined,
|
||||
discountLimitPrice: undefined,
|
||||
usePrice: undefined,
|
||||
takeType: 1,
|
||||
totalCount: undefined,
|
||||
takeLimitCount: undefined,
|
||||
validityType: CouponTemplateValidityTypeEnum.DATE.type,
|
||||
validTimes: [],
|
||||
validStartTime: undefined,
|
||||
validEndTime: undefined,
|
||||
fixedStartTerm: undefined,
|
||||
fixedEndTerm: undefined,
|
||||
productScope: PromotionProductScopeEnum.ALL.scope,
|
||||
productSpuIds: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef?.value?.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
open.value = true
|
||||
title.value = '添加优惠劵'
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: any) => {
|
||||
reset()
|
||||
const id = row.id
|
||||
try {
|
||||
const data = await getCouponTemplate(id)
|
||||
form.value = {
|
||||
...data,
|
||||
discountPrice: data.discountPrice !== undefined ? data.discountPrice / 100.0 : undefined,
|
||||
discountPercent: data.discountPercent !== undefined ? data.discountPercent / 10.0 : undefined,
|
||||
discountLimitPrice:
|
||||
data.discountLimitPrice !== undefined ? data.discountLimitPrice / 100.0 : undefined,
|
||||
usePrice: data.usePrice !== undefined ? data.usePrice / 100.0 : undefined,
|
||||
validTimes: [data.validStartTime, data.validEndTime]
|
||||
}
|
||||
open.value = true
|
||||
title.value = '修改优惠劵'
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
|
||||
// 金额相关字段的缩放
|
||||
let data = {
|
||||
...form.value,
|
||||
discountPrice:
|
||||
form.value.discountPrice !== undefined ? form.value.discountPrice * 100 : undefined,
|
||||
discountPercent:
|
||||
form.value.discountPercent !== undefined ? form.value.discountPercent * 10 : undefined,
|
||||
discountLimitPrice:
|
||||
form.value.discountLimitPrice !== undefined ? form.value.discountLimitPrice * 100 : undefined,
|
||||
usePrice: form.value.usePrice !== undefined ? form.value.usePrice * 100 : undefined,
|
||||
validStartTime:
|
||||
form.value.validTimes && form.value.validTimes.length === 2
|
||||
? form.value.validTimes[0]
|
||||
: undefined,
|
||||
validEndTime:
|
||||
form.value.validTimes && form.value.validTimes.length === 2
|
||||
? form.value.validTimes[1]
|
||||
: undefined
|
||||
}
|
||||
|
||||
// 修改的提交
|
||||
if (form.value.id != null) {
|
||||
try {
|
||||
await updateCouponTemplate(data)
|
||||
message.success('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
} catch {}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await createCouponTemplate(data)
|
||||
message.success('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 优惠劵模板状态修改 */
|
||||
const handleStatusChange = async (row: any) => {
|
||||
// 此时,row 已经变成目标状态了,所以可以直接提交请求和提示
|
||||
let text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
|
||||
|
||||
try {
|
||||
await message.confirm('确认要"' + text + '""' + row.name + '"优惠劵吗?')
|
||||
await updateCouponTemplateStatus(row.id, row.status)
|
||||
message.success(text + '成功')
|
||||
} catch {
|
||||
// 异常时,需要将 row.status 状态重置回之前的
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: any) => {
|
||||
const id = row.id
|
||||
try {
|
||||
await message.confirm('是否确认删除优惠劵编号为"' + id + '"的数据项?')
|
||||
await deleteCouponTemplate(id)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// 格式化【优惠金额/折扣】
|
||||
const discountFormat = (row: any) => {
|
||||
if (row.discountType === PromotionDiscountTypeEnum.PRICE.type) {
|
||||
return `¥${(row.discountPrice / 100.0).toFixed(2)}`
|
||||
}
|
||||
if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
|
||||
return `¥${(row.discountPrice / 100.0).toFixed(2)}`
|
||||
}
|
||||
return '未知【' + row.discountType + '】'
|
||||
}
|
||||
|
||||
// 格式化【领取上限】
|
||||
const takeLimitCountFormat = (row: any) => {
|
||||
if (row.takeLimitCount === -1) {
|
||||
return '无领取限制'
|
||||
}
|
||||
return `${row.takeLimitCount} 张/人`
|
||||
}
|
||||
|
||||
// 格式化【有效期限】
|
||||
const validityTypeFormat = (row: any) => {
|
||||
if (row.validityType === CouponTemplateValidityTypeEnum.DATE.type) {
|
||||
return `${formatDate(row.validStartTime)} 至 ${formatDate(row.validEndTime)}`
|
||||
}
|
||||
if (row.validityType === CouponTemplateValidityTypeEnum.TERM.type) {
|
||||
return `领取后第 ${row.fixedStartTerm} - ${row.fixedEndTerm} 天内可用`
|
||||
}
|
||||
return '未知【' + row.validityType + '】'
|
||||
}
|
||||
</script>
|
@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="65%">
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:isCol="true"
|
||||
:rules="rules"
|
||||
:schema="allSchemas.formSchema"
|
||||
>
|
||||
<!-- 先选择 -->
|
||||
<template #spuId>
|
||||
<el-button @click="spuSelectRef.open()">选择商品</el-button>
|
||||
<SpuAndSkuList
|
||||
ref="spuAndSkuListRef"
|
||||
:rule-config="ruleConfig"
|
||||
:spu-list="spuList"
|
||||
:spu-property-list-p="spuPropertyList"
|
||||
>
|
||||
<el-table-column align="center" label="秒杀库存" min-width="168">
|
||||
<template #default="{ row: sku }">
|
||||
<el-input-number v-model="sku.productConfig.stock" :min="0" class="w-100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="秒杀价格(元)" min-width="168">
|
||||
<template #default="{ row: sku }">
|
||||
<el-input-number
|
||||
v-model="sku.productConfig.seckillPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-100%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</SpuAndSkuList>
|
||||
</template>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<SpuSelect ref="spuSelectRef" :isSelectSku="true" @confirm="selectSpu" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SpuAndSkuList, SpuProperty, SpuSelect } from '../../components'
|
||||
import { allSchemas, rules } from './seckillActivity.data'
|
||||
|
||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||
import { SeckillProductVO } from '@/api/mall/promotion/seckill/seckillActivity'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
|
||||
import { convertToInteger, formatToFraction } from '@/utils'
|
||||
|
||||
defineOptions({ name: 'PromotionSeckillActivityForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
// ================= 商品选择相关 =================
|
||||
|
||||
const spuSelectRef = ref() // 商品和属性选择 Ref
|
||||
const spuAndSkuListRef = ref() // sku 秒杀配置组件Ref
|
||||
const ruleConfig: RuleConfig[] = [
|
||||
{
|
||||
name: 'productConfig.stock',
|
||||
rule: (arg) => arg > 1,
|
||||
message: '商品秒杀库存必须大于 1 !!!'
|
||||
},
|
||||
{
|
||||
name: 'productConfig.seckillPrice',
|
||||
rule: (arg) => arg > 0.01,
|
||||
message: '商品秒杀价格必须大于 0.01 !!!'
|
||||
}
|
||||
]
|
||||
const spuList = ref<SeckillActivityApi.SpuExtension[]>([]) // 选择的 spu
|
||||
const spuPropertyList = ref<SpuProperty<SeckillActivityApi.SpuExtension>[]>([])
|
||||
const selectSpu = (spuId: number, skuIds: number[]) => {
|
||||
formRef.value.setValues({ spuId })
|
||||
getSpuDetails(spuId, skuIds)
|
||||
}
|
||||
/**
|
||||
* 获取 SPU 详情
|
||||
*/
|
||||
const getSpuDetails = async (
|
||||
spuId: number,
|
||||
skuIds: number[] | undefined,
|
||||
products?: SeckillProductVO[]
|
||||
) => {
|
||||
const spuProperties: SpuProperty<SeckillActivityApi.SpuExtension>[] = []
|
||||
const res = (await ProductSpuApi.getSpuDetailList([spuId])) as SeckillActivityApi.SpuExtension[]
|
||||
if (res.length == 0) {
|
||||
return
|
||||
}
|
||||
spuList.value = []
|
||||
// 因为只能选择一个
|
||||
const spu = res[0]
|
||||
const selectSkus =
|
||||
typeof skuIds === 'undefined' ? spu?.skus : spu?.skus?.filter((sku) => skuIds.includes(sku.id!))
|
||||
selectSkus?.forEach((sku) => {
|
||||
let config: SeckillActivityApi.SeckillProductVO = {
|
||||
skuId: sku.id!,
|
||||
stock: 0,
|
||||
seckillPrice: 0
|
||||
}
|
||||
if (typeof products !== 'undefined') {
|
||||
const product = products.find((item) => item.skuId === sku.id)
|
||||
if (product) {
|
||||
// 分转元
|
||||
product.seckillPrice = formatToFraction(product.seckillPrice)
|
||||
}
|
||||
config = product || config
|
||||
}
|
||||
sku.productConfig = config
|
||||
})
|
||||
spu.skus = selectSkus as SeckillActivityApi.SkuExtension[]
|
||||
spuProperties.push({
|
||||
spuId: spu.id!,
|
||||
spuDetail: spu,
|
||||
propertyList: getPropertyList(spu)
|
||||
})
|
||||
spuList.value.push(spu)
|
||||
spuPropertyList.value = spuProperties
|
||||
}
|
||||
|
||||
// ================= end =================
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
await resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = (await SeckillActivityApi.getSeckillActivity(
|
||||
id
|
||||
)) as SeckillActivityApi.SeckillActivityVO
|
||||
await getSpuDetails(
|
||||
data.spuId!,
|
||||
data.products?.map((sku) => sku.skuId),
|
||||
data.products
|
||||
)
|
||||
formRef.value.setValues(data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = async () => {
|
||||
spuList.value = []
|
||||
spuPropertyList.value = []
|
||||
await nextTick()
|
||||
formRef.value.getElFormRef().resetFields()
|
||||
}
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.getElFormRef().validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formRef.value.formModel as SeckillActivityApi.SeckillActivityVO
|
||||
const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
|
||||
products.forEach((item: SeckillProductVO) => {
|
||||
// 秒杀价格元转分
|
||||
item.seckillPrice = convertToInteger(item.seckillPrice)
|
||||
})
|
||||
// 获取秒杀商品配置
|
||||
data.products = products
|
||||
if (formType.value === 'create') {
|
||||
await SeckillActivityApi.createSeckillActivity(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SeckillActivityApi.updateSeckillActivity(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.demo-table-expand {
|
||||
padding-left: 42px;
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
width: 82px;
|
||||
font-weight: bold;
|
||||
color: #99a9bf;
|
||||
}
|
||||
}
|
||||
</style>
|
137
src/views/mall/promotion/seckill/activity/index.vue
Normal file
137
src/views/mall/promotion/seckill/activity/index.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
|
||||
<!-- 新增等操作按钮 -->
|
||||
<template #actionMore>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-activity:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
</Search>
|
||||
</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>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SeckillActivityForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { allSchemas } from './seckillActivity.data'
|
||||
import { getListAllSimple } from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
|
||||
import SeckillActivityForm from './SeckillActivityForm.vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
|
||||
defineOptions({ name: 'PromotionSeckillActivity' })
|
||||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: SeckillActivityApi.getSeckillActivityPage, // 分页接口
|
||||
delListApi: SeckillActivityApi.deleteSeckillActivity // 删除接口
|
||||
})
|
||||
// 获得表格的各种操作
|
||||
const { getList, setSearchParams } = tableMethods
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (id: number) => {
|
||||
tableMethods.delList(id, false)
|
||||
}
|
||||
/** 商品图预览 */
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
createImageViewer({
|
||||
urlList: [imgUrl]
|
||||
})
|
||||
}
|
||||
const configList = ref([]) // 时段配置精简列表
|
||||
const convertSeckillConfigNames = computed(
|
||||
() => (row) =>
|
||||
configList.value
|
||||
?.filter((item) => row.configIds.includes(item.id))
|
||||
?.map((config) => config.name)
|
||||
)
|
||||
|
||||
const expandChange = (row, expandedRows) => {
|
||||
// TODO puhui:等 CRUD 完事后弄
|
||||
console.log(row, expandedRows)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
/*
|
||||
TODO
|
||||
后面准备封装成一个函数来操作 tableColumns 重新排列:比如说需求是表单上商品选择是在后面的而列表展示的时候需要调到位置。
|
||||
封装效果支持批量操作,给出 field 和需要插入的位置,例:[{field:'spuId',index: 1}] 效果为把 field 为 spuId 的 column 移动到第一个位置
|
||||
*/
|
||||
// 处理一下表格列让商品往前
|
||||
const index = allSchemas.tableColumns.findIndex((item) => item.field === 'spuId')
|
||||
const column = cloneDeep(allSchemas.tableColumns[index])
|
||||
allSchemas.tableColumns.splice(index, 1)
|
||||
// 添加到开头
|
||||
allSchemas.tableColumns.unshift(column)
|
||||
await getList()
|
||||
configList.value = await getListAllSimple()
|
||||
})
|
||||
</script>
|
@ -0,0 +1,259 @@
|
||||
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
|
||||
import { getListAllSimple } from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
spuId: [required],
|
||||
name: [required],
|
||||
startTime: [required],
|
||||
endTime: [required],
|
||||
sort: [required],
|
||||
configIds: [required],
|
||||
totalLimitCount: [required],
|
||||
singleLimitCount: [required],
|
||||
totalStock: [required]
|
||||
})
|
||||
|
||||
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '秒杀活动名称',
|
||||
field: 'name',
|
||||
isSearch: true,
|
||||
form: {
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '活动开始时间',
|
||||
field: 'startTime',
|
||||
formatter: dateFormatter2,
|
||||
isSearch: true,
|
||||
search: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
type: 'daterange'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
valueFormat: 'x'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '活动结束时间',
|
||||
field: 'endTime',
|
||||
formatter: dateFormatter2,
|
||||
isSearch: true,
|
||||
search: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
type: 'daterange'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
valueFormat: 'x'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀时段',
|
||||
field: 'configIds',
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
multiple: true,
|
||||
optionsAlias: {
|
||||
labelField: 'name',
|
||||
valueField: 'id'
|
||||
}
|
||||
},
|
||||
api: getListAllSimple
|
||||
},
|
||||
table: {
|
||||
width: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '新增订单数',
|
||||
field: 'orderCount',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '付款人数',
|
||||
field: 'userCount',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '订单实付金额',
|
||||
field: 'totalPrice',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '总限购数量',
|
||||
field: 'totalLimitCount',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '单次限够数量',
|
||||
field: 'singleLimitCount',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '排序',
|
||||
field: 'sort',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀库存',
|
||||
field: 'stock',
|
||||
isForm: false,
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
},
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀总库存',
|
||||
field: 'totalStock',
|
||||
isForm: false,
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀活动商品',
|
||||
field: 'spuId',
|
||||
isTable: true,
|
||||
isSearch: false,
|
||||
form: {
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
field: 'createTime',
|
||||
formatter: dateFormatter,
|
||||
search: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
type: 'daterange',
|
||||
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
|
||||
}
|
||||
},
|
||||
isForm: false,
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isForm: false,
|
||||
isSearch: true,
|
||||
form: {
|
||||
component: 'Radio'
|
||||
},
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
field: 'remark',
|
||||
isSearch: false,
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
field: 'action',
|
||||
isForm: false,
|
||||
table: {
|
||||
width: 120,
|
||||
fixed: 'right'
|
||||
}
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||
<Form ref="formRef" v-loading="formLoading" :rules="rules" :schema="allSchemas.formSchema" />
|
||||
<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="SeckillConfigForm" setup>
|
||||
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
import { allSchemas, rules } from './seckillConfig.data'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await SeckillConfigApi.getSeckillConfig(id)
|
||||
data.sliderPicUrls = data['sliderPicUrls']?.map((item) => ({
|
||||
url: item
|
||||
}))
|
||||
formRef.value.setValues(data)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.getElFormRef().validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
// 处理轮播图列表
|
||||
const data = formRef.value.formModel as SeckillConfigApi.SeckillConfigVO
|
||||
const cloneData = cloneDeep(data)
|
||||
const newSliderPicUrls = []
|
||||
cloneData.sliderPicUrls.forEach((item) => {
|
||||
// 如果是前端选的图
|
||||
typeof item === 'object' ? newSliderPicUrls.push(item.url) : newSliderPicUrls.push(item)
|
||||
})
|
||||
cloneData.sliderPicUrls = newSliderPicUrls
|
||||
if (formType.value === 'create') {
|
||||
await SeckillConfigApi.createSeckillConfig(cloneData)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SeckillConfigApi.updateSeckillConfig(cloneData)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
138
src/views/mall/promotion/seckill/config/index.vue
Normal file
138
src/views/mall/promotion/seckill/config/index.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams">
|
||||
<!-- 新增等操作按钮 -->
|
||||
<template #actionMore>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-config:create']"
|
||||
plain
|
||||
type="primary"
|
||||
@click="openForm('create')"
|
||||
>
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
</template>
|
||||
</Search>
|
||||
</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 #sliderPicUrls="{ row }">
|
||||
<el-image
|
||||
v-for="(item, index) in row.sliderPicUrls"
|
||||
:key="index"
|
||||
:src="item"
|
||||
class="w-60px h-60px mr-10px"
|
||||
@click="imagePreview(row.sliderPicUrls)"
|
||||
/>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(row)"
|
||||
/>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-config:update']"
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['promotion:seckill-config:delete']"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SeckillConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" name="PromotionSeckillConfig" setup>
|
||||
import { allSchemas } from './seckillConfig.data'
|
||||
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
|
||||
import SeckillConfigForm from './SeckillConfigForm.vue'
|
||||
import { createImageViewer } from '@/components/ImageViewer'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { isArray } from '@/utils/is'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: SeckillConfigApi.getSeckillConfigPage, // 分页接口
|
||||
delListApi: SeckillConfigApi.deleteSeckillConfig // 删除接口
|
||||
})
|
||||
// 获得表格的各种操作
|
||||
const { getList, setSearchParams } = tableMethods
|
||||
|
||||
/** 轮播图预览预览 */
|
||||
const imagePreview = (args) => {
|
||||
const urlList = []
|
||||
if (isArray(args)) {
|
||||
args.forEach((item) => {
|
||||
urlList.push(item)
|
||||
})
|
||||
} else {
|
||||
urlList.push(args)
|
||||
}
|
||||
createImageViewer({
|
||||
urlList
|
||||
})
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (id: number) => {
|
||||
tableMethods.delList(id, false)
|
||||
}
|
||||
|
||||
/** 修改用户状态 */
|
||||
const handleStatusChange = async (row: SeckillConfigApi.SeckillConfigVO) => {
|
||||
try {
|
||||
// 修改状态的二次确认
|
||||
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
|
||||
await message.confirm('确认要"' + text + '""' + row.name + '?')
|
||||
// 发起修改状态
|
||||
await SeckillConfigApi.updateSeckillConfigStatus(row.id, row.status)
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {
|
||||
// 取消后,进行恢复按钮
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||
}
|
||||
}
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
@ -0,0 +1,82 @@
|
||||
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
startTime: [required],
|
||||
endTime: [required],
|
||||
picUrl: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '秒杀时段名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
label: '开始时间点',
|
||||
field: 'startTime',
|
||||
isSearch: false,
|
||||
search: {
|
||||
component: 'TimePicker'
|
||||
},
|
||||
form: {
|
||||
component: 'TimePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '结束时间点',
|
||||
field: 'endTime',
|
||||
isSearch: false,
|
||||
search: {
|
||||
component: 'TimePicker'
|
||||
},
|
||||
form: {
|
||||
component: 'TimePicker',
|
||||
componentProps: {
|
||||
valueFormat: 'HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '秒杀轮播图',
|
||||
field: 'sliderPicUrls',
|
||||
isSearch: false,
|
||||
form: {
|
||||
component: 'UploadImgs'
|
||||
},
|
||||
table: {
|
||||
width: 300
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
form: {
|
||||
component: 'Radio'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
isSearch: false,
|
||||
formatter: dateFormatter
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
field: 'action',
|
||||
isForm: false
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
@ -89,7 +89,7 @@
|
||||
<el-table border style="width: 100%" :data="formData.templateFree">
|
||||
<el-table-column align="center" label="区域">
|
||||
<template #default="{ row }">
|
||||
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
|
||||
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
|
||||
<el-tree-select
|
||||
v-model="row.areaIds"
|
||||
multiple
|
||||
@ -137,7 +137,7 @@
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
|
||||
import { defaultProps } from '@/utils/tree'
|
||||
@ -171,7 +171,10 @@ const formRules = reactive({
|
||||
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const areaCache = ref([]) //由于区域节点懒加载,已选区域节点需要缓存展示
|
||||
const areaCache = ref([]) // 由于区域节点懒加载,已选区域节点需要缓存展示
|
||||
// TODO @jason:配送的时候,只允许选择省市级别,不允许选择区;如果这样的话,是不是打开弹窗,直接把城市都请求过来;
|
||||
// TODO @jaosn:因为只有省市两级,感觉就不用特殊做全国逻辑;选择全国,就默认把子节点都选择上;另外,选择父节点,要把子节点选中哈;
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
@ -204,9 +207,9 @@ const open = async (type: string, id?: number) => {
|
||||
}
|
||||
item.freePrice = fenToYuan(item.freePrice)
|
||||
})
|
||||
//已选的区域节点
|
||||
// 已选的区域节点
|
||||
const areaIds = chargeAreaIds.concat(freeAreaIds)
|
||||
//区域节点,懒加载方式。 已选节点需要缓存展示
|
||||
// 区域节点,懒加载方式。已选节点需要缓存展示
|
||||
areaCache.value = await getAreaListByIds(areaIds.join(','))
|
||||
}
|
||||
} finally {
|
||||
@ -226,8 +229,9 @@ const submitForm = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
|
||||
// 前端价格以元展示,提交到后端。用分计算
|
||||
// TODO @jason:不能直接这样改,要复制出来改。不然后端操作失败,数据已经被改了
|
||||
data.templateCharge.forEach((item) => {
|
||||
//前端价格以元展示,提交到后端。用分计算
|
||||
item.startPrice = yuanToFen(item.startPrice)
|
||||
item.extraPrice = yuanToFen(item.extraPrice)
|
||||
})
|
||||
@ -248,6 +252,7 @@ const submitForm = async () => {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
@ -269,6 +274,7 @@ const resetForm = () => {
|
||||
columnTitle.value = columnTitleMap.get(1)
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 配送计费方法改变 */
|
||||
const changeChargeMode = (chargeMode: number) => {
|
||||
columnTitle.value = columnTitleMap.get(chargeMode)
|
||||
@ -276,6 +282,24 @@ const changeChargeMode = (chargeMode: number) => {
|
||||
const defaultArea = [{ id: 1, name: '全国', disabled: false }]
|
||||
|
||||
/** 初始化数据 */
|
||||
// TODO @jason:是不是不用写这样一个初始化方法,columnTitleMap 直接就可以了呀
|
||||
// const columnTitleMap = {
|
||||
// '1': {
|
||||
// startCountTitle: '首件',
|
||||
// extraCountTitle: '续件',
|
||||
// freeCountTitle: '包邮件数'
|
||||
// },
|
||||
// '2': {
|
||||
// startCountTitle: '首件重量(kg)',
|
||||
// extraCountTitle: '续件重量(kg)',
|
||||
// freeCountTitle: '包邮重量(kg)'
|
||||
// },
|
||||
// '3': {
|
||||
// startCountTitle: '首件体积(m³)',
|
||||
// extraCountTitle: '续件体积(m³)',
|
||||
// freeCountTitle: '包邮体积(m³)'
|
||||
// }
|
||||
// }
|
||||
const initData = async () => {
|
||||
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
|
||||
// formLoading.value = true
|
||||
@ -286,7 +310,7 @@ const initData = async () => {
|
||||
// } finally {
|
||||
// formLoading.value = false
|
||||
// }
|
||||
//表头标题和计费方式的映射
|
||||
// 表头标题和计费方式的映射
|
||||
columnTitleMap.set(1, {
|
||||
startCountTitle: '首件',
|
||||
extraCountTitle: '续件',
|
||||
@ -320,6 +344,7 @@ const loadChargeArea = async (node, resolve) => {
|
||||
const item = data[0]
|
||||
if (areaIds.includes(item.id)) {
|
||||
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||
// TODO @jason:先不做这个功能哈。
|
||||
//item.disabled = true
|
||||
}
|
||||
resolve(data)
|
||||
@ -357,10 +382,11 @@ const loadFreeArea = async (node, resolve) => {
|
||||
} else {
|
||||
const id = node.data.id
|
||||
const data = await getChildrenArea(id)
|
||||
//已选区域需要禁止再次选择
|
||||
// 已选区域需要禁止再次选择
|
||||
data.forEach((item) => {
|
||||
if (areaIds.includes(item.id)) {
|
||||
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||
// TODO @jason:先不做这个功能哈。
|
||||
//item.disabled = true
|
||||
}
|
||||
})
|
||||
@ -378,11 +404,13 @@ const addChargeArea = () => {
|
||||
extraPrice: 1
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除计费区域 */
|
||||
const deleteChargeArea = (index) => {
|
||||
const data = formData.value
|
||||
data.templateCharge.splice(index, 1)
|
||||
}
|
||||
|
||||
/** 添加包邮区域 */
|
||||
const addFreeArea = () => {
|
||||
const data = formData.value
|
||||
@ -392,6 +420,7 @@ const addFreeArea = () => {
|
||||
freePrice: 1
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除包邮区域 */
|
||||
const deleteFreeArea = (index) => {
|
||||
const data = formData.value
|
||||
|
@ -92,12 +92,14 @@
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ExpressTemplateForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="DeliveryExpressTemplate">
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
|
||||
import ExpressTemplateForm from './ExpressTemplateForm.vue'
|
||||
|
||||
defineOptions({ name: 'DeliveryExpressTemplate' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const total = ref(0) // 列表的总页数
|
||||
@ -110,6 +112,7 @@ const queryParams = reactive({
|
||||
chargeMode: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
|
287
src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue
Normal file
287
src/views/mall/trade/delivery/pickUpStore/PickUpStoreForm.vue
Normal file
@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="60%">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店 logo" prop="logo">
|
||||
<UploadImg v-model="formData.logo" :limit="1" :is-show-tip="false" />
|
||||
<div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入门店名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店手机" prop="phone">
|
||||
<el-input v-model="formData.phone" placeholder="请输入门店手机" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="门店简介" prop="introduction">
|
||||
<el-input
|
||||
v-model="formData.introduction"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
placeholder="请输入门店简介"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店所在地区" prop="areaId">
|
||||
<el-cascader v-model="formData.areaId" :options="areaList" :props="areaTreeProps" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="门店详细地址" prop="detailAddress">
|
||||
<el-input v-model="formData.detailAddress" placeholder="请输入门店详细地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="营业开始时间" prop="openingTime">
|
||||
<el-time-select
|
||||
v-model="formData.openingTime"
|
||||
:max-time="formData.closingTime"
|
||||
placeholder="开始时间"
|
||||
start="08:30"
|
||||
step="00:15"
|
||||
end="23:30"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="营业结束时间" prop="closingTime">
|
||||
<el-time-select
|
||||
v-model="formData.closingTime"
|
||||
:min-time="formData.openingTime"
|
||||
placeholder="结束时间"
|
||||
start="08:30"
|
||||
step="00:15"
|
||||
end="23:30"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="经度" prop="longitude">
|
||||
<el-input v-model="formData.longitude" placeholder="请输入门店经度" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="纬度" prop="latitude">
|
||||
<el-input v-model="formData.latitude" placeholder="请输入门店纬度" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="获取经纬度">
|
||||
<el-button type="primary" @click="mapDialogVisible.value = true">获取</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
<el-dialog
|
||||
v-model="mapDialogVisible"
|
||||
title="获取经纬度"
|
||||
append-to-body
|
||||
width="500px"
|
||||
class="mapBox"
|
||||
>
|
||||
<iframe id="mapPage" width="100%" height="100%" frameborder="0" :src="tencentLbsUrl"></iframe>
|
||||
</el-dialog>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { getAreaTree } from '@/api/system/area'
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const mapDialogVisible = ref(false) // 地图弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
phone: '',
|
||||
logo: '',
|
||||
detailAddress: '',
|
||||
introduction: '',
|
||||
areaId: 0,
|
||||
openingTime: undefined,
|
||||
closingTime: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '门店名称不能为空', trigger: 'blur' }],
|
||||
logo: [{ required: true, message: '门店 logo 不能为空', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '门店手机不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
areaId: [{ required: true, message: '门店所在区域不能为空', trigger: 'blur' }],
|
||||
detailAddress: [{ required: true, message: '门店详细地址不能为空', trigger: 'blur' }],
|
||||
openingTime: [{ required: true, message: '营业开始时间不能为空', trigger: 'blur' }],
|
||||
closingTime: [{ required: true, message: '营业结束时间不能为空', trigger: 'blur' }],
|
||||
latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }],
|
||||
longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const areaTreeProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
emitPath: false
|
||||
}
|
||||
const areaList = ref() // 区域树
|
||||
const tencentLbsUrl = ref('') // 腾讯位置服务 url
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await DeliveryPickUpStoreApi.getDeliveryPickUpStore(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as DeliveryPickUpStoreApi.DeliveryPickUpStoreVO
|
||||
if (formType.value === 'create') {
|
||||
await DeliveryPickUpStoreApi.createDeliveryPickUpStore(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await DeliveryPickUpStoreApi.updateDeliveryPickUpStore(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
phone: '',
|
||||
logo: '',
|
||||
detailAddress: '',
|
||||
introduction: '',
|
||||
areaId: undefined,
|
||||
openingTime: undefined,
|
||||
closingTime: undefined,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 选择经纬度 */
|
||||
const selectAddress = function (loc: any): void {
|
||||
if (loc.latlng && loc.latlng.lat) {
|
||||
formData.value.latitude = loc.latlng.lat
|
||||
}
|
||||
if (loc.latlng && loc.latlng.lng) {
|
||||
formData.value.longitude = loc.latlng.lng
|
||||
}
|
||||
mapDialogVisible.value = false
|
||||
}
|
||||
|
||||
/** 初始化数据 */
|
||||
const initData = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await getAreaTree()
|
||||
areaList.value = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
// TODO @jason:要不创建一个 initTencentLbsMap
|
||||
window.selectAddress = selectAddress
|
||||
window.addEventListener(
|
||||
'message',
|
||||
function (event) {
|
||||
// 接收位置信息,用户选择确认位置点后选点组件会触发该事件,回传用户的位置信息
|
||||
let loc = event.data
|
||||
if (loc && loc.module === 'locationPicker') {
|
||||
// 防止其他应用也会向该页面 post 信息,需判断 module 是否为 'locationPicker'
|
||||
window.parent.selectAddress(loc)
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
const data = await ConfigApi.getConfigKey('tencent.lbs.key')
|
||||
let key = ''
|
||||
if (data && data.length > 0) {
|
||||
key = data
|
||||
}
|
||||
tencentLbsUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${key}&referer=myapp`
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.mapBox .el-dialog__body {
|
||||
height: 640px !important;
|
||||
}
|
||||
</style>
|
201
src/views/mall/trade/delivery/pickUpStore/index.vue
Normal file
201
src/views/mall/trade/delivery/pickUpStore/index.vue
Normal file
@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
|
||||
<el-form-item label="门店手机" prop="phone">
|
||||
<el-input
|
||||
v-model="queryParams.phone"
|
||||
placeholder="请输门店手机"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<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 label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="datetimerange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
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="['trade:delivery:pick-up-store:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" prop="id" />
|
||||
<el-table-column label="门店 logo" prop="logo">
|
||||
<template #default="scope">
|
||||
<img v-if="scope.row.logo" :src="scope.row.logo" alt="门店 logo" class="h-100px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="门店名称" prop="name" />
|
||||
<el-table-column label="门店手机" prop="phone" />
|
||||
<el-table-column label="门店详细地址" align="center" prop="detailAddress" />
|
||||
<el-table-column label="开启状态" align="center" prop="status">
|
||||
<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="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['trade:delivery:pick-up-store:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</ContentWrap>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DeliveryPickUpStoreForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="DeliveryPickUpStore">
|
||||
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
|
||||
import DeliveryPickUpStoreForm from './PickUpStoreForm.vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const total = ref(0) // 列表的总页数
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
status: undefined,
|
||||
phone: undefined,
|
||||
name: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await DeliveryPickUpStoreApi.deleteDeliveryPickUpStore(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await DeliveryPickUpStoreApi.getDeliveryPickUpStorePage(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 handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await DeliveryPickUpStoreApi.exportDeliveryPickUpStoreApi(queryParams)
|
||||
download.excel(data, '自提门店.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
572
src/views/mall/trade/order/index.vue
Normal file
572
src/views/mall/trade/order/index.vue
Normal file
@ -0,0 +1,572 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
:inline="true"
|
||||
>
|
||||
<el-form-item label="订单状态" prop="status">
|
||||
<el-select class="!w-280px" v-model="queryParams.status" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.TRADE_ORDER_STATUS)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="支付方式" prop="payChannelCode">
|
||||
<el-select
|
||||
v-model="queryParams.payChannelCode"
|
||||
class="!w-280px"
|
||||
clearable
|
||||
placeholder="全部"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-280px"
|
||||
start-placeholder="自定义时间"
|
||||
end-placeholder="自定义时间"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单来源" prop="terminal">
|
||||
<el-select class="!w-280px" v-model="queryParams.terminal" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.TERMINAL)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单类型" prop="type">
|
||||
<el-select class="!w-280px" v-model="queryParams.type" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.TRADE_ORDER_TYPE)"
|
||||
:key="(dict.value as string)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单搜索">
|
||||
<el-input
|
||||
v-show="true"
|
||||
class="!w-280px"
|
||||
v-model="queryType.v"
|
||||
clearable
|
||||
placeholder="请输入"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select style="width: 110px" v-model="queryType.k" clearable placeholder="全部">
|
||||
<el-option
|
||||
v-for="dict in searchList"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery" v-hasPermi="['trade:order:query']">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" v-hasPermi="['trade:order:query']">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
<el-button type="success" plain @click="handleExport" :loading="exportLoading">
|
||||
<!-- v-hasPermi="['trade:order:export']" -->
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出TODO
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<!-- 表格 -->
|
||||
<ContentWrap>
|
||||
<!-- 表单 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column type="expand" fixed="left">
|
||||
<template #default="scope">
|
||||
<el-descriptions class="mx-40">
|
||||
<el-descriptions-item label="商品原价(总): ">{{
|
||||
'¥ ' +
|
||||
parseFloat((scope.row.originalPrice / 100) as unknown as string).toFixed(2) +
|
||||
' 元'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="下单时间: ">
|
||||
{{ formatDate(scope.row.createTime) }}</el-descriptions-item
|
||||
>
|
||||
<el-descriptions-item label="推广人: ">TODO</el-descriptions-item>
|
||||
<el-descriptions-item label="用户备注: ">{{
|
||||
scope.row.userRemark
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="商家备注: ">{{ scope.row.remark }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" fixed="left">
|
||||
<template #header>
|
||||
<el-dropdown icon="eq:search" @command="handleDropType">
|
||||
<el-button link type="primary">全选({{ orderSelect.selectTotal }}) </el-button>
|
||||
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="1">当前页</el-dropdown-item>
|
||||
<el-dropdown-item command="2">所有页</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.itemSelect" @change="handcheckclick(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="订单号" align="center" min-width="110">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="showOrderDetail(scope.row)">{{
|
||||
scope.row.no
|
||||
}}</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单类型" align="center" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户信息" align="center" min-width="100">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="goUserDetail(scope.row)"
|
||||
>{{ scope.row.userId }}{{ '[' + scope.row.user.nickname + ']' }}</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="创建时间"
|
||||
prop="createTime"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column label="订单来源" align="center" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.terminal"
|
||||
:type="DICT_TYPE.TERMINAL"
|
||||
:value="scope.row.terminal"
|
||||
/>
|
||||
<span v-else>{{ scope.terminal }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="商品信息" align="left" min-width="200" prop="items">
|
||||
<template #default="scope">
|
||||
<el-popover
|
||||
ref="popover"
|
||||
placement="bottom"
|
||||
:title="'订单:' + scope.row.no"
|
||||
:width="400"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
<div>
|
||||
<div v-for="item in scope.row.items" :key="item">
|
||||
<el-image
|
||||
style="width: 36px; height: 36px"
|
||||
:src="item.picUrl"
|
||||
:preview-src-list="[item.picUrl]"
|
||||
fit="cover"
|
||||
@click="imagePreview(item.picUrl)"
|
||||
/>
|
||||
<span class="m-2">{{ item.spuName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-for="item in scope.row.items" :key="item">
|
||||
<div>
|
||||
<p>{{ item.spuName }}</p>
|
||||
<!-- TODO xiaobai: 是不是 (item.payPrice / 100.0).toFixed(2) -->
|
||||
<p>{{
|
||||
'¥ ' +
|
||||
parseFloat((item.payPrice / 100) as unknown as string).toFixed(2) +
|
||||
'元 x ' +
|
||||
item.count
|
||||
}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际支付(元)" align="center" prop="payPrice" min-width="100">
|
||||
<template #default="scope">
|
||||
{{ '¥ ' + parseFloat((scope.row.payPrice / 100) as unknown as string).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
align="center"
|
||||
label="支付时间"
|
||||
prop="payTime"
|
||||
min-width="180"
|
||||
/>
|
||||
<el-table-column label="支付类型" align="center" min-width="100" prop="payChannelCode">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.payChannelCode"
|
||||
:type="DICT_TYPE.PAY_CHANNEL_CODE_TYPE"
|
||||
:value="scope.row.payChannelCode"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订单状态" align="center" prop="status" min-width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag
|
||||
v-if="scope.row.status !== ''"
|
||||
:type="DICT_TYPE.TRADE_ORDER_STATUS"
|
||||
:value="scope.row.status"
|
||||
/>
|
||||
<span v-else>{{ scope.status }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right" min-width="150">
|
||||
<template #default="scope">
|
||||
<!-- <el-button v-if="scope.row.status == '0'" link type="primary" @click="sendXX(scope.row)"
|
||||
>待支付</el-button> -->
|
||||
<el-button v-if="scope.row.status == '10'" link type="primary" @click="sendXX(scope.row)"
|
||||
>发货</el-button
|
||||
>
|
||||
<el-button link type="primary" @click="showOrderDetail(scope.row)">详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<el-image-viewer
|
||||
v-if="imgViewVisible"
|
||||
:url-list="imageViewerList"
|
||||
@close="imgViewVisible = false"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts" name="OrderList">
|
||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||
import * as TradeOrderApi from '@/api/mall/trade/order'
|
||||
import {
|
||||
TradeOrderPageReqVO,
|
||||
SelectType,
|
||||
TradeOrderPageItemRespVO
|
||||
} from '@/api/mall/trade/order/type/orderType'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
||||
const message = useMessage()
|
||||
const { push } = useRouter()
|
||||
const imgViewVisible = ref(false) // 商品图预览
|
||||
const imageViewerList = ref<string[]>([]) // 商品图预览列表
|
||||
const queryFormRef = ref()
|
||||
const loading = ref(false)
|
||||
const exportLoading = ref(false)
|
||||
const total = ref(0) // 总记录数
|
||||
const list = ref<Array<TradeOrderPageItemRespVO | any>>([]) //表数据
|
||||
|
||||
// 选中状态选中处理
|
||||
const orderSelect: SelectType = reactive({
|
||||
queryParams: {} as TradeOrderPageReqVO,
|
||||
selectTotal: 0,
|
||||
selectAllFlag: false,
|
||||
selectData: new Map<number, Set<string>>(),
|
||||
unSelectList: new Set<string>()
|
||||
})
|
||||
|
||||
//表单搜索
|
||||
const queryParams: TradeOrderPageReqVO = reactive({
|
||||
pageNo: 1, //首页
|
||||
pageSize: 10 //页面大小
|
||||
})
|
||||
|
||||
const queryType = reactive({ k: '', v: '' }) // 订单搜索类型kv
|
||||
|
||||
/*
|
||||
* 订单搜索
|
||||
* 商品名称 商品件数 全部 需要后端支持TODO
|
||||
*/
|
||||
const searchList = ref([
|
||||
{ value: 'no', label: '订单号' },
|
||||
{ value: 'userId', label: '用户UID' },
|
||||
{ value: 'userNickname', label: '用户昵称' },
|
||||
{ value: 'userMobile', label: '用户电话' },
|
||||
{ value: 'spuName', label: '商品名称TODO' },
|
||||
{ value: 'itemCount', label: '商品件数TODO' }
|
||||
])
|
||||
|
||||
/**
|
||||
|
||||
当前页/? 如果pageNo存在,则将但前数据全部按照单个选中模式取消 ,不存在,则新增全页 增加 Map.pageNo Map.roderNoList
|
||||
单个选中 如果pagelist存在,订单号选中状态取反,并对总数按选中状态加减。如果pagelist不存在,订单号选中状态取反,并对总数按选中状态加减,增加 Map.pageNo,
|
||||
如果当前Map.pageNo 所对应list 为空 ,清除pageNo
|
||||
* @param command ===1 当前页 选中 ===2 所有页面选中
|
||||
*/
|
||||
const handleDropType = (command: string) => {
|
||||
let i = 0
|
||||
//当前页按钮
|
||||
if (command === '1') {
|
||||
//如果该页面有选中数据 则选中事件触发时 取消该页面
|
||||
if (orderSelect.selectData && orderSelect.selectData.has(queryParams.pageNo)) {
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
if (orderSelect.selectData.get(queryParams.pageNo)!.has(list.value[i].id)) {
|
||||
//选中数量减少
|
||||
orderSelect.selectTotal -= 1
|
||||
//考虑全选中,针对某一页面选中当前页时 会将所有数据中去掉该页面, 需要登记到 orderSelect.unSelectList
|
||||
unSelectListRecord(list.value[i].id, 'add')
|
||||
}
|
||||
list.value[i]['itemSelect'] = false
|
||||
}
|
||||
orderSelect.selectData.delete(queryParams.pageNo) //移除该页面
|
||||
} else {
|
||||
//当前页选中状态中 默认全选中
|
||||
orderSelect.selectData.set(queryParams.pageNo, new Set<string>())
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = true
|
||||
orderSelect.selectData.get(queryParams.pageNo)!.add(list.value[i].id)
|
||||
//选中数量增加
|
||||
orderSelect.selectTotal += 1
|
||||
//对于登记过取消状态中的数据排除
|
||||
unSelectListRecord(list.value[i].id, 'del')
|
||||
}
|
||||
}
|
||||
}
|
||||
//所有页按钮
|
||||
if (command === '2') {
|
||||
orderSelect.selectAllFlag = !orderSelect.selectAllFlag
|
||||
|
||||
if (orderSelect.selectAllFlag) {
|
||||
//打勾勾 //全选
|
||||
orderSelect.selectData?.set(queryParams.pageNo, new Set<string>())
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = true
|
||||
orderSelect.selectData?.get(queryParams.pageNo)?.add(list.value[i].id) //id是主键不重复
|
||||
}
|
||||
orderSelect.selectTotal = total.value
|
||||
} else {
|
||||
//取消勾勾
|
||||
for (i; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = false
|
||||
}
|
||||
initSelect() //重置之前选中的类容清空
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//对全选状态中的 单选或者当前页面单选时登记取消的数据
|
||||
const unSelectListRecord = (id: string, op: string) => {
|
||||
if (!orderSelect.selectAllFlag) {
|
||||
return
|
||||
}
|
||||
if (op == 'add') {
|
||||
orderSelect.unSelectList.add(id)
|
||||
} else {
|
||||
orderSelect.unSelectList.delete(id)
|
||||
}
|
||||
}
|
||||
/***复选框选中 */
|
||||
const handcheckclick = (row: any) => {
|
||||
if (row.itemSelect) {
|
||||
orderSelect.selectTotal += 1
|
||||
if (!orderSelect.selectData.has(queryParams.pageNo)) {
|
||||
orderSelect.selectData?.set(queryParams.pageNo, new Set<string>())
|
||||
}
|
||||
orderSelect.selectData?.get(queryParams.pageNo)?.add(row.id)
|
||||
unSelectListRecord(row.id, 'del')
|
||||
} else {
|
||||
orderSelect.selectTotal -= 1
|
||||
orderSelect.selectData.get(queryParams.pageNo)?.delete(row.id)
|
||||
unSelectListRecord(row.id, 'add')
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 导出数据
|
||||
*/
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
//增加查询条件 用于全选时后台查询数据
|
||||
orderSelect.queryParams = queryParams
|
||||
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
//全选时 根据上送的条件查询所有数据,在排除unseleectList 数据,
|
||||
//非全选时, 根据上送的selectData 直接查询数据 后台实现导出数据接口即可
|
||||
console.log(orderSelect)
|
||||
download.excel(orderSelect as any, '订单信息.xls') //?
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
//TODO
|
||||
exportLoading.value = false
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
queryType.v = '' //重置
|
||||
queryType.k = ''
|
||||
//休眠0.1s 等待watch响应
|
||||
setTimeout(() => {
|
||||
initSelect() //重置对选中设置恢复初始状态
|
||||
handleQuery()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
/**选中状态初始化**/
|
||||
const initSelect = () => {
|
||||
orderSelect.queryParams = {} as TradeOrderPageReqVO
|
||||
orderSelect.selectTotal = 0
|
||||
orderSelect.selectAllFlag = false
|
||||
orderSelect.selectData?.clear()
|
||||
orderSelect.unSelectList?.clear()
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TradeOrderApi.getOrderList(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
let i = 0
|
||||
if (orderSelect.selectData && orderSelect.selectData.has(queryParams.pageNo)) {
|
||||
//该页面已经加载过了。直接按照之前状态设置选中状态值
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
if (orderSelect.selectData.get(queryParams.pageNo)!.has(list.value[i].id)) {
|
||||
list.value[i]['itemSelect'] = true //之前已经选取过了
|
||||
} else {
|
||||
list.value[i]['itemSelect'] = false
|
||||
}
|
||||
}
|
||||
} else if (orderSelect.selectAllFlag) {
|
||||
//全选状态中 首次加载页面 默认全部选中
|
||||
orderSelect.selectData.set(queryParams.pageNo, new Set<string>())
|
||||
for (i = 0; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = true
|
||||
orderSelect.selectData.get(queryParams.pageNo)!.add(list.value[i].id)
|
||||
}
|
||||
} else {
|
||||
//非全选状态中 首次加载默认非选中状态
|
||||
for (i; i < list.value.length; i++) {
|
||||
list.value[i]['itemSelect'] = false //设置状态为未选中状态
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转订单详情
|
||||
*/
|
||||
const showOrderDetail = (row: any) => {
|
||||
push({ name: 'TradeOrderDetail', query: { id: row.id } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转用户详情
|
||||
*/
|
||||
const goUserDetail = (row: any) => {
|
||||
console.log('TODO User Detail: ' + row.userId)
|
||||
}
|
||||
/**
|
||||
* 发货
|
||||
*/
|
||||
const sendXX = (row: any) => {
|
||||
console.log('TODO Send XX: ' + row.no)
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品图预览
|
||||
* @param imgUrl
|
||||
*/
|
||||
const imagePreview = (imgUrl: string) => {
|
||||
imageViewerList.value = [imgUrl]
|
||||
imgViewVisible.value = true
|
||||
}
|
||||
|
||||
//针对订单搜索类型和值进行调整 使用监听器
|
||||
watch(
|
||||
() => [queryType.k, queryType.v],
|
||||
([newK, newV], [oldK]) => {
|
||||
//重置oldK对应得value
|
||||
if (oldK != newK) {
|
||||
if (oldK == 'no' && queryParams.no != '') {
|
||||
queryParams.no = ''
|
||||
} else if (oldK == 'userId' && queryParams.userId != '') {
|
||||
queryParams.userId = ''
|
||||
} else if (oldK == 'userNickname' && queryParams.userNickname != '') {
|
||||
queryParams.userNickname = ''
|
||||
} else if (oldK == 'userMobile' && queryParams.userMobile !== '') {
|
||||
queryParams.userMobile = ''
|
||||
} else if (oldK == 'spuName' && queryParams.spuName !== '') {
|
||||
queryParams.spuName = ''
|
||||
} else if (oldK == 'itemCount' && queryParams.itemCount !== '') {
|
||||
queryParams.itemCount = ''
|
||||
} else if (oldK == '' && queryParams.all !== '') {
|
||||
queryParams.all = ''
|
||||
}
|
||||
}
|
||||
// 根据选中得k设置Value
|
||||
if (newK == 'no') {
|
||||
queryParams.no = newV
|
||||
} else if (newK == 'userId') {
|
||||
queryParams.userId = newV
|
||||
} else if (newK == 'userNickname') {
|
||||
queryParams.userNickname = newV
|
||||
} else if (newK == 'userMobile') {
|
||||
queryParams.userMobile = newV
|
||||
} else if (newK == 'spuName') {
|
||||
queryParams.spuName = newV
|
||||
} else if (newK == 'itemCount') {
|
||||
queryParams.itemCount = newV
|
||||
} else if (newK == '') {
|
||||
queryParams.all = newV
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
initSelect()
|
||||
getList()
|
||||
})
|
||||
</script>
|
365
src/views/mall/trade/order/tradeOrderDetail.vue
Normal file
365
src/views/mall/trade/order/tradeOrderDetail.vue
Normal file
@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 订单信息 -->
|
||||
<el-descriptions title="订单信息">
|
||||
<el-descriptions-item label="订单号: ">{{ order.no }}</el-descriptions-item>
|
||||
<el-descriptions-item label="配送方式: ">物流配送</el-descriptions-item>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-descriptions-item label="营销活动: ">物流配送</el-descriptions-item>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-descriptions-item label="订单类型: ">
|
||||
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="order.type" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="收货人: ">{{ order.receiverName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="买家留言: ">{{ order.userRemark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="订单来源: ">
|
||||
<dict-tag :type="DICT_TYPE.TERMINAL" :value="order.terminal" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话: ">{{ order.receiverMobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商家备注: ">{{ order.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="支付单号: ">{{ order.payOrderId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="付款方式: ">
|
||||
<dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE_TYPE" :value="order.payChannelCode" />
|
||||
</el-descriptions-item>
|
||||
<!-- <el-descriptions-item label="买家: ">{{ order.user.nickname }}</el-descriptions-item> -->
|
||||
<!-- TODO 芋艿:待实现:跳转会员 -->
|
||||
<el-descriptions-item label="收货地址: ">
|
||||
{{ order.receiverAreaName }} {{ order.receiverDetailAddress }}
|
||||
<el-link
|
||||
v-clipboard:copy="order.receiverAreaName + ' ' + order.receiverDetailAddress"
|
||||
v-clipboard:success="clipboardSuccess"
|
||||
icon="ep:document-copy"
|
||||
type="primary"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 订单状态 -->
|
||||
<el-descriptions title="订单状态" :column="1">
|
||||
<el-descriptions-item label="订单状态: ">
|
||||
<!-- TODO xiaobai:status 一定有值哈,不用判断 -->
|
||||
<dict-tag
|
||||
v-if="order.status !== ''"
|
||||
:type="DICT_TYPE.TRADE_ORDER_STATUS"
|
||||
:value="order.status"
|
||||
/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label-class-name="no-colon">
|
||||
<el-button type="primary" size="small">调整价格</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">备注</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">发货</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">关闭订单</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">修改地址</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">打印电子面单</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">打印发货单</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
<el-button type="primary" size="small">确认收货</el-button>
|
||||
<!-- TODO 芋艿:待实现 -->
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">提醒: </span></template>
|
||||
买家付款成功后,货款将直接进入您的商户号(微信、支付宝)<br />
|
||||
请及时关注你发出的包裹状态,确保可以配送至买家手中 <br />
|
||||
如果买家表示没收到货或货物有问题,请及时联系买家处理,友好协商
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 物流信息 TODO -->
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<el-descriptions title="商品信息">
|
||||
<el-descriptions-item labelClassName="no-colon">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="15">
|
||||
<el-table :data="order.items" border>
|
||||
<el-table-column prop="spuName" label="商品" width="auto">
|
||||
<template #default="{ row }">
|
||||
{{ row.spuName }}
|
||||
<el-tag
|
||||
size="medium"
|
||||
v-for="property in row.properties"
|
||||
:key="property.propertyId"
|
||||
>
|
||||
{{ property.propertyName }}: {{ property.valueName }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="商品原价(元)" width="150">
|
||||
<template #default="{ row }"> ¥{{ (row.price / 100.0).toFixed(2) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="count" label="数量" width="100" />
|
||||
<el-table-column prop="payPrice" label="合计(元)" width="150">
|
||||
<template #default="{ row }"> ¥{{ (row.payPrice / 100.0).toFixed(2) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="afterSaleStatus" label="售后状态" width="auto">
|
||||
<template #default="{ row }">
|
||||
<dict-tag
|
||||
:type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS"
|
||||
:value="row.afterSaleStatus"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-col>
|
||||
<el-col :span="10" />
|
||||
</el-row>
|
||||
</el-descriptions-item>
|
||||
<!-- 占位 -->
|
||||
<!-- <el-descriptions-item v-for="item in 5" label-class-name="no-colon" :key="item" /> -->
|
||||
</el-descriptions>
|
||||
<el-descriptions column="6">
|
||||
<el-descriptions-item label="商品总额: ">
|
||||
<!-- TODO xiaobai: 是不是 (item.payPrice / 100.0).toFixed(2) -->
|
||||
¥{{ parseFloat((order.totalPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="运费金额: ">
|
||||
¥{{ parseFloat((order.deliveryPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="订单调价: ">
|
||||
¥{{
|
||||
parseFloat((order.adjustPrice / 100.0) as unknown as string).toFixed(2)
|
||||
}}</el-descriptions-item
|
||||
>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">商品优惠: </span></template>
|
||||
<!-- 没理解TODO order.totalPrice - order.totalPrice -->
|
||||
¥{{
|
||||
parseFloat(((order.totalPrice - order.totalPrice) / 100.0) as unknown as string).toFixed(
|
||||
2
|
||||
)
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">订单优惠: </span></template>
|
||||
¥{{ parseFloat((order.discountPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label><span style="color: red">积分抵扣: </span></template>
|
||||
¥{{ parseFloat((order.pointPrice / 100.0) as unknown as string).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-for="item in 5" label-class-name="no-colon" :key="item" />
|
||||
<!-- 占位 -->
|
||||
<el-descriptions-item label="应付金额: ">
|
||||
¥{{ (order.payPrice / 100.0).toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- TODO 芋艿:需要改改 -->
|
||||
<div v-for="group in detailGroups" :key="group.title">
|
||||
<el-descriptions v-bind="group.groupProps" :title="group.title">
|
||||
<!-- 订单操作日志 -->
|
||||
<el-descriptions-item v-if="group.key === 'orderLog'" labelClassName="no-colon">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="activity in detailInfo[group.key]"
|
||||
:key="activity.timestamp"
|
||||
:timestamp="activity.timestamp"
|
||||
>
|
||||
{{ activity.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-descriptions-item>
|
||||
|
||||
<!-- 物流信息 -->
|
||||
<!-- TODO @xiaobai:改成一个包裹哈;目前只允许发货一次 -->
|
||||
<el-descriptions-item v-if="group.key === 'expressInfo'" labelClassName="no-colon">
|
||||
<!-- 循环包裹物流信息 -->
|
||||
<div v-show="(pkgInfo = detailInfo[group.key]) !== null" style="border: 1px dashed">
|
||||
<!-- 包裹详情 -->
|
||||
<el-descriptions class="m-5">
|
||||
<el-descriptions-item
|
||||
v-for="(pkgChild, pkgCIdx) in group.children"
|
||||
v-bind="pkgChild.childProps"
|
||||
:key="`pkgChild_${pkgCIdx}`"
|
||||
:label="pkgChild.label"
|
||||
>
|
||||
<!-- 包裹商品列表 -->
|
||||
<template v-if="pkgChild.valueKey === 'goodsList' && pkgInfo[pkgChild.valueKey]">
|
||||
<div
|
||||
v-for="(goodInfo, goodInfoIdx) in pkgInfo[pkgChild.valueKey]"
|
||||
:key="`goodInfo_${goodInfoIdx}`"
|
||||
style="display: flex"
|
||||
>
|
||||
<el-image
|
||||
style="width: 100px; height: 100px; flex: none"
|
||||
:src="goodInfo.imgUrl"
|
||||
/>
|
||||
<el-descriptions :column="1">
|
||||
<el-descriptions-item labelClassName="no-colon">{{
|
||||
goodInfo.name
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="数量">{{ goodInfo.count }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 包裹物流详情 -->
|
||||
<template v-else-if="pkgChild.valueKey === 'wlxq'">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="6" :offset="1">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(activity, index) in pkgInfo[pkgChild.valueKey]"
|
||||
:key="index"
|
||||
:timestamp="activity.timestamp"
|
||||
>
|
||||
{{ activity.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ pkgInfo[pkgChild.valueKey] }}
|
||||
</template>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" name="TradeOrderDetail" setup>
|
||||
// TODO @xiaobai:在 order 下创建一个 order/detail,然后改名为 index.vue
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as TradeOrderApi from '@/api/mall/trade/order'
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const { query } = useRoute()
|
||||
const queryParams = reactive({
|
||||
id: query.id
|
||||
})
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const order = ref<any>({
|
||||
items: [],
|
||||
user: {}
|
||||
}) // 详情数据
|
||||
|
||||
const detailGroups = ref([
|
||||
{
|
||||
title: '物流信息',
|
||||
key: 'expressInfo',
|
||||
children: [
|
||||
{ label: '发货时间: ', valueKey: 'fhsj' },
|
||||
{ label: '物流公司: ', valueKey: 'wlgs' },
|
||||
{ label: '运单号: ', valueKey: 'ydh' },
|
||||
{ label: '物流状态: ', valueKey: 'wlzt', childProps: { span: 3 } },
|
||||
{ label: '物流详情: ', valueKey: 'wlxq' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '订单操作日志',
|
||||
key: 'orderLog'
|
||||
}
|
||||
])
|
||||
|
||||
const detailInfo = ref({
|
||||
expressInfo:
|
||||
// 物流信息
|
||||
{
|
||||
label: '包裹1',
|
||||
name: 'bg1',
|
||||
fhsj: '2022-11-03 16:50:45',
|
||||
wlgs: '极兔',
|
||||
ydh: '2132123',
|
||||
wlzt: '不支持此快递公司',
|
||||
wlxq: [
|
||||
{
|
||||
content: '正在派送途中,请您准备签收(派件人:王涛,电话:13854563814)',
|
||||
timestamp: '2018-04-15 15:00:16'
|
||||
},
|
||||
{
|
||||
content: '快件到达 【烟台龙口东江村委营业点】',
|
||||
timestamp: '2018-04-13 14:54:19'
|
||||
},
|
||||
{
|
||||
content: '快件已发车',
|
||||
timestamp: '2018-04-11 12:55:52'
|
||||
},
|
||||
{
|
||||
content: '快件已发车',
|
||||
timestamp: '2018-04-11 12:55:52'
|
||||
},
|
||||
{
|
||||
content: '快件已发车',
|
||||
timestamp: '2018-04-11 12:55:52'
|
||||
}
|
||||
]
|
||||
},
|
||||
orderLog: [
|
||||
// 订单操作日志
|
||||
{
|
||||
content: '买家【乌鸦】关闭了订单',
|
||||
timestamp: '2018-04-15 15:00:16'
|
||||
},
|
||||
{
|
||||
content: '买家【乌鸦】下单了',
|
||||
timestamp: '2018-04-15 15:00:16'
|
||||
}
|
||||
],
|
||||
goodsInfo: [] // 商品详情tableData
|
||||
})
|
||||
// 暂考虑一次性加载详情页面所有数据 TODO
|
||||
const getlist = async () => {
|
||||
dialogVisible.value = true
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await TradeOrderApi.getOrderDetail(queryParams.id as unknown as number)
|
||||
order.value = res
|
||||
console.log(order)
|
||||
} catch {
|
||||
message.error('获取详情数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
onMounted(async () => {
|
||||
await getlist()
|
||||
})
|
||||
const clipboardSuccess = () => {
|
||||
message.success('复制成功')
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-descriptions) {
|
||||
&:not(:nth-child(1)) {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.el-descriptions__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
width: 3px;
|
||||
height: 20px;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-descriptions-item__container {
|
||||
margin: 0 10px;
|
||||
|
||||
.no-colon {
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
93
src/views/member/point/config/index.vue
Normal file
93
src/views/member/point/config/index.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="hideId" v-show="false">
|
||||
<el-input v-model="formData.id" />
|
||||
</el-form-item>
|
||||
<!-- TODO @xiaqing:展示给用户的字段名,可以和 crmeb 保持一直,然后每一个表单都有类似 crmeb 的 tip;例如说:积分抵用比例(1积分抵多少金额)单位:元 -->
|
||||
<el-form-item label="积分抵扣" prop="tradeDeductEnable">
|
||||
<el-switch v-model="formData.tradeDeductEnable" />
|
||||
</el-form-item>
|
||||
<!-- TODO @xiaqing:用户看到的是元,最多 2 位;分是后端的存储哈 -->
|
||||
<el-form-item label="抵扣单位(分)" prop="tradeDeductUnitPrice">
|
||||
<el-input-number
|
||||
v-model="formData.tradeDeductUnitPrice"
|
||||
placeholder="请输入抵扣单位(分)"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="积分抵扣最大值" prop="tradeDeductMaxPrice">
|
||||
<el-input-number
|
||||
v-model="formData.tradeDeductMaxPrice"
|
||||
placeholder="请输入积分抵扣最大值"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="1 元赠送多少分" prop="tradeGivePoint">
|
||||
<el-input-number
|
||||
v-model="formData.tradeGivePoint"
|
||||
placeholder="请输入 1 元赠送多少积分"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">提交</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ConfigApi from '@/api/point/config'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
tradeDeductEnable: undefined,
|
||||
tradeDeductUnitPrice: undefined,
|
||||
tradeDeductMaxPrice: undefined,
|
||||
tradeGivePoint: undefined
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 修改积分配置 */
|
||||
const onSubmit = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as ConfigApi.ConfigVO
|
||||
await ConfigApi.saveConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 获得积分配置 */
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfig()
|
||||
formData.value = data
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getConfig()
|
||||
})
|
||||
</script>
|
180
src/views/member/point/record/RecordForm.vue
Normal file
180
src/views/member/point/record/RecordForm.vue
Normal file
@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="业务编码" prop="bizId">
|
||||
<el-input v-model="formData.bizId" placeholder="请输入业务编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型" prop="bizType">
|
||||
<el-select v-model="formData.bizType" placeholder="请选择业务类型">
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.MEMBER_POINT_BIZ_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="操作类型">
|
||||
<el-option label="增加" value="1" />
|
||||
<el-option label="扣减" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="积分标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入积分标题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="积分描述">
|
||||
<Editor :model-value="formData.description" height="150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="积分" prop="point">
|
||||
<el-input v-model="formData.point" placeholder="请输入积分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="变动后的积分" prop="totalPoint">
|
||||
<el-input v-model="formData.totalPoint" placeholder="请输入变动后的积分" />
|
||||
</el-form-item>
|
||||
<el-form-item label="积分状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="积分状态">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.MEMBER_POINT_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户id" prop="userId">
|
||||
<el-input v-model="formData.userId" placeholder="请输入用户id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="冻结时间" prop="freezingTime">
|
||||
<el-date-picker
|
||||
v-model="formData.freezingTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择冻结时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="解冻时间" prop="thawingTime">
|
||||
<el-date-picker
|
||||
v-model="formData.thawingTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择解冻时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="发生时间" prop="createDate">
|
||||
<el-date-picker
|
||||
v-model="formData.createDate"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择发生时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import * as RecordApi from '@/api/point/record'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
bizId: undefined,
|
||||
bizType: undefined,
|
||||
type: undefined,
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
point: undefined,
|
||||
totalPoint: undefined,
|
||||
status: undefined,
|
||||
userId: undefined,
|
||||
freezingTime: undefined,
|
||||
thawingTime: undefined,
|
||||
createDate: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
totalPoint: [{ required: true, message: '变动后的积分不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await RecordApi.getRecord(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as RecordApi.RecordVO
|
||||
if (formType.value === 'create') {
|
||||
await RecordApi.createRecord(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await RecordApi.updateRecord(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @xiaqing:不需要更新操作哇?
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
bizId: undefined,
|
||||
bizType: undefined,
|
||||
type: undefined,
|
||||
title: undefined,
|
||||
description: undefined,
|
||||
point: undefined,
|
||||
totalPoint: undefined,
|
||||
status: undefined,
|
||||
userId: undefined,
|
||||
freezingTime: undefined,
|
||||
thawingTime: undefined,
|
||||
createDate: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
193
src/views/member/point/record/index.vue
Normal file
193
src/views/member/point/record/index.vue
Normal file
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="业务编码" prop="bizId">
|
||||
<el-input
|
||||
v-model="queryParams.bizId"
|
||||
placeholder="请输入业务编码"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型" prop="bizType">
|
||||
<el-select
|
||||
v-model="queryParams.bizType"
|
||||
placeholder="请选择业务类型"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.MEMBER_POINT_BIZ_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="操作类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="操作类型" clearable class="!w-240px">
|
||||
<el-option label="增加" value="1" />
|
||||
<el-option label="扣减" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="积分标题" prop="title">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
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.MEMBER_POINT_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="获得时间" prop="createDate">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createDate"
|
||||
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-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<!-- TODO @xiaqing:展示用户的昵称哈; -->
|
||||
<el-table-column label="用户" align="center" prop="userId" />
|
||||
<el-table-column label="积分标题" align="center" prop="title" />
|
||||
<el-table-column label="积分描述" align="center" prop="description" />
|
||||
<el-table-column
|
||||
label="获得时间"
|
||||
align="center"
|
||||
prop="createDate"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<!-- todo @xiaqing:可以参考 crmeb 的展示,把积分和增加减少放一起,用红色和绿色展示 -->
|
||||
<el-table-column
|
||||
label="操作类型"
|
||||
align="center"
|
||||
prop="type"
|
||||
:formatter="
|
||||
(a, b, c) => {
|
||||
return c === '1' ? '增加' : '扣减'
|
||||
}
|
||||
"
|
||||
/>
|
||||
<el-table-column label="积分" align="center" prop="point" />
|
||||
<el-table-column label="变动后的积分" align="center" prop="totalPoint" />
|
||||
<el-table-column label="业务编码" align="center" prop="bizId" />
|
||||
<el-table-column label="业务类型" align="center" prop="bizType">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.MEMBER_POINT_BIZ_TYPE" :value="scope.row.bizType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.MEMBER_POINT_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="冻结时间"
|
||||
align="center"
|
||||
prop="freezingTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="解冻时间"
|
||||
align="center"
|
||||
prop="thawingTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<RecordForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getStrDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as RecordApi from '@/api/point/record'
|
||||
import RecordForm from './RecordForm.vue'
|
||||
|
||||
defineOptions({ name: 'PointRecord' })
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
bizId: null,
|
||||
bizType: null,
|
||||
type: null,
|
||||
title: null,
|
||||
status: null,
|
||||
createDate: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await RecordApi.getRecordPage(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()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
97
src/views/member/signin/config/SignInConfigForm.vue
Normal file
97
src/views/member/signin/config/SignInConfigForm.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="签到天数" prop="day">
|
||||
<el-input-number v-model="formData.day" :min="1" :max="7" :precision="0" />
|
||||
<el-text class="mx-1" style="margin-left: 10px" type="danger">
|
||||
只允许设置1-7,默认签到7天为一个周期</el-text
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item label="签到分数" prop="point">
|
||||
<el-input-number v-model="formData.point" :precision="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as SignInConfigApi from '@/api/point/signInConfig'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
day: undefined,
|
||||
point: undefined
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await SignInConfigApi.getSignInConfig(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as SignInConfigApi.SignInConfigVO
|
||||
if (formType.value === 'create') {
|
||||
await SignInConfigApi.createSignInConfig(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SignInConfigApi.updateSignInConfig(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
day: undefined,
|
||||
point: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
171
src/views/member/signin/config/index.vue
Normal file
171
src/views/member/signin/config/index.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<!-- TODO @xiaqing:搜索可以去掉,因为一共就没几条配置哈 -->
|
||||
<el-form-item label="签到天数" prop="day">
|
||||
<el-input
|
||||
v-model="queryParams.day"
|
||||
placeholder="请输入签到天数"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
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="['point:sign-in-config:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['point:sign-in-config:export']"
|
||||
>
|
||||
<!-- TODO @xiaqing:四个功能的导出都可以去掉 -->
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<!-- TODO @xiaqing:展示优化下,改成第 1 天、第 2 天这种 -->
|
||||
<el-table-column label="签到天数" align="center" prop="day" />
|
||||
<el-table-column label="获得积分" align="center" prop="point" />
|
||||
<!-- TODO @xiaqing:展示一个是否开启 -->
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['point:sign-in-config:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['point:sign-in-config: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SignInConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import download from '@/utils/download'
|
||||
import * as SignInConfigApi from '@/api/point/signInConfig'
|
||||
import SignInConfigForm from './SignInConfigForm.vue'
|
||||
|
||||
defineOptions({ name: 'SignInConfig' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
day: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
// TODO @xiaqing:可以不分页;
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await SignInConfigApi.getSignInConfigPage(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()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SignInConfigApi.deleteSignInConfig(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await SignInConfigApi.exportSignInConfig(queryParams)
|
||||
download.excel(data, '积分签到规则.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
99
src/views/member/signin/record/SignInRecordForm.vue
Normal file
99
src/views/member/signin/record/SignInRecordForm.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="100px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="签到用户" prop="userId">
|
||||
<el-input v-model="formData.userId" placeholder="请输入签到用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签到天数" prop="day">
|
||||
<el-input v-model="formData.day" placeholder="请输入签到天数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签到的分数" prop="point">
|
||||
<el-input v-model="formData.point" placeholder="请输入签到的分数" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as SignInRecordApi from '@/api/point/signInRecord'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
day: undefined,
|
||||
point: undefined
|
||||
})
|
||||
const formRules = reactive({})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await SignInRecordApi.getSignInRecord(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as SignInRecordApi.SignInRecordVO
|
||||
if (formType.value === 'create') {
|
||||
await SignInRecordApi.createSignInRecord(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SignInRecordApi.updateSignInRecord(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
day: undefined,
|
||||
point: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
179
src/views/member/signin/record/index.vue
Normal file
179
src/views/member/signin/record/index.vue
Normal file
@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="签到用户" prop="userId">
|
||||
<el-input
|
||||
v-model="queryParams.userId"
|
||||
placeholder="请输入签到用户"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="签到天数" prop="day">
|
||||
<el-input
|
||||
v-model="queryParams.day"
|
||||
placeholder="请输入签到天数"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</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="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['point:sign-in-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">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<!-- TODO @xiaqing:展示用户昵称 -->
|
||||
<el-table-column label="签到用户" align="center" prop="userId" />
|
||||
<el-table-column label="签到天数" align="center" prop="day" />
|
||||
<el-table-column label="获得积分" align="center" prop="point" />
|
||||
<el-table-column
|
||||
label="签到时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['point:sign-in-record: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<SignInRecordForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SignInRecord">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as SignInRecordApi from '@/api/point/signInRecord'
|
||||
import SignInRecordForm from './SignInRecordForm.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
userId: null,
|
||||
day: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await SignInRecordApi.getSignInRecordPage(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()
|
||||
// const openForm = (type: string, id?: number) => {
|
||||
// formRef.value.open(type, id)
|
||||
// }
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SignInRecordApi.deleteSignInRecord(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await SignInRecordApi.exportSignInRecord(queryParams)
|
||||
download.excel(data, '用户签到积分.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
@ -227,7 +227,7 @@ onMounted(async () => {
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@media (min-width: 992px) and (max-width: 1300px) {
|
||||
@media (width >= 992px) and (width <= 1300px) {
|
||||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
@ -237,7 +237,7 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
@media (width >= 768px) and (width <= 991px) {
|
||||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
@ -247,7 +247,7 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@media (width <= 767px) {
|
||||
.waterfall {
|
||||
column-count: 1;
|
||||
}
|
||||
|
@ -67,19 +67,19 @@ const emit = defineEmits<{
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1300px) {
|
||||
@media (width >= 992px) and (width <= 1300px) {
|
||||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
@media (width >= 768px) and (width <= 991px) {
|
||||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@media (width <= 767px) {
|
||||
.waterfall {
|
||||
column-count: 1;
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ const emit = defineEmits<{
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@media (min-width: 992px) and (max-width: 1300px) {
|
||||
@media (width >= 992px) and (width <= 1300px) {
|
||||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
@ -41,7 +41,7 @@ const emit = defineEmits<{
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
@media (width >= 768px) and (width <= 991px) {
|
||||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
@ -51,7 +51,7 @@ const emit = defineEmits<{
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
@media (width <= 767px) {
|
||||
.waterfall {
|
||||
column-count: 1;
|
||||
}
|
||||
|
@ -37,7 +37,12 @@
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
>
|
||||
<!-- 展示 HTML 内容 -->
|
||||
<template #templateContent="{ row }">
|
||||
<div v-dompurify-html="row.templateContent"></div>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<template #footer>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
|
@ -1,41 +1,42 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
plugins: ['stylelint-order'],
|
||||
customSyntax: 'postcss-html',
|
||||
extends: ['stylelint-config-standard'],
|
||||
customSyntax: 'postcss-html',
|
||||
rules: {
|
||||
'function-no-unknown': null,
|
||||
'selector-class-pattern': null,
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['global', 'deep']
|
||||
ignorePseudoClasses: ['global']
|
||||
}
|
||||
],
|
||||
'selector-pseudo-element-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoElements: ['v-deep']
|
||||
}
|
||||
],
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin']
|
||||
ignoreAtRules: ['tailwind', 'apply', 'variants', 'responsive', 'screen', 'function', 'if', 'each', 'include', 'mixin']
|
||||
}
|
||||
],
|
||||
'no-empty-source': null,
|
||||
'import-notation': null,
|
||||
'named-grid-areas-no-invalid': null,
|
||||
'unicode-bom': 'never',
|
||||
'no-descending-specificity': null,
|
||||
'font-family-no-missing-generic-family-keyword': null,
|
||||
'declaration-colon-space-after': 'always-single-line',
|
||||
'declaration-colon-space-before': 'never',
|
||||
'declaration-block-trailing-semicolon': null,
|
||||
// 'declaration-block-trailing-semicolon': 'always',
|
||||
'rule-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
ignore: ['after-comment', 'first-nested']
|
||||
}
|
||||
],
|
||||
'unit-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreUnits: ['rpx']
|
||||
}
|
||||
],
|
||||
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
|
||||
'order/order': [
|
||||
[
|
||||
'dollar-variables',
|
||||
@ -52,165 +53,14 @@ module.exports = {
|
||||
},
|
||||
'rules'
|
||||
],
|
||||
{
|
||||
severity: 'warning'
|
||||
}
|
||||
],
|
||||
// Specify the alphabetical order of the attributes in the declaration block
|
||||
'order/properties-order': [
|
||||
'position',
|
||||
'top',
|
||||
'right',
|
||||
'bottom',
|
||||
'left',
|
||||
'z-index',
|
||||
'display',
|
||||
'float',
|
||||
'width',
|
||||
'height',
|
||||
'max-width',
|
||||
'max-height',
|
||||
'min-width',
|
||||
'min-height',
|
||||
'padding',
|
||||
'padding-top',
|
||||
'padding-right',
|
||||
'padding-bottom',
|
||||
'padding-left',
|
||||
'margin',
|
||||
'margin-top',
|
||||
'margin-right',
|
||||
'margin-bottom',
|
||||
'margin-left',
|
||||
'margin-collapse',
|
||||
'margin-top-collapse',
|
||||
'margin-right-collapse',
|
||||
'margin-bottom-collapse',
|
||||
'margin-left-collapse',
|
||||
'overflow',
|
||||
'overflow-x',
|
||||
'overflow-y',
|
||||
'clip',
|
||||
'clear',
|
||||
'font',
|
||||
'font-family',
|
||||
'font-size',
|
||||
'font-smoothing',
|
||||
'osx-font-smoothing',
|
||||
'font-style',
|
||||
'font-weight',
|
||||
'hyphens',
|
||||
'src',
|
||||
'line-height',
|
||||
'letter-spacing',
|
||||
'word-spacing',
|
||||
'color',
|
||||
'text-align',
|
||||
'text-decoration',
|
||||
'text-indent',
|
||||
'text-overflow',
|
||||
'text-rendering',
|
||||
'text-size-adjust',
|
||||
'text-shadow',
|
||||
'text-transform',
|
||||
'word-break',
|
||||
'word-wrap',
|
||||
'white-space',
|
||||
'vertical-align',
|
||||
'list-style',
|
||||
'list-style-type',
|
||||
'list-style-position',
|
||||
'list-style-image',
|
||||
'pointer-events',
|
||||
'cursor',
|
||||
'background',
|
||||
'background-attachment',
|
||||
'background-color',
|
||||
'background-image',
|
||||
'background-position',
|
||||
'background-repeat',
|
||||
'background-size',
|
||||
'border',
|
||||
'border-collapse',
|
||||
'border-top',
|
||||
'border-right',
|
||||
'border-bottom',
|
||||
'border-left',
|
||||
'border-color',
|
||||
'border-image',
|
||||
'border-top-color',
|
||||
'border-right-color',
|
||||
'border-bottom-color',
|
||||
'border-left-color',
|
||||
'border-spacing',
|
||||
'border-style',
|
||||
'border-top-style',
|
||||
'border-right-style',
|
||||
'border-bottom-style',
|
||||
'border-left-style',
|
||||
'border-width',
|
||||
'border-top-width',
|
||||
'border-right-width',
|
||||
'border-bottom-width',
|
||||
'border-left-width',
|
||||
'border-radius',
|
||||
'border-top-right-radius',
|
||||
'border-bottom-right-radius',
|
||||
'border-bottom-left-radius',
|
||||
'border-top-left-radius',
|
||||
'border-radius-topright',
|
||||
'border-radius-bottomright',
|
||||
'border-radius-bottomleft',
|
||||
'border-radius-topleft',
|
||||
'content',
|
||||
'quotes',
|
||||
'outline',
|
||||
'outline-offset',
|
||||
'opacity',
|
||||
'filter',
|
||||
'visibility',
|
||||
'size',
|
||||
'zoom',
|
||||
'transform',
|
||||
'box-align',
|
||||
'box-flex',
|
||||
'box-orient',
|
||||
'box-pack',
|
||||
'box-shadow',
|
||||
'box-sizing',
|
||||
'table-layout',
|
||||
'animation',
|
||||
'animation-delay',
|
||||
'animation-duration',
|
||||
'animation-iteration-count',
|
||||
'animation-name',
|
||||
'animation-play-state',
|
||||
'animation-timing-function',
|
||||
'animation-fill-mode',
|
||||
'transition',
|
||||
'transition-delay',
|
||||
'transition-duration',
|
||||
'transition-property',
|
||||
'transition-timing-function',
|
||||
'background-clip',
|
||||
'backface-visibility',
|
||||
'resize',
|
||||
'appearance',
|
||||
'user-select',
|
||||
'interpolation-mode',
|
||||
'direction',
|
||||
'marks',
|
||||
'page',
|
||||
'set-link-source',
|
||||
'unicode-bidi',
|
||||
'speak'
|
||||
{ severity: 'warning' }
|
||||
]
|
||||
},
|
||||
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
|
||||
extends: ['stylelint-config-recommended', 'stylelint-config-html'],
|
||||
extends: ['stylelint-config-recommended'],
|
||||
rules: {
|
||||
'keyframes-name-pattern': null,
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
@ -226,6 +76,11 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['*.less', '**/*.less'],
|
||||
customSyntax: 'postcss-less',
|
||||
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue']
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user