Merge remote-tracking branch 'yudao/dev' into dev
This commit is contained in:
commit
c3b0403ade
15
README.md
15
README.md
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
* nodejs > 16.0.0 && pnpm > 7.30.0
|
* nodejs > 16.0.0 && pnpm > 7.30.0
|
||||||
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
* 演示地址【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>
|
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
||||||
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
||||||
* 视频教程:<https://doc.iocoder.cn/video/>
|
* 视频教程:<https://doc.iocoder.cn/video/>
|
||||||
@ -19,8 +20,8 @@
|
|||||||
|
|
||||||
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||||
|
|
||||||
* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin)
|
* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 实现
|
||||||
* 改换saas,自动引入等功能 [vue-element-plus-admin](https://gitee.com/yudaocode/vue-element-plus-admin)
|
* 改换 saas,自动引入等功能
|
||||||
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
|
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
|
||||||
|
|
||||||
![首页](preview/home.png)
|
![首页](preview/home.png)
|
||||||
@ -38,11 +39,11 @@
|
|||||||
| 框架 | 说明 | 版本 |
|
| 框架 | 说明 | 版本 |
|
||||||
|----------------------------------------------------------------------|------------------|--------|
|
|----------------------------------------------------------------------|------------------|--------|
|
||||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.47 |
|
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.47 |
|
||||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.1.4 |
|
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.3.1 |
|
||||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.34 |
|
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.3 |
|
||||||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.9.5 |
|
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.0.4 |
|
||||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.33 |
|
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.35 |
|
||||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 9.13.0 |
|
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.1.0 |
|
||||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
|
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
|
||||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.1.6 |
|
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.1.6 |
|
||||||
| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
|
| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
|
||||||
|
@ -17,7 +17,6 @@ const include = [
|
|||||||
'cropperjs',
|
'cropperjs',
|
||||||
'lodash-es',
|
'lodash-es',
|
||||||
'nprogress',
|
'nprogress',
|
||||||
'animate.css',
|
|
||||||
'web-storage-cache',
|
'web-storage-cache',
|
||||||
'@iconify/iconify',
|
'@iconify/iconify',
|
||||||
'@vueuse/core',
|
'@vueuse/core',
|
||||||
@ -33,38 +32,58 @@ const include = [
|
|||||||
'element-plus/es',
|
'element-plus/es',
|
||||||
'element-plus/es/locale/lang/zh-cn',
|
'element-plus/es/locale/lang/zh-cn',
|
||||||
'element-plus/es/locale/lang/en',
|
'element-plus/es/locale/lang/en',
|
||||||
'element-plus/es/components/backtop/style/index',
|
'element-plus/es/components/backtop/style/css',
|
||||||
'element-plus/es/components/form/style/index',
|
'element-plus/es/components/form/style/css',
|
||||||
'element-plus/es/components/radio-group/style/index',
|
'element-plus/es/components/radio-group/style/css',
|
||||||
'element-plus/es/components/radio/style/index',
|
'element-plus/es/components/radio/style/css',
|
||||||
'element-plus/es/components/checkbox/style/index',
|
'element-plus/es/components/checkbox/style/css',
|
||||||
'element-plus/es/components/checkbox-group/style/index',
|
'element-plus/es/components/checkbox-group/style/css',
|
||||||
'element-plus/es/components/switch/style/index',
|
'element-plus/es/components/switch/style/css',
|
||||||
'element-plus/es/components/time-picker/style/index',
|
'element-plus/es/components/time-picker/style/css',
|
||||||
'element-plus/es/components/date-picker/style/index',
|
'element-plus/es/components/date-picker/style/css',
|
||||||
'element-plus/es/components/col/style/index',
|
'element-plus/es/components/descriptions/style/css',
|
||||||
'element-plus/es/components/form-item/style/index',
|
'element-plus/es/components/descriptions-item/style/css',
|
||||||
'element-plus/es/components/alert/style/index',
|
'element-plus/es/components/link/style/css',
|
||||||
'element-plus/es/components/breadcrumb/style/index',
|
'element-plus/es/components/tooltip/style/css',
|
||||||
'element-plus/es/components/select/style/index',
|
'element-plus/es/components/drawer/style/css',
|
||||||
'element-plus/es/components/input/style/index',
|
'element-plus/es/components/dialog/style/css',
|
||||||
'element-plus/es/components/breadcrumb-item/style/index',
|
'element-plus/es/components/checkbox-button/style/css',
|
||||||
'element-plus/es/components/tag/style/index',
|
'element-plus/es/components/option-group/style/css',
|
||||||
'element-plus/es/components/pagination/style/index',
|
'element-plus/es/components/radio-button/style/css',
|
||||||
'element-plus/es/components/table/style/index',
|
'element-plus/es/components/cascader/style/css',
|
||||||
'element-plus/es/components/table-column/style/index',
|
'element-plus/es/components/color-picker/style/css',
|
||||||
'element-plus/es/components/card/style/index',
|
'element-plus/es/components/input-number/style/css',
|
||||||
'element-plus/es/components/row/style/index',
|
'element-plus/es/components/rate/style/css',
|
||||||
'element-plus/es/components/button/style/index',
|
'element-plus/es/components/select-v2/style/css',
|
||||||
'element-plus/es/components/menu/style/index',
|
'element-plus/es/components/tree-select/style/css',
|
||||||
'element-plus/es/components/sub-menu/style/index',
|
'element-plus/es/components/slider/style/css',
|
||||||
'element-plus/es/components/menu-item/style/index',
|
'element-plus/es/components/time-select/style/css',
|
||||||
'element-plus/es/components/option/style/index',
|
'element-plus/es/components/autocomplete/style/css',
|
||||||
'element-plus/es/components/dropdown/style/index',
|
'element-plus/es/components/image-viewer/style/css',
|
||||||
'element-plus/es/components/dropdown-menu/style/index',
|
'element-plus/es/components/upload/style/css',
|
||||||
'element-plus/es/components/dropdown-item/style/index',
|
'element-plus/es/components/col/style/css',
|
||||||
'element-plus/es/components/skeleton/style/index',
|
'element-plus/es/components/form-item/style/css',
|
||||||
|
'element-plus/es/components/alert/style/css',
|
||||||
|
'element-plus/es/components/breadcrumb/style/css',
|
||||||
|
'element-plus/es/components/select/style/css',
|
||||||
|
'element-plus/es/components/input/style/css',
|
||||||
|
'element-plus/es/components/breadcrumb-item/style/css',
|
||||||
|
'element-plus/es/components/tag/style/css',
|
||||||
|
'element-plus/es/components/pagination/style/css',
|
||||||
|
'element-plus/es/components/table/style/css',
|
||||||
|
'element-plus/es/components/table-v2/style/css',
|
||||||
|
'element-plus/es/components/table-column/style/css',
|
||||||
|
'element-plus/es/components/card/style/css',
|
||||||
|
'element-plus/es/components/row/style/css',
|
||||||
|
'element-plus/es/components/button/style/css',
|
||||||
|
'element-plus/es/components/menu/style/css',
|
||||||
|
'element-plus/es/components/sub-menu/style/css',
|
||||||
|
'element-plus/es/components/menu-item/style/css',
|
||||||
|
'element-plus/es/components/option/style/css',
|
||||||
|
'element-plus/es/components/dropdown/style/css',
|
||||||
|
'element-plus/es/components/dropdown-menu/style/css',
|
||||||
|
'element-plus/es/components/dropdown-item/style/css',
|
||||||
|
'element-plus/es/components/skeleton/style/css',
|
||||||
'element-plus/es/components/skeleton/style/css',
|
'element-plus/es/components/skeleton/style/css',
|
||||||
'element-plus/es/components/backtop/style/css',
|
'element-plus/es/components/backtop/style/css',
|
||||||
'element-plus/es/components/menu/style/css',
|
'element-plus/es/components/menu/style/css',
|
||||||
@ -78,20 +97,13 @@ const include = [
|
|||||||
'element-plus/es/components/breadcrumb/style/css',
|
'element-plus/es/components/breadcrumb/style/css',
|
||||||
'element-plus/es/components/breadcrumb-item/style/css',
|
'element-plus/es/components/breadcrumb-item/style/css',
|
||||||
'element-plus/es/components/image/style/css',
|
'element-plus/es/components/image/style/css',
|
||||||
'element-plus/es/components/tag/style/css',
|
'element-plus/es/components/collapse-transition/style/css',
|
||||||
'element-plus/es/components/dialog/style/css',
|
'element-plus/es/components/timeline/style/css',
|
||||||
'element-plus/es/components/form/style/css',
|
'element-plus/es/components/timeline-item/style/css',
|
||||||
'element-plus/es/components/form-item/style/css',
|
'element-plus/es/components/collapse/style/css',
|
||||||
'element-plus/es/components/card/style/css',
|
'element-plus/es/components/collapse-item/style/css',
|
||||||
'element-plus/es/components/tooltip/style/css',
|
'element-plus/es/components/button-group/style/css',
|
||||||
'element-plus/es/components/radio-group/style/css',
|
'element-plus/es/components/text/style/css'
|
||||||
'element-plus/es/components/radio/style/css',
|
|
||||||
'element-plus/es/components/input-number/style/css',
|
|
||||||
'element-plus/es/components/tree-select/style/css',
|
|
||||||
'element-plus/es/components/drawer/style/css',
|
|
||||||
'element-plus/es/components/image-viewer/style/css',
|
|
||||||
'element-plus/es/components/upload/style/css',
|
|
||||||
'element-plus/es/components/switch/style/css'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const exclude = ['@iconify/json']
|
const exclude = ['@iconify/json']
|
||||||
|
67
package.json
67
package.json
@ -33,12 +33,12 @@
|
|||||||
"@form-create/element-ui": "^3.1.17",
|
"@form-create/element-ui": "^3.1.17",
|
||||||
"@iconify/iconify": "^3.1.0",
|
"@iconify/iconify": "^3.1.0",
|
||||||
"@videojs-player/vue": "^1.0.0",
|
"@videojs-player/vue": "^1.0.0",
|
||||||
"@vueuse/core": "^9.13.0",
|
"@vueuse/core": "^10.1.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"@zxcvbn-ts/core": "^2.2.1",
|
"@zxcvbn-ts/core": "^2.2.1",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.3.6",
|
||||||
"benz-amr-recorder": "^1.1.5",
|
"benz-amr-recorder": "^1.1.5",
|
||||||
"bpmn-js-token-simulation": "^0.10.0",
|
"bpmn-js-token-simulation": "^0.10.0",
|
||||||
"camunda-bpmn-moddle": "^7.0.1",
|
"camunda-bpmn-moddle": "^7.0.1",
|
||||||
@ -46,19 +46,19 @@
|
|||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"diagram-js": "^11.6.0",
|
"diagram-js": "^11.6.0",
|
||||||
"echarts": "^5.4.1",
|
"echarts": "^5.4.2",
|
||||||
"echarts-wordcloud": "^2.1.0",
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "2.3.3",
|
"element-plus": "2.3.3",
|
||||||
"fast-xml-parser": "^4.1.3",
|
"fast-xml-parser": "^4.2.2",
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
"intro.js": "^7.0.1",
|
"intro.js": "^7.0.1",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"min-dash": "^4.0.0",
|
"min-dash": "^4.1.0",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.0.34",
|
"pinia": "^2.0.35",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.3",
|
||||||
"qs": "^6.11.1",
|
"qs": "^6.11.1",
|
||||||
"steady-xml": "^0.1.0",
|
"steady-xml": "^0.1.0",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
@ -73,61 +73,60 @@
|
|||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.5.0",
|
"@commitlint/cli": "^17.6.1",
|
||||||
"@commitlint/config-conventional": "^17.4.4",
|
"@commitlint/config-conventional": "^17.6.1",
|
||||||
"@iconify/json": "^2.2.38",
|
"@iconify/json": "^2.2.54",
|
||||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||||
"@purge-icons/generated": "^0.9.0",
|
"@purge-icons/generated": "^0.9.0",
|
||||||
"@types/intro.js": "^5.1.1",
|
"@types/intro.js": "^5.1.1",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.7",
|
||||||
"@types/node": "^18.15.5",
|
"@types/node": "^18.16.0",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/qrcode": "^1.5.0",
|
"@types/qrcode": "^1.5.0",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||||
"@typescript-eslint/parser": "^5.56.0",
|
"@typescript-eslint/parser": "^5.59.0",
|
||||||
"@vitejs/plugin-legacy": "^4.0.2",
|
"@vitejs/plugin-legacy": "^4.0.2",
|
||||||
"@vitejs/plugin-vue": "^4.1.0",
|
"@vitejs/plugin-vue": "^4.1.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"bpmn-js": "^8.9.0",
|
"bpmn-js": "^8.9.0",
|
||||||
"bpmn-js-properties-panel": "^0.46.0",
|
"bpmn-js-properties-panel": "^0.46.0",
|
||||||
"consola": "^2.15.3",
|
"consola": "^3.1.0",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-define-config": "^1.17.0",
|
"eslint-define-config": "^1.18.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-vue": "^9.9.0",
|
"eslint-plugin-vue": "^9.11.0",
|
||||||
"lint-staged": "^13.2.0",
|
"lint-staged": "^13.2.1",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.23",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"postcss-scss": "^4.0.6",
|
"postcss-scss": "^4.0.6",
|
||||||
"prettier": "^2.8.6",
|
"prettier": "^2.8.8",
|
||||||
"rimraf": "^4.4.1",
|
"rimraf": "^5.0.0",
|
||||||
"rollup": "^3.20.0",
|
"rollup": "^3.20.7",
|
||||||
"sass": "^1.59.3",
|
"sass": "^1.62.0",
|
||||||
"stylelint": "^15.3.0",
|
"stylelint": "^15.6.0",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"stylelint-config-prettier": "^9.0.5",
|
"stylelint-config-recommended": "^12.0.0",
|
||||||
"stylelint-config-recommended": "^11.0.0",
|
"stylelint-config-standard": "^33.0.0",
|
||||||
"stylelint-config-standard": "^31.0.0",
|
|
||||||
"stylelint-order": "^6.0.3",
|
"stylelint-order": "^6.0.3",
|
||||||
"terser": "^5.16.6",
|
"terser": "^5.17.1",
|
||||||
"typescript": "5.0.2",
|
"typescript": "5.0.4",
|
||||||
"unplugin-auto-import": "^0.15.1",
|
"unplugin-auto-import": "^0.15.3",
|
||||||
"unplugin-element-plus": "^0.7.0",
|
"unplugin-element-plus": "^0.7.1",
|
||||||
"unplugin-vue-components": "^0.24.1",
|
"unplugin-vue-components": "^0.24.1",
|
||||||
"vite": "4.2.1",
|
"vite": "4.3.1",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-ejs": "^1.6.4",
|
"vite-plugin-ejs": "^1.6.4",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-progress": "^0.0.6",
|
"vite-plugin-progress": "^0.0.7",
|
||||||
"vite-plugin-purge-icons": "^0.9.2",
|
"vite-plugin-purge-icons": "^0.9.2",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vite-plugin-top-level-await": "^1.3.0",
|
"vite-plugin-top-level-await": "^1.3.0",
|
||||||
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
|
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
|
||||||
"vite-plugin-windicss": "^1.8.10",
|
"vite-plugin-windicss": "^1.8.10",
|
||||||
"vue-tsc": "^1.2.0",
|
"vue-tsc": "^1.4.4",
|
||||||
"windicss": "^3.5.6"
|
"windicss": "^3.5.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
@ -54,3 +54,8 @@ export const getBrand = (id: number) => {
|
|||||||
export const getBrandParam = (params: PageParam) => {
|
export const getBrandParam = (params: PageParam) => {
|
||||||
return request.get({ url: '/product/brand/page', params })
|
return request.get({ url: '/product/brand/page', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获得商品品牌精简信息列表
|
||||||
|
export const getSimpleBrandList = () => {
|
||||||
|
return request.get({ url: '/product/brand/list-all-simple' })
|
||||||
|
}
|
||||||
|
@ -71,8 +71,8 @@ export const getPropertyList = (params: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获得属性项列表
|
// 获得属性项列表
|
||||||
export const getPropertyListAndValue = (params: any) => {
|
export const getPropertyListAndValue = (data: any) => {
|
||||||
return request.get({ url: '/product/property/get-value-list', params })
|
return request.post({ url: '/product/property/get-value-list', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------ 属性值 -------------------
|
// ------------------------ 属性值 -------------------
|
||||||
|
@ -1,46 +1,92 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
// 创建商品 SPU
|
export interface Property {
|
||||||
export function createSpu(data) {
|
propertyId?: number // 属性编号
|
||||||
return request.post({
|
propertyName?: string // 属性名称
|
||||||
url: '/product/spu/create',
|
valueId?: number // 属性值编号
|
||||||
data: data
|
valueName?: string // 属性值名称
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新商品 SPU
|
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
|
||||||
export function updateSpu(data) {
|
export interface SkuType {
|
||||||
return request.put({
|
id?: number // 商品 SKU 编号
|
||||||
url: '/product/spu/update',
|
spuId?: number // SPU 编号
|
||||||
data: data
|
properties?: Property[] // 属性数组
|
||||||
})
|
price?: number // 商品价格
|
||||||
|
marketPrice?: number // 市场价
|
||||||
|
costPrice?: number // 成本价
|
||||||
|
barCode?: string // 商品条码
|
||||||
|
picUrl?: string // 图片地址
|
||||||
|
stock?: number // 库存
|
||||||
|
weight?: number // 商品重量,单位:kg 千克
|
||||||
|
volume?: number // 商品体积,单位:m^3 平米
|
||||||
|
subCommissionFirstPrice?: number // 一级分销的佣金
|
||||||
|
subCommissionSecondPrice?: number // 二级分销的佣金
|
||||||
|
salesCount?: number // 商品销量
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除商品 SPU
|
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
|
||||||
export function deleteSpu(id) {
|
export interface SpuType {
|
||||||
return request.delete({
|
id?: number
|
||||||
url: `/product/spu/delete?id=${id}`
|
name?: string // 商品名称
|
||||||
})
|
categoryId?: number | null // 商品分类
|
||||||
|
keyword?: string // 关键字
|
||||||
|
unit?: number | null // 单位
|
||||||
|
picUrl?: string // 商品封面图
|
||||||
|
sliderPicUrls?: string[] // 商品轮播图
|
||||||
|
introduction?: string // 商品简介
|
||||||
|
deliveryTemplateId?: number | null // 运费模版
|
||||||
|
brandId?: number | null // 商品品牌编号
|
||||||
|
specType?: boolean // 商品规格
|
||||||
|
subCommissionType?: boolean // 分销类型
|
||||||
|
skus: SkuType[] // sku数组
|
||||||
|
description?: string // 商品详情
|
||||||
|
sort?: string // 商品排序
|
||||||
|
giveIntegral?: number // 赠送积分
|
||||||
|
virtualSalesCount?: number // 虚拟销量
|
||||||
|
recommendHot?: boolean // 是否热卖
|
||||||
|
recommendBenefit?: boolean // 是否优惠
|
||||||
|
recommendBest?: boolean // 是否精品
|
||||||
|
recommendNew?: boolean // 是否新品
|
||||||
|
recommendGood?: boolean // 是否优品
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得商品 SPU 详情
|
// 获得 Spu 列表
|
||||||
export function getSpuDetail(id) {
|
export const getSpuPage = (params: PageParam) => {
|
||||||
return request.get({
|
return request.get({ url: '/product/spu/page', params })
|
||||||
url: `/product/spu/get-detail?id=${id}`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得商品 SPU 分页
|
// 获得 Spu 列表 tabsCount
|
||||||
export function getSpuPage(query) {
|
export const getTabsCount = () => {
|
||||||
return request.get({
|
return request.get({ url: '/product/spu/get-count' })
|
||||||
url: '/product/spu/page',
|
|
||||||
params: query
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获得商品 SPU 精简列表
|
// 创建商品 Spu
|
||||||
export function getSpuSimpleList() {
|
export const createSpu = (data: SpuType) => {
|
||||||
return request.get({
|
return request.post({ url: '/product/spu/create', data })
|
||||||
url: '/product/spu/get-simple-list'
|
}
|
||||||
})
|
|
||||||
|
// 更新商品 Spu
|
||||||
|
export const updateSpu = (data: SpuType) => {
|
||||||
|
return request.put({ url: '/product/spu/update', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新商品 Spu status
|
||||||
|
export const updateStatus = (data: { id: number; status: number }) => {
|
||||||
|
return request.put({ url: '/product/spu/update-status', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获得商品 Spu
|
||||||
|
export const getSpu = (id: number) => {
|
||||||
|
return request.get({ url: `/product/spu/get-detail?id=${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除商品 Spu
|
||||||
|
export const deleteSpu = (id: number) => {
|
||||||
|
return request.delete({ url: `/product/spu/delete?id=${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出商品 Spu Excel
|
||||||
|
export const exportSpu = async (params) => {
|
||||||
|
return await request.download({ url: '/product/spu/export', params })
|
||||||
}
|
}
|
||||||
|
40
src/api/mall/trade/delivery/express/index.ts
Normal file
40
src/api/mall/trade/delivery/express/index.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface DeliveryExpressVO {
|
||||||
|
id: number
|
||||||
|
code: string
|
||||||
|
name: string
|
||||||
|
logo: string
|
||||||
|
sort: number
|
||||||
|
status: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询快递公司列表
|
||||||
|
export const getDeliveryExpressPage = async (params: PageParam) => {
|
||||||
|
return await request.get({ url: '/trade/delivery/express/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询快递公司详情
|
||||||
|
export const getDeliveryExpress = async (id: number) => {
|
||||||
|
return await request.get({ url: '/trade/delivery/express/get?id=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增快递公司
|
||||||
|
export const createDeliveryExpress = async (data: DeliveryExpressVO) => {
|
||||||
|
return await request.post({ url: '/trade/delivery/express/create', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改快递公司
|
||||||
|
export const updateDeliveryExpress = async (data: DeliveryExpressVO) => {
|
||||||
|
return await request.put({ url: '/trade/delivery/express/update', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除快递公司
|
||||||
|
export const deleteDeliveryExpress = async (id: number) => {
|
||||||
|
return await request.delete({ url: '/trade/delivery/express/delete?id=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出快递公司 Excel
|
||||||
|
export const exportDeliveryExpressApi = async (params) => {
|
||||||
|
return await request.download({ url: '/trade/delivery/express/export-excel', params })
|
||||||
|
}
|
54
src/api/mall/trade/delivery/expressTemplate/index.ts
Normal file
54
src/api/mall/trade/delivery/expressTemplate/index.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface DeliveryExpressTemplateVO {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
chargeMode: number
|
||||||
|
sort: number
|
||||||
|
templateCharge: ExpressTemplateChargeVO[]
|
||||||
|
templateFree: ExpressTemplateFreeVO[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type ExpressTemplateChargeVO = {
|
||||||
|
areaIds: number[]
|
||||||
|
startCount: number
|
||||||
|
startPrice: number
|
||||||
|
extraCount: number
|
||||||
|
extraPrice: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare type ExpressTemplateFreeVO = {
|
||||||
|
areaIds: number[]
|
||||||
|
freeCount: number
|
||||||
|
freePrice: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询快递运费模板列表
|
||||||
|
export const getDeliveryExpressTemplatePage = async (params: PageParam) => {
|
||||||
|
return await request.get({ url: '/trade/delivery/express-template/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询快递运费模板详情
|
||||||
|
export const getDeliveryExpressTemplate = async (id: number) => {
|
||||||
|
return await request.get({ url: '/trade/delivery/express-template/get?id=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增快递运费模板
|
||||||
|
export const createDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
|
||||||
|
return await request.post({ url: '/trade/delivery/express-template/create', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改快递运费模板
|
||||||
|
export const updateDeliveryExpressTemplate = async (data: DeliveryExpressTemplateVO) => {
|
||||||
|
return await request.put({ url: '/trade/delivery/express-template/update', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除快递运费模板
|
||||||
|
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 })
|
||||||
|
}
|
@ -5,6 +5,14 @@ export const getAreaTree = async () => {
|
|||||||
return await request.get({ url: '/system/area/tree' })
|
return await request.get({ url: '/system/area/tree' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getChildrenArea = async (id: number) => {
|
||||||
|
return await request.get({ url: '/system/area/get-children?id=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAreaListByIds = async (ids) => {
|
||||||
|
return await request.get({ url: '/system/area/get-by-ids?ids=' + ids })
|
||||||
|
}
|
||||||
|
|
||||||
// 获得 IP 对应的地区名
|
// 获得 IP 对应的地区名
|
||||||
export const getAreaByIp = async (ip: string) => {
|
export const getAreaByIp = async (ip: string) => {
|
||||||
return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
|
return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
|
||||||
|
@ -39,7 +39,7 @@ export const updateNotifyTemplate = async (data: NotifyTemplateVO) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除站内信模板
|
// 删除站内信模板
|
||||||
export const deleteNotifyTemplateApi = async (id: number) => {
|
export const deleteNotifyTemplate = async (id: number) => {
|
||||||
return await request.delete({ url: '/system/notify-template/delete?id=' + id })
|
return await request.delete({ url: '/system/notify-template/delete?id=' + id })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,6 @@ export const getAccessTokenPage = (params: PageParam) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除 token
|
// 删除 token
|
||||||
export const deleteAccessToken = (accessToken: number) => {
|
export const deleteAccessToken = (accessToken: string) => {
|
||||||
return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
|
return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Slots } from 'vue'
|
import type { Slots } from 'vue'
|
||||||
import { getSlot } from '@/utils/tsxHelper'
|
import { getSlot } from '@/utils/tsxHelper'
|
||||||
import { PlaceholderMoel } from './types'
|
import { PlaceholderModel } from './types'
|
||||||
import { FormSchema } from '@/types/form'
|
import { FormSchema } from '@/types/form'
|
||||||
import { ColProps } from '@/types/components'
|
import { ColProps } from '@/types/components'
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import { ColProps } from '@/types/components'
|
|||||||
* @returns 返回提示信息对象
|
* @returns 返回提示信息对象
|
||||||
* @description 用于自动设置placeholder
|
* @description 用于自动设置placeholder
|
||||||
*/
|
*/
|
||||||
export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
|
export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']
|
const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']
|
||||||
const selectMap = ['Select', 'SelectV2', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
|
const selectMap = ['Select', 'SelectV2', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
|
||||||
@ -108,8 +108,8 @@ export const setItemComponentSlots = (
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param schema Form表单结构化数组
|
* @param schema Form表单结构化数组
|
||||||
* @param formModel FormMoel
|
* @param formModel FormModel
|
||||||
* @returns FormMoel
|
* @returns FormModel
|
||||||
* @description 生成对应的formModel
|
* @description 生成对应的formModel
|
||||||
*/
|
*/
|
||||||
export const initModel = (schema: FormSchema[], formModel: Recordable) => {
|
export const initModel = (schema: FormSchema[], formModel: Recordable) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FormSchema } from '@/types/form'
|
import { FormSchema } from '@/types/form'
|
||||||
|
|
||||||
export interface PlaceholderMoel {
|
export interface PlaceholderModel {
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
startPlaceholder?: string
|
startPlaceholder?: string
|
||||||
endPlaceholder?: string
|
endPlaceholder?: string
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="upload-box">
|
<div class="upload-box">
|
||||||
<el-upload
|
<el-upload
|
||||||
:action="updateUrl"
|
|
||||||
list-type="picture-card"
|
|
||||||
:class="['upload', drag ? 'no-border' : '']"
|
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
:multiple="true"
|
:accept="fileType.join(',')"
|
||||||
:limit="limit"
|
:action="updateUrl"
|
||||||
:headers="uploadHeaders"
|
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
|
:class="['upload', drag ? 'no-border' : '']"
|
||||||
|
:drag="drag"
|
||||||
|
:headers="uploadHeaders"
|
||||||
|
:limit="limit"
|
||||||
|
:multiple="true"
|
||||||
|
:on-error="uploadError"
|
||||||
:on-exceed="handleExceed"
|
:on-exceed="handleExceed"
|
||||||
:on-success="uploadSuccess"
|
:on-success="uploadSuccess"
|
||||||
:on-error="uploadError"
|
list-type="picture-card"
|
||||||
:drag="drag"
|
|
||||||
:accept="fileType.join(',')"
|
|
||||||
>
|
>
|
||||||
<div class="upload-empty">
|
<div class="upload-empty">
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
@ -40,15 +40,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-image-viewer
|
<el-image-viewer
|
||||||
v-if="imgViewVisible"
|
v-if="imgViewVisible"
|
||||||
@close="imgViewVisible = false"
|
|
||||||
:url-list="[viewImageUrl]"
|
:url-list="[viewImageUrl]"
|
||||||
|
@close="imgViewVisible = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="UploadImgs">
|
<script lang="ts" name="UploadImgs" setup>
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
|
import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
|
||||||
import { ElNotification } from 'element-plus'
|
import { ElNotification } from 'element-plus'
|
||||||
import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
|
|
||||||
|
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||||
@ -88,8 +88,19 @@ const uploadHeaders = ref({
|
|||||||
'tenant-id': getTenantId()
|
'tenant-id': getTenantId()
|
||||||
})
|
})
|
||||||
|
|
||||||
const fileList = ref<UploadUserFile[]>(props.modelValue)
|
const fileList = ref<UploadUserFile[]>()
|
||||||
|
// fix: 改为动态监听赋值解决图片回显问题
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(data) => {
|
||||||
|
if (!data) return
|
||||||
|
fileList.value = data
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* @description 文件上传之前判断
|
* @description 文件上传之前判断
|
||||||
* @param rawFile 上传的文件
|
* @param rawFile 上传的文件
|
||||||
@ -116,9 +127,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
|||||||
interface UploadEmits {
|
interface UploadEmits {
|
||||||
(e: 'update:modelValue', value: UploadUserFile[]): void
|
(e: 'update:modelValue', value: UploadUserFile[]): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<UploadEmits>()
|
const emit = defineEmits<UploadEmits>()
|
||||||
const uploadSuccess = (response, uploadFile: UploadFile) => {
|
const uploadSuccess = (response, uploadFile: UploadFile) => {
|
||||||
if (!response) return
|
if (!response) return
|
||||||
|
// TODO 多图上传组件成功后只是把保存成功后的url替换掉组件选图时的文件路径,所以返回的fileList包含的是一个包含文件信息的对象列表
|
||||||
uploadFile.url = response.data
|
uploadFile.url = response.data
|
||||||
emit('update:modelValue', fileList.value)
|
emit('update:modelValue', fileList.value)
|
||||||
message.success('上传成功')
|
message.success('上传成功')
|
||||||
@ -159,35 +172,40 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss" scoped>
|
||||||
.is-error {
|
.is-error {
|
||||||
.upload {
|
.upload {
|
||||||
:deep(.el-upload--picture-card),
|
:deep(.el-upload--picture-card),
|
||||||
:deep(.el-upload-dragger) {
|
:deep(.el-upload-dragger) {
|
||||||
border: 1px dashed var(--el-color-danger) !important;
|
border: 1px dashed var(--el-color-danger) !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--el-color-primary) !important;
|
border-color: var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.disabled) {
|
:deep(.disabled) {
|
||||||
.el-upload--picture-card,
|
.el-upload--picture-card,
|
||||||
.el-upload-dragger {
|
.el-upload-dragger {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background: var(--el-disabled-bg-color) !important;
|
background: var(--el-disabled-bg-color) !important;
|
||||||
border: 1px dashed var(--el-border-color-darker);
|
border: 1px dashed var(--el-border-color-darker);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: var(--el-border-color-darker) !important;
|
border-color: var(--el-border-color-darker) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-box {
|
.upload-box {
|
||||||
.no-border {
|
.no-border {
|
||||||
:deep(.el-upload--picture-card) {
|
:deep(.el-upload--picture-card) {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.upload) {
|
:deep(.upload) {
|
||||||
.el-upload-dragger {
|
.el-upload-dragger {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -199,14 +217,17 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px dashed var(--el-border-color-darker);
|
border: 1px dashed var(--el-border-color-darker);
|
||||||
border-radius: v-bind(borderRadius);
|
border-radius: v-bind(borderRadius);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px dashed var(--el-color-primary);
|
border: 1px dashed var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-upload-dragger.is-dragover {
|
.el-upload-dragger.is-dragover {
|
||||||
background-color: var(--el-color-primary-light-9);
|
background-color: var(--el-color-primary-light-9);
|
||||||
border: 2px dashed var(--el-color-primary) !important;
|
border: 2px dashed var(--el-color-primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-upload-list__item,
|
.el-upload-list__item,
|
||||||
.el-upload--picture-card {
|
.el-upload--picture-card {
|
||||||
width: v-bind(width);
|
width: v-bind(width);
|
||||||
@ -214,11 +235,13 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: v-bind(borderRadius);
|
border-radius: v-bind(borderRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-image {
|
.upload-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-handle {
|
.upload-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -233,6 +256,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
background: rgb(0 0 0 / 60%);
|
background: rgb(0 0 0 / 60%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: var(--el-transition-duration-fast);
|
transition: var(--el-transition-duration-fast);
|
||||||
|
|
||||||
.handle-icon {
|
.handle-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -240,15 +264,18 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0 6%;
|
padding: 0 6%;
|
||||||
color: aliceblue;
|
color: aliceblue;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
margin-bottom: 15%;
|
margin-bottom: 15%;
|
||||||
font-size: 140%;
|
font-size: 140%;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-upload-list__item {
|
.el-upload-list__item {
|
||||||
&:hover {
|
&:hover {
|
||||||
.upload-handle {
|
.upload-handle {
|
||||||
@ -256,6 +283,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-empty {
|
.upload-empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -263,12 +291,14 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
color: var(--el-color-info);
|
color: var(--el-color-info);
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-upload__tip {
|
.el-upload__tip {
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -2,9 +2,9 @@ import { Layout } from '@/utils/routerHelper'
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
/**
|
/**
|
||||||
* redirect: noredirect 当设置 noredirect 的时候该路由在面包屑导航中不可被点击
|
* redirect: noredirect 当设置 noredirect 的时候该路由在面包屑导航中不可被点击
|
||||||
* name:'router-name' 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
* name:'router-name' 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
||||||
* meta : {
|
* meta : {
|
||||||
hidden: true 当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false)
|
hidden: true 当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false)
|
||||||
|
|
||||||
alwaysShow: true 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,
|
alwaysShow: true 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,
|
||||||
@ -31,7 +31,7 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
canTo: true 设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)
|
canTo: true 设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)
|
||||||
}
|
}
|
||||||
**/
|
**/
|
||||||
const remainingRouter: AppRouteRecordRaw[] = [
|
const remainingRouter: AppRouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
@ -345,6 +345,42 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' }
|
meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/product',
|
||||||
|
component: Layout,
|
||||||
|
name: 'Product',
|
||||||
|
meta: {
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'productSpuAdd', // TODO @puhui999:最好拆成 add 和 edit 两个路由;添加商品;修改商品 fix
|
||||||
|
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||||
|
name: 'ProductSpuAdd',
|
||||||
|
meta: {
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true,
|
||||||
|
icon: 'ep:edit',
|
||||||
|
title: '添加商品',
|
||||||
|
activeMenu: '/product/product-spu'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'productSpuEdit/:spuId(\\d+)',
|
||||||
|
component: () => import('@/views/mall/product/spu/addForm.vue'),
|
||||||
|
name: 'productSpuEdit',
|
||||||
|
meta: {
|
||||||
|
noCache: true,
|
||||||
|
hidden: true,
|
||||||
|
canTo: true,
|
||||||
|
icon: 'ep:edit',
|
||||||
|
title: '编辑商品',
|
||||||
|
activeMenu: '/product/product-spu'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解决表格内容超过表格总宽度后,横向滚动条前端顶不到表格边缘的问题
|
||||||
|
.el-scrollbar__bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
/* nprogress 适配 element-plus 的主题色 */
|
/* nprogress 适配 element-plus 的主题色 */
|
||||||
#nprogress {
|
#nprogress {
|
||||||
& .bar {
|
& .bar {
|
||||||
|
@ -155,3 +155,57 @@ export const fileSizeFormatter = (row, column, cellValue) => {
|
|||||||
const sizeStr = size.toFixed(2) //保留的小数位数
|
const sizeStr = size.toFixed(2) //保留的小数位数
|
||||||
return sizeStr + ' ' + unitArr[index]
|
return sizeStr + ' ' + unitArr[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将值复制到目标对象,且以目标对象属性为准,例:target: {a:1} source:{a:2,b:3} 结果为:{a:2}
|
||||||
|
* @param target 目标对象
|
||||||
|
* @param source 源对象
|
||||||
|
*/
|
||||||
|
export const copyValueToTarget = (target, source) => {
|
||||||
|
const newObj = Object.assign({}, target, source)
|
||||||
|
// 删除多余属性
|
||||||
|
Object.keys(newObj).forEach((key) => {
|
||||||
|
// 如果不是target中的属性则删除
|
||||||
|
if (Object.keys(target).indexOf(key) === -1) {
|
||||||
|
delete newObj[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 更新目标对象值
|
||||||
|
Object.assign(target, newObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO @puhui999:返回要带上 .00 哈.例如说 1.00
|
||||||
|
/**
|
||||||
|
* 将一个整数转换为分数保留两位小数
|
||||||
|
* @param num
|
||||||
|
*/
|
||||||
|
export const formatToFraction = (num: number | string | undefined): number => {
|
||||||
|
if (typeof num === 'undefined') return 0
|
||||||
|
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
|
||||||
|
return parseFloat((parsedNumber / 100).toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将一个分数转换为整数
|
||||||
|
* @param num
|
||||||
|
*/
|
||||||
|
export const convertToInteger = (num: number | string | undefined): number => {
|
||||||
|
if (typeof num === 'undefined') return 0
|
||||||
|
const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
|
||||||
|
// TODO 分转元后还有小数则四舍五入
|
||||||
|
return Math.round(parsedNumber * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元转分
|
||||||
|
*/
|
||||||
|
export const yuanToFen = (amount: string | number): number => {
|
||||||
|
return Math.round(Number(amount) * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分转元
|
||||||
|
*/
|
||||||
|
export const fenToYuan = (amount: string | number): number => {
|
||||||
|
return Number((Number(amount) / 100).toFixed(2))
|
||||||
|
}
|
||||||
|
@ -11,7 +11,8 @@ const DEFAULT_CONFIG: TreeHelperConfig = {
|
|||||||
export const defaultProps = {
|
export const defaultProps = {
|
||||||
children: 'children',
|
children: 'children',
|
||||||
label: 'name',
|
label: 'name',
|
||||||
value: 'id'
|
value: 'id',
|
||||||
|
isLeaf: 'leaf'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
|
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
|
||||||
|
@ -87,7 +87,7 @@
|
|||||||
<MyProcessViewer
|
<MyProcessViewer
|
||||||
key="designer"
|
key="designer"
|
||||||
v-model="bpmnXML"
|
v-model="bpmnXML"
|
||||||
:value="bpmnXML"
|
:value="bpmnXML as any"
|
||||||
v-bind="bpmnControlForm"
|
v-bind="bpmnControlForm"
|
||||||
:prefix="bpmnControlForm.prefix"
|
:prefix="bpmnControlForm.prefix"
|
||||||
/>
|
/>
|
||||||
|
@ -68,7 +68,7 @@ const formRules = reactive({
|
|||||||
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const userList = ref([]) // 用户列表
|
const userList = ref<any[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
|
@ -117,6 +117,7 @@ import { dateFormatter } from '@/utils/formatTime'
|
|||||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import UserGroupForm from './UserGroupForm.vue'
|
import UserGroupForm from './UserGroupForm.vue'
|
||||||
|
import { UserVO } from '@/api/system/user'
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
@ -131,7 +132,7 @@ const queryParams = reactive({
|
|||||||
createTime: []
|
createTime: []
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const userList = ref([]) // 用户列表
|
const userList = ref<UserVO[]>([]) // 用户列表
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
|
||||||
<MyProcessPenal
|
<MyProcessPenal
|
||||||
key="penal"
|
key="penal"
|
||||||
:bpmnModeler="modeler"
|
:bpmnModeler="modeler as any"
|
||||||
:prefix="controlForm.prefix"
|
:prefix="controlForm.prefix"
|
||||||
class="process-panel"
|
class="process-panel"
|
||||||
:model="model"
|
:model="model"
|
||||||
|
@ -219,7 +219,7 @@
|
|||||||
<MyProcessViewer
|
<MyProcessViewer
|
||||||
key="designer"
|
key="designer"
|
||||||
v-model="bpmnXML"
|
v-model="bpmnXML"
|
||||||
:value="bpmnXML"
|
:value="bpmnXML as any"
|
||||||
v-bind="bpmnControlForm"
|
v-bind="bpmnControlForm"
|
||||||
:prefix="bpmnControlForm.prefix"
|
:prefix="bpmnControlForm.prefix"
|
||||||
/>
|
/>
|
||||||
|
@ -28,7 +28,7 @@ const props = defineProps({
|
|||||||
id: propTypes.number.def(undefined)
|
id: propTypes.number.def(undefined)
|
||||||
})
|
})
|
||||||
const detailLoading = ref(false) // 表单的加载中
|
const detailLoading = ref(false) // 表单的加载中
|
||||||
const detailData = ref({}) // 详情数据
|
const detailData = ref<any>({}) // 详情数据
|
||||||
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
|
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
|
||||||
|
|
||||||
/** 获得数据 */
|
/** 获得数据 */
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-card>
|
</el-card>
|
||||||
<!-- 流程图预览 -->
|
<!-- 流程图预览 -->
|
||||||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
|
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="BpmProcessInstanceCreate">
|
<script setup lang="ts" name="BpmProcessInstanceCreate">
|
||||||
|
@ -33,6 +33,12 @@
|
|||||||
import FcDesigner from '@form-create/designer'
|
import FcDesigner from '@form-create/designer'
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
import { isString } from '@/utils/is'
|
import { isString } from '@/utils/is'
|
||||||
|
|
||||||
|
import hljs from 'highlight.js' // 导入代码高亮文件
|
||||||
|
import 'highlight.js/styles/github.css' // 导入代码高亮样式
|
||||||
|
import xml from 'highlight.js/lib/languages/java'
|
||||||
|
import json from 'highlight.js/lib/languages/json'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息
|
const message = useMessage() // 消息
|
||||||
|
|
||||||
@ -112,10 +118,6 @@ const copy = async (text: string) => {
|
|||||||
/**
|
/**
|
||||||
* 代码高亮
|
* 代码高亮
|
||||||
*/
|
*/
|
||||||
import hljs from 'highlight.js' // 导入代码高亮文件
|
|
||||||
import 'highlight.js/styles/github.css' // 导入代码高亮样式
|
|
||||||
import xml from 'highlight.js/lib/languages/java'
|
|
||||||
import json from 'highlight.js/lib/languages/json'
|
|
||||||
const highlightedCode = (code) => {
|
const highlightedCode = (code) => {
|
||||||
// 处理语言和代码
|
// 处理语言和代码
|
||||||
let language = 'json'
|
let language = 'json'
|
||||||
|
@ -59,6 +59,14 @@ import { useClipboard } from '@vueuse/core'
|
|||||||
import { handleTree2 } from '@/utils/tree'
|
import { handleTree2 } from '@/utils/tree'
|
||||||
import * as CodegenApi from '@/api/infra/codegen'
|
import * as CodegenApi from '@/api/infra/codegen'
|
||||||
|
|
||||||
|
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 { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@ -184,13 +192,6 @@ const copy = async (text: string) => {
|
|||||||
/**
|
/**
|
||||||
* 代码高亮
|
* 代码高亮
|
||||||
*/
|
*/
|
||||||
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 highlightedCode = (item) => {
|
||||||
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
|
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
|
||||||
const result = hljs.highlight(language, item.code || '', true)
|
const result = hljs.highlight(language, item.code || '', true)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
|
<doc-alert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
|
||||||
<doc-alert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
|
<doc-alert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
|
||||||
|
|
||||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||||
<el-row>
|
<el-row>
|
||||||
<!-- 基本信息 -->
|
<!-- 基本信息 -->
|
||||||
@ -51,127 +50,224 @@
|
|||||||
<!-- 命令统计 -->
|
<!-- 命令统计 -->
|
||||||
<el-col :span="12" class="mt-3">
|
<el-col :span="12" class="mt-3">
|
||||||
<el-card :gutter="12" shadow="hover">
|
<el-card :gutter="12" shadow="hover">
|
||||||
<div ref="commandStatsRef" class="h-88"></div>
|
<Echart :options="commandStatsRefChika" :height="420" />
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<!-- 内存使用量统计 -->
|
<!-- 内存使用量统计 -->
|
||||||
<el-col :span="12" class="mt-3">
|
<el-col :span="12" class="mt-3">
|
||||||
<el-card class="ml-3" :gutter="12" shadow="hover">
|
<el-card class="ml-3" :gutter="12" shadow="hover">
|
||||||
<div ref="usedmemory" class="h-88"></div>
|
<Echart :options="usedmemoryEchartChika" :height="420" />
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="InfraRedis">
|
<script setup lang="ts">
|
||||||
import * as echarts from 'echarts'
|
import echarts from '@/plugins/echarts'
|
||||||
|
import { GaugeChart } from 'echarts/charts'
|
||||||
|
import { ToolboxComponent } from 'echarts/components'
|
||||||
import * as RedisApi from '@/api/infra/redis'
|
import * as RedisApi from '@/api/infra/redis'
|
||||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||||
|
|
||||||
const cache = ref<RedisMonitorInfoVO>()
|
const cache = ref<RedisMonitorInfoVO>()
|
||||||
|
|
||||||
// 基本信息
|
// 基本信息
|
||||||
const readRedisInfo = async () => {
|
const readRedisInfo = async () => {
|
||||||
const data = await RedisApi.getCache()
|
const data = await RedisApi.getCache()
|
||||||
cache.value = data
|
cache.value = data
|
||||||
loadEchartOptions(data.commandStats)
|
|
||||||
}
|
}
|
||||||
// 图表
|
|
||||||
const commandStatsRef = ref<HTMLElement>()
|
|
||||||
const usedmemory = ref<HTMLDivElement>()
|
|
||||||
|
|
||||||
const loadEchartOptions = (stats) => {
|
// 内存使用情况
|
||||||
const commandStats = [] as any[]
|
const usedmemoryEchartChika = reactive({
|
||||||
const nameList = [] as string[]
|
title: {
|
||||||
stats.forEach((row) => {
|
// 仪表盘标题。
|
||||||
commandStats.push({
|
text: '内存使用情况',
|
||||||
name: row.command,
|
left: 'center',
|
||||||
value: row.calls
|
show: true, // 是否显示标题,默认 true。
|
||||||
})
|
offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||||
nameList.push(row.command)
|
color: 'yellow', // 文字的颜色,默认 #333。
|
||||||
})
|
fontSize: 20 // 文字的字体大小,默认 15。
|
||||||
|
},
|
||||||
const commandStatsInstance = echarts.init(commandStatsRef.value!, 'macarons')
|
toolbox: {
|
||||||
|
show: false,
|
||||||
commandStatsInstance.setOption({
|
feature: {
|
||||||
title: {
|
restore: { show: true },
|
||||||
text: '命令统计',
|
saveAsImage: { show: true }
|
||||||
left: 'center'
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
series: [
|
||||||
trigger: 'item',
|
{
|
||||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
name: '峰值',
|
||||||
},
|
type: 'gauge',
|
||||||
legend: {
|
min: 0,
|
||||||
type: 'scroll',
|
max: 50,
|
||||||
orient: 'vertical',
|
splitNumber: 10,
|
||||||
right: 30,
|
//这是指针的颜色
|
||||||
top: 10,
|
color: '#F5C74E',
|
||||||
bottom: 20,
|
radius: '85%',
|
||||||
data: nameList,
|
center: ['50%', '50%'],
|
||||||
textStyle: {
|
startAngle: 225,
|
||||||
color: '#a1a1a1'
|
endAngle: -45,
|
||||||
|
axisLine: {
|
||||||
|
// 坐标轴线
|
||||||
|
lineStyle: {
|
||||||
|
// 属性lineStyle控制线条样式
|
||||||
|
color: [
|
||||||
|
[0.2, '#7FFF00'],
|
||||||
|
[0.8, '#00FFFF'],
|
||||||
|
[1, '#FF0000']
|
||||||
|
],
|
||||||
|
//width: 6 外框的大小(环的宽度)
|
||||||
|
width: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
// 坐标轴小标记
|
||||||
|
//里面的线长是5(短线)
|
||||||
|
length: 5, // 属性length控制线长
|
||||||
|
lineStyle: {
|
||||||
|
// 属性lineStyle控制线条样式
|
||||||
|
color: '#76D9D7'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
// 分隔线
|
||||||
|
length: 20, // 属性length控制线长
|
||||||
|
lineStyle: {
|
||||||
|
// 属性lineStyle(详见lineStyle)控制线条样式
|
||||||
|
color: '#76D9D7'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#76D9D7',
|
||||||
|
distance: 15,
|
||||||
|
fontSize: 15
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
// 指针的大小
|
||||||
|
width: 7,
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
textStyle: {
|
||||||
|
fontWeight: 'normal',
|
||||||
|
// 里面文字下的数值大小(50)
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#FFFFFF'
|
||||||
|
},
|
||||||
|
valueAnimation: true
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
show: true
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
series: [
|
]
|
||||||
{
|
})
|
||||||
name: '命令',
|
|
||||||
type: 'pie',
|
// 指令使用情况
|
||||||
radius: [20, 120],
|
const commandStatsRefChika = reactive({
|
||||||
center: ['40%', '60%'],
|
title: {
|
||||||
data: commandStats,
|
text: '命令统计',
|
||||||
roseType: 'radius',
|
left: 'center'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
type: 'scroll',
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 30,
|
||||||
|
top: 10,
|
||||||
|
bottom: 20,
|
||||||
|
data: [] as any[],
|
||||||
|
textStyle: {
|
||||||
|
color: '#a1a1a1'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '命令',
|
||||||
|
type: 'pie',
|
||||||
|
radius: [20, 120],
|
||||||
|
center: ['40%', '60%'],
|
||||||
|
data: [] as any[],
|
||||||
|
roseType: 'radius',
|
||||||
|
label: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
label: {
|
label: {
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
emphasis: {
|
itemStyle: {
|
||||||
label: {
|
shadowBlur: 10,
|
||||||
show: true
|
shadowOffsetX: 0,
|
||||||
},
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||||
itemStyle: {
|
|
||||||
shadowBlur: 10,
|
|
||||||
shadowOffsetX: 0,
|
|
||||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
})
|
]
|
||||||
|
})
|
||||||
|
|
||||||
const usedMemoryInstance = echarts.init(usedmemory.value!, 'macarons')
|
/** 加载数据 */
|
||||||
usedMemoryInstance.setOption({
|
const getSummary = () => {
|
||||||
title: {
|
// 初始化命令图表
|
||||||
text: '内存使用情况',
|
initCommandStatsChart()
|
||||||
left: 'center'
|
usedMemoryInstance()
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '峰值',
|
|
||||||
type: 'gauge',
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
progress: {
|
|
||||||
show: true
|
|
||||||
},
|
|
||||||
detail: {
|
|
||||||
formatter: cache.value!.info.used_memory_human
|
|
||||||
},
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: parseFloat(cache.value!.info.used_memory_human),
|
|
||||||
name: '内存消耗'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
/** 命令使用情况 */
|
||||||
// TODO @hiiwbs 微信,优化使用 Echart 组件
|
const initCommandStatsChart = async () => {
|
||||||
|
usedmemoryEchartChika.series[0].data = []
|
||||||
|
// 发起请求
|
||||||
|
try {
|
||||||
|
const data = await RedisApi.getCache()
|
||||||
|
cache.value = data
|
||||||
|
// 处理数据
|
||||||
|
const commandStats = [] as any[]
|
||||||
|
const nameList = [] as string[]
|
||||||
|
data.commandStats.forEach((row) => {
|
||||||
|
commandStats.push({
|
||||||
|
name: row.command,
|
||||||
|
value: row.calls
|
||||||
|
})
|
||||||
|
nameList.push(row.command)
|
||||||
|
})
|
||||||
|
commandStatsRefChika.legend.data = nameList
|
||||||
|
commandStatsRefChika.series[0].data = commandStats
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
const usedMemoryInstance = async () => {
|
||||||
|
try {
|
||||||
|
const data = await RedisApi.getCache()
|
||||||
|
cache.value = data
|
||||||
|
// 仪表盘详情,用于显示数据。
|
||||||
|
usedmemoryEchartChika.series[0].detail = {
|
||||||
|
show: true, // 是否显示详情,默认 true。
|
||||||
|
offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
|
||||||
|
color: 'auto', // 文字的颜色,默认 auto。
|
||||||
|
fontSize: 30, // 文字的字体大小,默认 15。
|
||||||
|
formatter: cache.value!.info.used_memory_human // 格式化函数或者字符串
|
||||||
|
}
|
||||||
|
|
||||||
|
usedmemoryEchartChika.series[0].data[0] = {
|
||||||
|
value: cache.value!.info.used_memory_human,
|
||||||
|
name: '内存消耗'
|
||||||
|
}
|
||||||
|
console.log(cache.value!.info)
|
||||||
|
usedmemoryEchartChika.tooltip = {
|
||||||
|
formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
echarts.use([ToolboxComponent])
|
||||||
|
echarts.use([GaugeChart])
|
||||||
|
// 读取 redis 信息
|
||||||
readRedisInfo()
|
readRedisInfo()
|
||||||
|
// 加载数据
|
||||||
|
getSummary()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -2,42 +2,49 @@
|
|||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-form
|
<el-form
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
ref="queryFormRef"
|
||||||
:inline="true"
|
:inline="true"
|
||||||
|
:model="queryParams"
|
||||||
|
class="-mb-15px"
|
||||||
label-width="68px"
|
label-width="68px"
|
||||||
>
|
>
|
||||||
<el-form-item label="名称" prop="name">
|
<el-form-item label="名称" prop="name">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
placeholder="请输入名称"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入名称"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="创建时间" prop="createTime">
|
<el-form-item label="创建时间" prop="createTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryParams.createTime"
|
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')]"
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<el-button @click="handleQuery">
|
||||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-hasPermi="['product:property:create']"
|
||||||
plain
|
plain
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openForm('create')"
|
@click="openForm('create')"
|
||||||
v-hasPermi="['product:property:create']"
|
|
||||||
>
|
>
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
|
新增
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -46,23 +53,23 @@
|
|||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list">
|
<el-table v-loading="loading" :data="list">
|
||||||
<el-table-column label="编号" align="center" prop="id" />
|
<el-table-column align="center" label="编号" prop="id" />
|
||||||
<el-table-column label="名称" align="center" />
|
<el-table-column align="center" label="名称" prop="name" />
|
||||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
<el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="创建时间"
|
:formatter="dateFormatter"
|
||||||
align="center"
|
align="center"
|
||||||
|
label="创建时间"
|
||||||
prop="createTime"
|
prop="createTime"
|
||||||
width="180"
|
width="180"
|
||||||
:formatter="dateFormatter"
|
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column align="center" label="操作">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
|
v-hasPermi="['product:property:update']"
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openForm('update', scope.row.id)"
|
@click="openForm('update', scope.row.id)"
|
||||||
v-hasPermi="['product:property:update']"
|
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -70,10 +77,10 @@
|
|||||||
<router-link :to="'/property/value/' + scope.row.id">属性值</router-link>
|
<router-link :to="'/property/value/' + scope.row.id">属性值</router-link>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-hasPermi="['product:property:delete']"
|
||||||
link
|
link
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDelete(scope.row.id)"
|
@click="handleDelete(scope.row.id)"
|
||||||
v-hasPermi="['product:property:delete']"
|
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -82,9 +89,9 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination
|
<Pagination
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
v-model:limit="queryParams.pageSize"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
:total="total"
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
@ -92,10 +99,11 @@
|
|||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<PropertyForm ref="formRef" @success="getList" />
|
<PropertyForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="ProductProperty">
|
<script lang="ts" name="ProductProperty" setup>
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import * as PropertyApi from '@/api/mall/product/property'
|
import * as PropertyApi from '@/api/mall/product/property'
|
||||||
import PropertyForm from './PropertyForm.vue'
|
import PropertyForm from './PropertyForm.vue'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
178
src/views/mall/product/spu/addForm.vue
Normal file
178
src/views/mall/product/spu/addForm.vue
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap v-loading="formLoading">
|
||||||
|
<el-tabs v-model="activeName">
|
||||||
|
<el-tab-pane label="商品信息" name="basicInfo">
|
||||||
|
<BasicInfoForm
|
||||||
|
ref="basicInfoRef"
|
||||||
|
v-model:activeName="activeName"
|
||||||
|
:propFormData="formData"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="商品详情" name="description">
|
||||||
|
<DescriptionForm
|
||||||
|
ref="descriptionRef"
|
||||||
|
v-model:activeName="activeName"
|
||||||
|
:propFormData="formData"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="其他设置" name="otherSettings">
|
||||||
|
<OtherSettingsForm
|
||||||
|
ref="otherSettingsRef"
|
||||||
|
v-model:activeName="activeName"
|
||||||
|
: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 @click="close">返回</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="ProductSpuForm" setup>
|
||||||
|
import { cloneDeep } from 'lodash-es'
|
||||||
|
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||||
|
import { BasicInfoForm, DescriptionForm, OtherSettingsForm } from './components'
|
||||||
|
// 业务api
|
||||||
|
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||||
|
import { convertToInteger, formatToFraction } from '@/utils'
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { push, currentRoute } = useRouter() // 路由
|
||||||
|
const { params } = 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
|
||||||
|
// spu 表单数据
|
||||||
|
const formData = ref<ProductSpuApi.SpuType>({
|
||||||
|
name: '', // 商品名称
|
||||||
|
categoryId: null, // 商品分类
|
||||||
|
keyword: '', // 关键字
|
||||||
|
unit: null, // 单位
|
||||||
|
picUrl: '', // 商品封面图
|
||||||
|
sliderPicUrls: [], // 商品轮播图
|
||||||
|
introduction: '', // 商品简介
|
||||||
|
deliveryTemplateId: 1, // 运费模版
|
||||||
|
brandId: null, // 商品品牌
|
||||||
|
specType: false, // 商品规格
|
||||||
|
subCommissionType: false, // 分销类型
|
||||||
|
skus: [
|
||||||
|
{
|
||||||
|
price: 0, // 商品价格
|
||||||
|
marketPrice: 0, // 市场价
|
||||||
|
costPrice: 0, // 成本价
|
||||||
|
barCode: '', // 商品条码
|
||||||
|
picUrl: '', // 图片地址
|
||||||
|
stock: 0, // 库存
|
||||||
|
weight: 0, // 商品重量
|
||||||
|
volume: 0, // 商品体积
|
||||||
|
subCommissionFirstPrice: 0, // 一级分销的佣金
|
||||||
|
subCommissionSecondPrice: 0 // 二级分销的佣金
|
||||||
|
}
|
||||||
|
],
|
||||||
|
description: '', // 商品详情
|
||||||
|
sort: 0, // 商品排序
|
||||||
|
giveIntegral: 0, // 赠送积分
|
||||||
|
virtualSalesCount: 0, // 虚拟销量
|
||||||
|
recommendHot: false, // 是否热卖
|
||||||
|
recommendBenefit: false, // 是否优惠
|
||||||
|
recommendBest: false, // 是否精品
|
||||||
|
recommendNew: false, // 是否新品
|
||||||
|
recommendGood: false // 是否优品
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 获得详情 */
|
||||||
|
const getDetail = async () => {
|
||||||
|
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) => {
|
||||||
|
// 回显价格分转元
|
||||||
|
item.price = formatToFraction(item.price)
|
||||||
|
item.marketPrice = formatToFraction(item.marketPrice)
|
||||||
|
item.costPrice = formatToFraction(item.costPrice)
|
||||||
|
item.subCommissionFirstPrice = formatToFraction(item.subCommissionFirstPrice)
|
||||||
|
item.subCommissionSecondPrice = formatToFraction(item.subCommissionSecondPrice)
|
||||||
|
})
|
||||||
|
formData.value = res
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交按钮 */
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
// 三个表单逐一校验,如果有一个表单校验不通过则切换到对应表单,如果有两个及以上的情况则切换到最前面的一个并弹出提示消息
|
||||||
|
// 校验各表单
|
||||||
|
try {
|
||||||
|
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) => {
|
||||||
|
// 因为是空数据这里判断一下商品条码是否为空就行
|
||||||
|
if (sku.barCode === '') {
|
||||||
|
const index = deepCopyFormData.skus.findIndex(
|
||||||
|
(item) => JSON.stringify(item.properties) === JSON.stringify(sku.properties)
|
||||||
|
)
|
||||||
|
// 删除这条 sku
|
||||||
|
deepCopyFormData.skus.splice(index, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
deepCopyFormData.skus.forEach((item) => {
|
||||||
|
// 给sku name赋值
|
||||||
|
item.name = deepCopyFormData.name
|
||||||
|
// sku相关价格元转分
|
||||||
|
item.price = convertToInteger(item.price)
|
||||||
|
item.marketPrice = convertToInteger(item.marketPrice)
|
||||||
|
item.costPrice = convertToInteger(item.costPrice)
|
||||||
|
item.subCommissionFirstPrice = convertToInteger(item.subCommissionFirstPrice)
|
||||||
|
item.subCommissionSecondPrice = convertToInteger(item.subCommissionSecondPrice)
|
||||||
|
})
|
||||||
|
// 处理轮播图列表
|
||||||
|
const newSliderPicUrls = []
|
||||||
|
deepCopyFormData.sliderPicUrls.forEach((item) => {
|
||||||
|
// 如果是前端选的图
|
||||||
|
typeof item === 'object' ? newSliderPicUrls.push(item.url) : newSliderPicUrls.push(item)
|
||||||
|
})
|
||||||
|
deepCopyFormData.sliderPicUrls = newSliderPicUrls
|
||||||
|
// 校验都通过后提交表单
|
||||||
|
const data = deepCopyFormData as ProductSpuApi.SpuType
|
||||||
|
const id = params.spuId as number
|
||||||
|
if (!id) {
|
||||||
|
await ProductSpuApi.createSpu(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await ProductSpuApi.updateSpu(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
close()
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭按钮 */
|
||||||
|
const close = () => {
|
||||||
|
delView(unref(currentRoute))
|
||||||
|
push('/product/product-spu')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
await getDetail()
|
||||||
|
})
|
||||||
|
</script>
|
274
src/views/mall/product/spu/components/BasicInfoForm.vue
Normal file
274
src/views/mall/product/spu/components/BasicInfoForm.vue
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="productSpuBasicInfoRef" :model="formData" :rules="rules" label-width="120px">
|
||||||
|
<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">
|
||||||
|
<!-- TODO @puhui999:只能选根节点 -->
|
||||||
|
<el-form-item label="商品分类" prop="categoryId">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="formData.categoryId"
|
||||||
|
:data="categoryList"
|
||||||
|
:props="defaultProps"
|
||||||
|
check-strictly
|
||||||
|
class="w-1/1"
|
||||||
|
node-key="id"
|
||||||
|
placeholder="请选择商品分类"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="商品关键字" prop="keyword">
|
||||||
|
<el-input v-model="formData.keyword" placeholder="请输入商品关键字" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="单位" prop="unit">
|
||||||
|
<el-select v-model="formData.unit" class="w-1/1" placeholder="请选择单位">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.PRODUCT_UNIT)"
|
||||||
|
: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="introduction">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.introduction"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入商品简介"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="商品封面图" prop="picUrl">
|
||||||
|
<UploadImg v-model="formData.picUrl" height="80px" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="商品轮播图" prop="sliderPicUrls">
|
||||||
|
<UploadImgs v-model:modelValue="formData.sliderPicUrls" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<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-select>
|
||||||
|
<el-button class="ml-20px">运费模板</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="品牌" prop="brandId">
|
||||||
|
<el-select v-model="formData.brandId" placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in brandList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="商品规格" props="specType">
|
||||||
|
<el-radio-group v-model="formData.specType" @change="onChangeSpec">
|
||||||
|
<el-radio :label="false" class="radio">单规格</el-radio>
|
||||||
|
<el-radio :label="true">多规格</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="分销类型" props="subCommissionType">
|
||||||
|
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
|
||||||
|
<el-radio :label="false">默认设置</el-radio>
|
||||||
|
<el-radio :label="true" class="radio">自行设置</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<!-- 多规格添加-->
|
||||||
|
<el-col :span="24">
|
||||||
|
<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" />
|
||||||
|
</el-form-item>
|
||||||
|
<template v-if="formData.specType && propertyList.length > 0">
|
||||||
|
<el-form-item label="批量设置">
|
||||||
|
<SkuList :is-batch="true" :prop-form-data="formData" :propertyList="propertyList" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="属性列表">
|
||||||
|
<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" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="ProductSpuBasicInfoForm" setup>
|
||||||
|
import { PropType } from 'vue'
|
||||||
|
import { copyValueToTarget } from '@/utils'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
|
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 * as ProductCategoryApi from '@/api/mall/product/category'
|
||||||
|
import { getSimpleBrandList } from '@/api/mall/product/brand'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
propFormData: {
|
||||||
|
type: Object as PropType<SpuType>,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
activeName: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
const attributesAddFormRef = ref() // 添加商品属性表单
|
||||||
|
const productSpuBasicInfoRef = ref() // 表单 Ref
|
||||||
|
const propertyList = ref([]) // 商品属性列表
|
||||||
|
const skuListRef = ref() // 商品属性列表Ref
|
||||||
|
/** 调用 SkuList generateTableData 方法*/
|
||||||
|
const generateSkus = (propertyList) => {
|
||||||
|
skuListRef.value.generateTableData(propertyList)
|
||||||
|
}
|
||||||
|
const formData = reactive<SpuType>({
|
||||||
|
name: '', // 商品名称
|
||||||
|
categoryId: null, // 商品分类
|
||||||
|
keyword: '', // 关键字
|
||||||
|
unit: '', // 单位
|
||||||
|
picUrl: '', // 商品封面图
|
||||||
|
sliderPicUrls: [], // 商品轮播图
|
||||||
|
introduction: '', // 商品简介
|
||||||
|
deliveryTemplateId: 1, // 运费模版
|
||||||
|
brandId: null, // 商品品牌
|
||||||
|
specType: false, // 商品规格
|
||||||
|
subCommissionType: false, // 分销类型
|
||||||
|
skus: []
|
||||||
|
})
|
||||||
|
const rules = reactive({
|
||||||
|
name: [required],
|
||||||
|
categoryId: [required],
|
||||||
|
keyword: [required],
|
||||||
|
unit: [required],
|
||||||
|
introduction: [required],
|
||||||
|
picUrl: [required],
|
||||||
|
sliderPicUrls: [required],
|
||||||
|
// deliveryTemplateId: [required],
|
||||||
|
brandId: [required],
|
||||||
|
specType: [required],
|
||||||
|
subCommissionType: [required]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将传进来的值赋值给 formData
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.propFormData,
|
||||||
|
(data) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copyValueToTarget(formData, data)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单校验
|
||||||
|
*/
|
||||||
|
const emit = defineEmits(['update:activeName'])
|
||||||
|
const validate = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!productSpuBasicInfoRef) return
|
||||||
|
return await unref(productSpuBasicInfoRef).validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
message.warning('商品信息未完善!!')
|
||||||
|
emit('update:activeName', 'basicInfo')
|
||||||
|
// 目的截断之后的校验
|
||||||
|
throw new Error('商品信息未完善!!')
|
||||||
|
} else {
|
||||||
|
// 校验通过更新数据
|
||||||
|
Object.assign(props.propFormData, formData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defineExpose({ validate })
|
||||||
|
|
||||||
|
/** 分销类型 */
|
||||||
|
const changeSubCommissionType = () => {
|
||||||
|
// 默认为零,类型切换后也要重置为零
|
||||||
|
for (const item of formData.skus) {
|
||||||
|
item.subCommissionFirstPrice = 0
|
||||||
|
item.subCommissionSecondPrice = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选择规格 */
|
||||||
|
const onChangeSpec = () => {
|
||||||
|
// 重置商品属性列表
|
||||||
|
propertyList.value = []
|
||||||
|
// 重置sku列表
|
||||||
|
formData.skus = [
|
||||||
|
{
|
||||||
|
price: 0,
|
||||||
|
marketPrice: 0,
|
||||||
|
costPrice: 0,
|
||||||
|
barCode: '',
|
||||||
|
picUrl: '',
|
||||||
|
stock: 0,
|
||||||
|
weight: 0,
|
||||||
|
volume: 0,
|
||||||
|
subCommissionFirstPrice: 0,
|
||||||
|
subCommissionSecondPrice: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoryList = ref([]) // 分类树
|
||||||
|
const brandList = ref([]) // 精简商品品牌列表
|
||||||
|
onMounted(async () => {
|
||||||
|
// 获得分类树
|
||||||
|
const data = await ProductCategoryApi.getCategoryList({})
|
||||||
|
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||||
|
// 获取商品品牌列表
|
||||||
|
brandList.value = await getSimpleBrandList()
|
||||||
|
})
|
||||||
|
</script>
|
83
src/views/mall/product/spu/components/DescriptionForm.vue
Normal file
83
src/views/mall/product/spu/components/DescriptionForm.vue
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<el-form 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>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="DescriptionForm" setup>
|
||||||
|
import type { SpuType } from '@/api/mall/product/spu'
|
||||||
|
import { Editor } from '@/components/Editor'
|
||||||
|
import { PropType } from 'vue'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { copyValueToTarget } from '@/utils'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const props = defineProps({
|
||||||
|
propFormData: {
|
||||||
|
type: Object as PropType<SpuType>,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
activeName: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
const descriptionFormRef = ref() // 表单Ref
|
||||||
|
const formData = ref<SpuType>({
|
||||||
|
description: '' // 商品详情
|
||||||
|
})
|
||||||
|
// 表单规则
|
||||||
|
const rules = reactive({
|
||||||
|
description: [required]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 富文本编辑器如果输入过再清空会有残留,需再重置一次
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => formData.value.description,
|
||||||
|
(newValue) => {
|
||||||
|
if ('<p><br></p>' === newValue) {
|
||||||
|
formData.value.description = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* 将传进来的值赋值给formData
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.propFormData,
|
||||||
|
(data) => {
|
||||||
|
if (!data) return
|
||||||
|
// fix:三个表单组件监听赋值必须使用 copyValueToTarget 使用 formData.value = data 会监听非常多次
|
||||||
|
copyValueToTarget(formData.value, data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// fix: 去掉深度监听只有对象引用发生改变的时候才执行,解决改一动多的问题
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单校验
|
||||||
|
*/
|
||||||
|
const emit = defineEmits(['update:activeName'])
|
||||||
|
const validate = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!descriptionFormRef) return
|
||||||
|
return await unref(descriptionFormRef).validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
message.warning('商品详情为完善!!')
|
||||||
|
emit('update:activeName', 'description')
|
||||||
|
// 目的截断之后的校验
|
||||||
|
throw new Error('商品详情为完善!!')
|
||||||
|
} else {
|
||||||
|
// 校验通过更新数据
|
||||||
|
Object.assign(props.propFormData, formData.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defineExpose({ validate })
|
||||||
|
</script>
|
145
src/views/mall/product/spu/components/OtherSettingsForm.vue
Normal file
145
src/views/mall/product/spu/components/OtherSettingsForm.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="otherSettingsFormRef" :model="formData" :rules="rules" label-width="120px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="商品排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="赠送积分" prop="giveIntegral">
|
||||||
|
<el-input-number v-model="formData.giveIntegral" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="虚拟销量" prop="virtualSalesCount">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formData.virtualSalesCount"
|
||||||
|
:min="0"
|
||||||
|
placeholder="请输入虚拟销量"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="商品推荐">
|
||||||
|
<el-checkbox-group v-model="checkboxGroup" @change="onChangeGroup">
|
||||||
|
<el-checkbox v-for="(item, index) in recommendOptions" :key="index" :label="item.value">
|
||||||
|
{{ item.name }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<!-- TODO tag展示暂时不考虑排序 -->
|
||||||
|
<el-form-item label="活动优先级">
|
||||||
|
<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>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<!-- TODO @puhui999:等优惠劵 ok 在搞 -->
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="赠送优惠劵">
|
||||||
|
<el-button>选择优惠券</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="OtherSettingsForm" setup>
|
||||||
|
import type { SpuType } from '@/api/mall/product/spu'
|
||||||
|
import { PropType } from 'vue'
|
||||||
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { copyValueToTarget } from '@/utils'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
propFormData: {
|
||||||
|
type: Object as PropType<SpuType>,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
activeName: propTypes.string.def('')
|
||||||
|
})
|
||||||
|
|
||||||
|
const otherSettingsFormRef = ref() // 表单Ref
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref<SpuType>({
|
||||||
|
sort: 1, // 商品排序
|
||||||
|
giveIntegral: 1, // 赠送积分
|
||||||
|
virtualSalesCount: 1, // 虚拟销量
|
||||||
|
recommendHot: false, // 是否热卖
|
||||||
|
recommendBenefit: false, // 是否优惠
|
||||||
|
recommendBest: false, // 是否精品
|
||||||
|
recommendNew: false, // 是否新品
|
||||||
|
recommendGood: false // 是否优品
|
||||||
|
})
|
||||||
|
// 表单规则
|
||||||
|
const rules = reactive({
|
||||||
|
sort: [required],
|
||||||
|
giveIntegral: [required],
|
||||||
|
virtualSalesCount: [required]
|
||||||
|
})
|
||||||
|
const recommendOptions = [
|
||||||
|
{ name: '是否热卖', value: 'recommendHot' },
|
||||||
|
{ name: '是否优惠', value: 'recommendBenefit' },
|
||||||
|
{ name: '是否精品', value: 'recommendBest' },
|
||||||
|
{ name: '是否新品', value: 'recommendNew' },
|
||||||
|
{ name: '是否优品', value: 'recommendGood' }
|
||||||
|
] // 商品推荐选项
|
||||||
|
const checkboxGroup = ref<string[]>([]) // 选中的推荐选项
|
||||||
|
|
||||||
|
/** 选择商品后赋值 */
|
||||||
|
const onChangeGroup = () => {
|
||||||
|
recommendOptions.forEach(({ value }) => {
|
||||||
|
formData.value[value] = checkboxGroup.value.includes(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将传进来的值赋值给formData
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.propFormData,
|
||||||
|
(data) => {
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copyValueToTarget(formData.value, data)
|
||||||
|
recommendOptions.forEach(({ value }) => {
|
||||||
|
if (formData.value[value] && !checkboxGroup.value.includes(value)) {
|
||||||
|
checkboxGroup.value.push(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单校验
|
||||||
|
*/
|
||||||
|
const emit = defineEmits(['update:activeName'])
|
||||||
|
const validate = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!otherSettingsFormRef) return
|
||||||
|
return await unref(otherSettingsFormRef).validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
message.warning('商品其他设置未完善!!')
|
||||||
|
emit('update:activeName', 'otherSettings')
|
||||||
|
// 目的截断之后的校验
|
||||||
|
throw new Error('商品其他设置未完善!!')
|
||||||
|
} else {
|
||||||
|
// 校验通过更新数据
|
||||||
|
Object.assign(props.propFormData, formData.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defineExpose({ validate })
|
||||||
|
</script>
|
117
src/views/mall/product/spu/components/ProductAttributes.vue
Normal file
117
src/views/mall/product/spu/components/ProductAttributes.vue
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<el-col v-for="(item, index) in attributeList" :key="index">
|
||||||
|
<div>
|
||||||
|
<el-text class="mx-1">属性名:</el-text>
|
||||||
|
<el-tag class="mx-1" closable type="success" @close="handleCloseProperty(index)"
|
||||||
|
>{{ item.name }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-text class="mx-1">属性值:</el-text>
|
||||||
|
<el-tag
|
||||||
|
v-for="(value, valueIndex) in item.values"
|
||||||
|
:key="value.id"
|
||||||
|
class="mx-1"
|
||||||
|
closable
|
||||||
|
@close="handleCloseValue(index, valueIndex)"
|
||||||
|
>
|
||||||
|
{{ value.name }}
|
||||||
|
</el-tag>
|
||||||
|
<el-input
|
||||||
|
v-show="inputVisible(index)"
|
||||||
|
:id="`input${index}`"
|
||||||
|
:ref="setInputRef"
|
||||||
|
v-model="inputValue"
|
||||||
|
class="!w-20"
|
||||||
|
size="small"
|
||||||
|
@blur="handleInputConfirm(index, item.id)"
|
||||||
|
@keyup.enter="handleInputConfirm(index, item.id)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
v-show="!inputVisible(index)"
|
||||||
|
class="button-new-tag ml-1"
|
||||||
|
size="small"
|
||||||
|
@click="showInput(index)"
|
||||||
|
>
|
||||||
|
+ 添加
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-divider class="my-10px" />
|
||||||
|
</el-col>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" name="ProductAttributes" setup>
|
||||||
|
import { ElInput } from 'element-plus'
|
||||||
|
import * as PropertyApi from '@/api/mall/product/property'
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const inputValue = ref('') // 输入框值
|
||||||
|
const attributeIndex = ref<number | null>(null) // 获取焦点时记录当前属性项的index
|
||||||
|
// 输入框显隐控制
|
||||||
|
const inputVisible = computed(() => (index) => {
|
||||||
|
if (attributeIndex.value === null) return false
|
||||||
|
if (attributeIndex.value === index) return true
|
||||||
|
})
|
||||||
|
const inputRef = ref([]) //标签输入框Ref
|
||||||
|
/** 解决 ref 在 v-for 中的获取问题*/
|
||||||
|
const setInputRef = (el) => {
|
||||||
|
if (el === null || typeof el === 'undefined') return
|
||||||
|
// 如果不存在id相同的元素才添加
|
||||||
|
if (!inputRef.value.some((item) => item.input?.attributes.id === el.input?.attributes.id)) {
|
||||||
|
inputRef.value.push(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const attributeList = ref([]) // 商品属性列表
|
||||||
|
const props = defineProps({
|
||||||
|
propertyList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.propertyList,
|
||||||
|
(data) => {
|
||||||
|
if (!data) return
|
||||||
|
attributeList.value = data
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 删除属性值*/
|
||||||
|
const handleCloseValue = (index, valueIndex) => {
|
||||||
|
attributeList.value[index].values?.splice(valueIndex, 1)
|
||||||
|
}
|
||||||
|
/** 删除属性*/
|
||||||
|
const handleCloseProperty = (index) => {
|
||||||
|
attributeList.value?.splice(index, 1)
|
||||||
|
}
|
||||||
|
/** 显示输入框并获取焦点 */
|
||||||
|
const showInput = async (index) => {
|
||||||
|
attributeIndex.value = index
|
||||||
|
inputRef.value[index].focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
|
||||||
|
/** 输入框失去焦点或点击回车时触发 */
|
||||||
|
const handleInputConfirm = async (index, propertyId) => {
|
||||||
|
if (inputValue.value) {
|
||||||
|
// 保存属性值
|
||||||
|
try {
|
||||||
|
const id = await PropertyApi.createPropertyValue({ propertyId, name: inputValue.value })
|
||||||
|
attributeList.value[index].values.push({ id, name: inputValue.value })
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
emit('success', attributeList.value)
|
||||||
|
} catch {
|
||||||
|
message.error('添加失败,请重试') // TODO 缺少国际化
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributeIndex.value = null
|
||||||
|
inputValue.value = ''
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
v-loading="formLoading"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="80px"
|
||||||
|
>
|
||||||
|
<el-form-item label="属性名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="ProductPropertyForm" setup>
|
||||||
|
import * as PropertyApi from '@/api/mall/product/property'
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('添加商品属性') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formData = ref({
|
||||||
|
name: ''
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const attributeList = ref([]) // 商品属性列表
|
||||||
|
const props = defineProps({
|
||||||
|
propertyList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.propertyList,
|
||||||
|
(data) => {
|
||||||
|
if (!data) return
|
||||||
|
attributeList.value = data
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async () => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as PropertyApi.PropertyVO
|
||||||
|
// 检查属性是否已存在,如果有则返回属性和其下属性值
|
||||||
|
const res = await PropertyApi.getPropertyListAndValue({ name: data.name })
|
||||||
|
if (res.length === 0) {
|
||||||
|
const propertyId = await PropertyApi.createProperty(data)
|
||||||
|
attributeList.value.push({ id: propertyId, ...formData.value, values: [] })
|
||||||
|
} else {
|
||||||
|
if (res[0].values === null) {
|
||||||
|
res[0].values = []
|
||||||
|
}
|
||||||
|
attributeList.value.push(res[0]) // 因为只用一个
|
||||||
|
}
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
dialogVisible.value = false
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
name: '',
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
317
src/views/mall/product/spu/components/SkuList.vue
Normal file
317
src/views/mall/product/spu/components/SkuList.vue
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<template>
|
||||||
|
<el-table
|
||||||
|
:data="isBatch ? skuList : formData.skus"
|
||||||
|
border
|
||||||
|
class="tabNumWidth"
|
||||||
|
max-height="500"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<el-table-column align="center" fixed="left" label="图片" min-width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<UploadImg v-model="row.picUrl" height="80px" width="100%" />
|
||||||
|
</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="120"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<!-- TODO puhui999:展示成蓝色,有点区分度哈 -->
|
||||||
|
{{ row.properties[index]?.valueName }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
<el-table-column align="center" label="商品条码" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input v-model="row.barCode" class="w-100%" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="销售价(元)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.price" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="市场价(元)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.marketPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.1"
|
||||||
|
class="w-100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="成本价(元)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.costPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.1"
|
||||||
|
class="w-100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="库存" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.stock" :min="0" class="w-100%" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="重量(kg)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.weight" :min="0" :precision="2" :step="0.1" class="w-100%" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="体积(m^3)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<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">
|
||||||
|
<el-table-column align="center" label="一级返佣(元)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.subCommissionFirstPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.1"
|
||||||
|
class="w-100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="二级返佣(元)" min-width="168">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.subCommissionSecondPrice"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.1"
|
||||||
|
class="w-100%"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</template>
|
||||||
|
<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">
|
||||||
|
批量添加
|
||||||
|
</el-button>
|
||||||
|
<el-button v-else link size="small" type="primary" @click="deleteSku(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="SkuList" setup>
|
||||||
|
import { PropType } 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'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
propFormData: {
|
||||||
|
type: Object as PropType<SpuType>,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
propertyList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
isBatch: propTypes.bool.def(false) // 是否作为批量操作组件
|
||||||
|
})
|
||||||
|
const formData = ref<SpuType>() // 表单数据
|
||||||
|
const skuList = ref<SkuType[]>([
|
||||||
|
{
|
||||||
|
price: 0, // 商品价格
|
||||||
|
marketPrice: 0, // 市场价
|
||||||
|
costPrice: 0, // 成本价
|
||||||
|
barCode: '', // 商品条码
|
||||||
|
picUrl: '', // 图片地址
|
||||||
|
stock: 0, // 库存
|
||||||
|
weight: 0, // 商品重量
|
||||||
|
volume: 0, // 商品体积
|
||||||
|
subCommissionFirstPrice: 0, // 一级分销的佣金
|
||||||
|
subCommissionSecondPrice: 0 // 二级分销的佣金
|
||||||
|
}
|
||||||
|
]) // 批量添加时的临时数据
|
||||||
|
// TODO @puhui999:保存时,每个商品规格的表单要校验下。例如说,销售金额最低是 0.01 这种。
|
||||||
|
|
||||||
|
/** 批量添加 */
|
||||||
|
const batchAdd = () => {
|
||||||
|
formData.value.skus.forEach((item) => {
|
||||||
|
copyValueToTarget(item, skuList.value[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除 sku */
|
||||||
|
const deleteSku = (row) => {
|
||||||
|
const index = formData.value.skus.findIndex(
|
||||||
|
// 直接把列表转成字符串比较
|
||||||
|
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||||
|
)
|
||||||
|
formData.value.skus.splice(index, 1)
|
||||||
|
}
|
||||||
|
const tableHeaders = ref<{ prop: string; label: string }[]>([]) // 多属性表头
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将传进来的值赋值给 skuList
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
() => props.propFormData,
|
||||||
|
(data) => {
|
||||||
|
if (!data) return
|
||||||
|
formData.value = data
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 生成表数据 */
|
||||||
|
const generateTableData = (propertyList: any[]) => {
|
||||||
|
// 构建数据结构
|
||||||
|
const propertyValues = propertyList.map((item) =>
|
||||||
|
item.values.map((v) => ({
|
||||||
|
propertyId: item.id,
|
||||||
|
propertyName: item.name,
|
||||||
|
valueId: v.id,
|
||||||
|
valueName: v.name
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
// TODO @puhui:是不是 buildSkuList,这样容易理解一点哈。item 改成 sku
|
||||||
|
const buildList = build(propertyValues)
|
||||||
|
// 如果回显的 sku 属性和添加的属性不一致则重置 skus 列表
|
||||||
|
if (!validateData(propertyList)) {
|
||||||
|
// 如果不一致则重置表数据,默认添加新的属性重新生成 sku 列表
|
||||||
|
formData.value!.skus = []
|
||||||
|
}
|
||||||
|
for (const item of buildList) {
|
||||||
|
const row = {
|
||||||
|
properties: Array.isArray(item) ? item : [item], // 如果只有一个属性的话返回的是一个 property 对象
|
||||||
|
price: 0,
|
||||||
|
marketPrice: 0,
|
||||||
|
costPrice: 0,
|
||||||
|
barCode: '',
|
||||||
|
picUrl: '',
|
||||||
|
stock: 0,
|
||||||
|
weight: 0,
|
||||||
|
volume: 0,
|
||||||
|
subCommissionFirstPrice: 0,
|
||||||
|
subCommissionSecondPrice: 0
|
||||||
|
}
|
||||||
|
// 如果存在属性相同的 sku 则不做处理
|
||||||
|
const index = formData.value!.skus.findIndex(
|
||||||
|
(sku) => JSON.stringify(sku.properties) === JSON.stringify(row.properties)
|
||||||
|
)
|
||||||
|
if (index !== -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
formData.value.skus.push(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 skus 前置校验
|
||||||
|
*/
|
||||||
|
const validateData = (propertyList: any[]) => {
|
||||||
|
const skuPropertyIds = []
|
||||||
|
formData.value.skus.forEach((sku) =>
|
||||||
|
sku.properties
|
||||||
|
?.map((property) => property.propertyId)
|
||||||
|
.forEach((propertyId) => {
|
||||||
|
if (skuPropertyIds.indexOf(propertyId) === -1) {
|
||||||
|
skuPropertyIds.push(propertyId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const propertyIds = propertyList.map((item) => item.id)
|
||||||
|
return skuPropertyIds.length === propertyIds.length
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建所有排列组合 */
|
||||||
|
const build = (propertyValuesList: Property[][]) => {
|
||||||
|
if (propertyValuesList.length === 0) {
|
||||||
|
return []
|
||||||
|
} else if (propertyValuesList.length === 1) {
|
||||||
|
return propertyValuesList[0]
|
||||||
|
} else {
|
||||||
|
const result: Property[][] = []
|
||||||
|
const rest = build(propertyValuesList.slice(1))
|
||||||
|
for (let i = 0; i < propertyValuesList[0].length; i++) {
|
||||||
|
for (let j = 0; j < rest.length; j++) {
|
||||||
|
// 第一次不是数组结构,后面的都是数组结构
|
||||||
|
if (Array.isArray(rest[j])) {
|
||||||
|
result.push([propertyValuesList[0][i], ...rest[j]])
|
||||||
|
} else {
|
||||||
|
result.push([propertyValuesList[0][i], rest[j]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听属性列表,生成相关参数和表头 */
|
||||||
|
watch(
|
||||||
|
() => props.propertyList,
|
||||||
|
(propertyList) => {
|
||||||
|
// 如果不是多规格则结束
|
||||||
|
if (!formData.value.specType) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 如果当前组件作为批量添加数据使用,则重置表数据
|
||||||
|
if (props.isBatch) {
|
||||||
|
skuList.value = [
|
||||||
|
{
|
||||||
|
price: 0,
|
||||||
|
marketPrice: 0,
|
||||||
|
costPrice: 0,
|
||||||
|
barCode: '',
|
||||||
|
picUrl: '',
|
||||||
|
stock: 0,
|
||||||
|
weight: 0,
|
||||||
|
volume: 0,
|
||||||
|
subCommissionFirstPrice: 0,
|
||||||
|
subCommissionSecondPrice: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断代理对象是否为空
|
||||||
|
if (JSON.stringify(propertyList) === '[]') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 重置表头
|
||||||
|
tableHeaders.value = []
|
||||||
|
// 生成表头
|
||||||
|
propertyList.forEach((item, index) => {
|
||||||
|
// name加属性项index区分属性值
|
||||||
|
tableHeaders.value.push({ prop: `name${index}`, label: item.name })
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果回显的 sku 属性和添加的属性一致则不处理
|
||||||
|
if (validateData(propertyList)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 添加新属性没有属性值也不做处理
|
||||||
|
if (propertyList.some((item) => item.values.length === 0)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 生成 table 数据,即 sku 列表
|
||||||
|
generateTableData(propertyList)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// 暴露出生成 sku 方法,给添加属性成功时调用
|
||||||
|
defineExpose({ generateTableData })
|
||||||
|
</script>
|
15
src/views/mall/product/spu/components/index.ts
Normal file
15
src/views/mall/product/spu/components/index.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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 SkuList from './SkuList.vue'
|
||||||
|
|
||||||
|
export {
|
||||||
|
BasicInfoForm,
|
||||||
|
DescriptionForm,
|
||||||
|
OtherSettingsForm,
|
||||||
|
ProductAttributes,
|
||||||
|
ProductAttributesAddForm,
|
||||||
|
SkuList
|
||||||
|
}
|
422
src/views/mall/product/spu/index.vue
Normal file
422
src/views/mall/product/spu/index.vue
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="queryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<!-- TODO @puhui999:品牌应该是数据下拉哈 -->
|
||||||
|
<el-form-item label="品牌名称" prop="name">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.name"
|
||||||
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入品牌名称"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- TODO 分类只能选择二级分类目前还没做,还是先以联调通顺为主 -->
|
||||||
|
<!-- TODO puhui999:我们要不改成支持选择一级。如果选择一级,后端要递归查询下子分类,然后去 in? -->
|
||||||
|
<el-form-item label="商品分类" prop="categoryId">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="queryParams.categoryId"
|
||||||
|
:data="categoryList"
|
||||||
|
:props="defaultProps"
|
||||||
|
check-strictly
|
||||||
|
class="w-1/1"
|
||||||
|
node-key="id"
|
||||||
|
placeholder="请选择商品分类"
|
||||||
|
/>
|
||||||
|
</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-240px"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
type="daterange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<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-button v-hasPermi="['product:spu:create']" plain type="primary" @click="openForm">
|
||||||
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
|
新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['product:spu:export']"
|
||||||
|
:loading="exportLoading"
|
||||||
|
plain
|
||||||
|
type="success"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<Icon class="mr-5px" icon="ep:download" />
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-tabs v-model="queryParams.tabType" @tab-click="handleTabClick">
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="item in tabsData"
|
||||||
|
:key="item.type"
|
||||||
|
:label="item.name + '(' + item.count + ')'"
|
||||||
|
:name="item.type"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</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" />
|
||||||
|
</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-column align="center" label="状态" min-width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="row.status >= 0">
|
||||||
|
<el-switch
|
||||||
|
v-model="row.status"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
active-text="上架"
|
||||||
|
inactive-text="下架"
|
||||||
|
inline-prompt
|
||||||
|
@change="changeStatus(row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-tag type="info">回收站</el-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</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>
|
||||||
|
<template v-if="queryParams.tabType === 4">
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['product:spu:delete']"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(row.id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['product:spu:update']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="changeStatus(row, ProductSpuStatusEnum.DISABLE.status)"
|
||||||
|
>
|
||||||
|
恢复到仓库
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<!-- 只有不是上架和回收站的商品可以编辑 -->
|
||||||
|
<el-button
|
||||||
|
v-if="queryParams.tabType !== 0"
|
||||||
|
v-hasPermi="['product:spu:update']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm(row.id)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['product:spu:update']"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="changeStatus(row, ProductSpuStatusEnum.RECYCLE.status)"
|
||||||
|
>
|
||||||
|
加入回收站
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
:total="total"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="ProductSpu" 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 { 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'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const { currentRoute, push } = useRouter() // 路由跳转
|
||||||
|
|
||||||
|
const loading = ref(false) // 列表的加载中
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
|
// tabs 数据
|
||||||
|
const tabsData = ref([
|
||||||
|
{
|
||||||
|
count: 0,
|
||||||
|
name: '出售中商品',
|
||||||
|
type: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: 0,
|
||||||
|
name: '仓库中商品',
|
||||||
|
type: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: 0,
|
||||||
|
name: '已经售空商品',
|
||||||
|
type: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: 0,
|
||||||
|
name: '警戒库存',
|
||||||
|
type: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
count: 0,
|
||||||
|
name: '商品回收站',
|
||||||
|
type: 4
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
/** 获得每个 Tab 的数量 */
|
||||||
|
const getTabsCount = async () => {
|
||||||
|
const res = await ProductSpuApi.getTabsCount()
|
||||||
|
for (let objName in res) {
|
||||||
|
tabsData.value[Number(objName)].count = res[objName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const queryParams = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
tabType: 0
|
||||||
|
}) // 查询参数
|
||||||
|
const queryFormRef = ref() // 搜索的表单Ref
|
||||||
|
|
||||||
|
const handleTabClick = (tab: TabsPaneContext) => {
|
||||||
|
queryParams.value.tabType = tab.paneName
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更改 SPU 状态
|
||||||
|
*
|
||||||
|
* @param row
|
||||||
|
* @param status 更改前的值
|
||||||
|
*/
|
||||||
|
const changeStatus = async (row, status?: number) => {
|
||||||
|
const deepCopyValue = cloneDeep(unref(row))
|
||||||
|
if (typeof status !== 'undefined') deepCopyValue.status = status
|
||||||
|
try {
|
||||||
|
let text = ''
|
||||||
|
switch (deepCopyValue.status) {
|
||||||
|
case ProductSpuStatusEnum.DISABLE.status:
|
||||||
|
text = ProductSpuStatusEnum.DISABLE.name
|
||||||
|
break
|
||||||
|
case ProductSpuStatusEnum.ENABLE.status:
|
||||||
|
text = ProductSpuStatusEnum.ENABLE.name
|
||||||
|
break
|
||||||
|
case ProductSpuStatusEnum.RECYCLE.status:
|
||||||
|
text = `加入${ProductSpuStatusEnum.RECYCLE.name}`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
await message.confirm(
|
||||||
|
deepCopyValue.status === -1
|
||||||
|
? `确认要将[${row.name}]${text}吗?`
|
||||||
|
: row.status === -1 // 再判断一次原对象是否等于-1,例: 把回收站中的商品恢复到仓库中,事件触发时原对象status为-1 深拷贝对象status被赋值为0
|
||||||
|
? `确认要将[${row.name}]恢复到仓库吗?`
|
||||||
|
: `确认要${text}[${row.name}]吗?`
|
||||||
|
)
|
||||||
|
await ProductSpuApi.updateStatus({ id: deepCopyValue.id, status: deepCopyValue.status })
|
||||||
|
message.success('更新状态成功')
|
||||||
|
// 刷新 tabs 数据
|
||||||
|
await getTabsCount()
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {
|
||||||
|
// 取消更改状态时回显数据
|
||||||
|
row.status =
|
||||||
|
row.status === ProductSpuStatusEnum.DISABLE.status
|
||||||
|
? ProductSpuStatusEnum.ENABLE.status
|
||||||
|
: ProductSpuStatusEnum.DISABLE.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await ProductSpuApi.deleteSpu(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新tabs数据
|
||||||
|
await getTabsCount()
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 商品图预览 */
|
||||||
|
const imagePreview = (imgUrl: string) => {
|
||||||
|
createImageViewer({
|
||||||
|
urlList: [imgUrl]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增或修改
|
||||||
|
*
|
||||||
|
* @param id 商品 SPU 编号
|
||||||
|
*/
|
||||||
|
const openForm = (id?: number) => {
|
||||||
|
// 修改
|
||||||
|
if (typeof id === 'number') {
|
||||||
|
push('/product/productSpuEdit/' + id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 新增
|
||||||
|
push('/product/productSpuAdd')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看商品详情
|
||||||
|
*/
|
||||||
|
const openDetail = () => {
|
||||||
|
message.alert('查看详情未完善!!!')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await ProductSpuApi.exportSpu(queryParams)
|
||||||
|
download.excel(data, '商品列表.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化更新列表 TODO @puhui999:这个是必须加的么?fix: 因为编辑表单是以路由的方式打开,保存表单后列表不会刷新
|
||||||
|
watch(
|
||||||
|
() => currentRoute.value,
|
||||||
|
() => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const categoryList = ref() // 分类树
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(async () => {
|
||||||
|
await getTabsCount()
|
||||||
|
await getList()
|
||||||
|
// 获得分类树
|
||||||
|
const data = await ProductCategoryApi.getCategoryList({})
|
||||||
|
categoryList.value = handleTree(data, 'id', 'parentId')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.demo-table-expand {
|
||||||
|
padding-left: 42px;
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
width: 82px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #99a9bf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
124
src/views/mall/trade/delivery/express/ExpressForm.vue
Normal file
124
src/views/mall/trade/delivery/express/ExpressForm.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<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="code">
|
||||||
|
<el-input v-model="formData.code" placeholder="请输入快递编码" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="快递公司名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入快递名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<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-form-item label="分类排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
<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-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" name="ExpressForm">
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express'
|
||||||
|
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,
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
logo: '',
|
||||||
|
sort: 0,
|
||||||
|
status: CommonStatusEnum.ENABLE
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
code: [{ required: true, message: '快递编码不能为空', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
|
||||||
|
logo: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }],
|
||||||
|
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }],
|
||||||
|
status: [{ 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 DeliveryExpressApi.getDeliveryExpress(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 DeliveryExpressApi.DeliveryExpressVO
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await DeliveryExpressApi.createDeliveryExpress(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await DeliveryExpressApi.updateDeliveryExpress(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
picUrl: '',
|
||||||
|
bigPicUrl: '',
|
||||||
|
status: CommonStatusEnum.ENABLE
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
184
src/views/mall/trade/delivery/express/index.vue
Normal file
184
src/views/mall/trade/delivery/express/index.vue
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="快递公司编号" prop="code">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.code"
|
||||||
|
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>
|
||||||
|
<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:express:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['trade:delivery:express: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="code" />
|
||||||
|
<el-table-column label="快递公司名称" prop="name" />
|
||||||
|
<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="排序" align="center" prop="sort" />
|
||||||
|
<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:express:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['trade:delivery:express:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<ExpressForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="Express">
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express'
|
||||||
|
import ExpressForm from './ExpressForm.vue'
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
code: '',
|
||||||
|
name: ''
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await DeliveryExpressApi.getDeliveryExpressPage(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 DeliveryExpressApi.deleteDeliveryExpress(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await DeliveryExpressApi.exportDeliveryExpressApi(queryParams)
|
||||||
|
download.excel(data, '快递公司.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="80%">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="80px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="模板名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入模板名称" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="计费方式" prop="chargeMode">
|
||||||
|
<el-radio-group v-model="formData.chargeMode" @change="changeChargeMode">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="运费" prop="templateCharge">
|
||||||
|
<el-table border style="width: 100%" :data="formData.templateCharge">
|
||||||
|
<el-table-column align="center" label="区域" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
|
||||||
|
<el-tree-select
|
||||||
|
v-model="row.areaIds"
|
||||||
|
lazy
|
||||||
|
:load="loadChargeArea"
|
||||||
|
:props="defaultProps"
|
||||||
|
multiple
|
||||||
|
node-key="id"
|
||||||
|
check-strictly
|
||||||
|
show-checkbox
|
||||||
|
check-on-click-node
|
||||||
|
:render-after-expand="false"
|
||||||
|
:cache-data="areaCache"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
align="center"
|
||||||
|
:label="columnTitle.startCountTitle"
|
||||||
|
width="180"
|
||||||
|
prop="startCount"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.startCount" :min="1" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="180" align="center" label="运费(元)" prop="startPrice">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.startPrice" :min="1" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
width="180"
|
||||||
|
align="center"
|
||||||
|
:label="columnTitle.extraCountTitle"
|
||||||
|
prop="extraCount"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.extraCount" :min="1" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column width="180" align="center" label="续费(元)" prop="extraPrice">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.extraPrice" :min="1" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="danger" @click="deleteChargeArea(scope.$index)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" plain @click="addChargeArea()">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 添加区域
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="包邮区域" prop="templateFree">
|
||||||
|
<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
|
||||||
|
lazy
|
||||||
|
:load="loadFreeArea"
|
||||||
|
:props="defaultProps"
|
||||||
|
node-key="id"
|
||||||
|
check-strictly
|
||||||
|
show-checkbox
|
||||||
|
check-on-click-node
|
||||||
|
:render-after-expand="true"
|
||||||
|
:cache-data="areaCache"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" :label="columnTitle.freeCountTitle" prop="freeCount">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.freeCount" :min="1" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="包邮金额(元)" prop="freePrice">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number v-model="row.freePrice" :min="1" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="danger" @click="deleteFreeArea(scope.$index)"> 删除 </el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" plain @click="addFreeArea()">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 添加区域
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" controls-position="right" :min="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 { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
|
||||||
|
import { defaultProps } from '@/utils/tree'
|
||||||
|
import { yuanToFen, fenToYuan } from '@/utils'
|
||||||
|
import { getChildrenArea, getAreaListByIds } from '@/api/system/area'
|
||||||
|
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 formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
chargeMode: 1,
|
||||||
|
sort: 0,
|
||||||
|
templateCharge: [],
|
||||||
|
templateFree: []
|
||||||
|
})
|
||||||
|
const columnTitleMap = new Map()
|
||||||
|
const columnTitle = ref({
|
||||||
|
startCountTitle: '首件',
|
||||||
|
extraCountTitle: '续件',
|
||||||
|
freeCountTitle: '包邮件数'
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],
|
||||||
|
chargeMode: [{ required: true, message: '配送计费方式不能为空', trigger: 'blur' }],
|
||||||
|
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const areaCache = ref([]) //由于区域节点懒加载,已选区域节点需要缓存展示
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
try {
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
formData.value = await DeliveryExpressTemplateApi.getDeliveryExpressTemplate(id)
|
||||||
|
columnTitle.value = columnTitleMap.get(formData.value.chargeMode)
|
||||||
|
const chargeAreaIds = []
|
||||||
|
const freeAreaIds = []
|
||||||
|
formData.value.templateCharge.forEach((item) => {
|
||||||
|
for (let i = 0; i < item.areaIds.length; i++) {
|
||||||
|
if (!chargeAreaIds.includes(item.areaIds[i])) {
|
||||||
|
chargeAreaIds.push(item.areaIds[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//前端价格以元展示
|
||||||
|
item.startPrice = fenToYuan(item.startPrice)
|
||||||
|
item.extraPrice = fenToYuan(item.extraPrice)
|
||||||
|
})
|
||||||
|
formData.value.templateFree.forEach((item) => {
|
||||||
|
for (let i = 0; i < item.areaIds.length; i++) {
|
||||||
|
if (!chargeAreaIds.includes(item.areaIds[i]) && !freeAreaIds.includes(item.areaIds[i])) {
|
||||||
|
freeAreaIds.push(item.areaIds[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.freePrice = fenToYuan(item.freePrice)
|
||||||
|
})
|
||||||
|
//已选的区域节点
|
||||||
|
const areaIds = chargeAreaIds.concat(freeAreaIds)
|
||||||
|
//区域节点,懒加载方式。 已选节点需要缓存展示
|
||||||
|
areaCache.value = await getAreaListByIds(areaIds.join(','))
|
||||||
|
}
|
||||||
|
} 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 DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
|
||||||
|
data.templateCharge.forEach((item) => {
|
||||||
|
//前端价格以元展示,提交到后端。用分计算
|
||||||
|
item.startPrice = yuanToFen(item.startPrice)
|
||||||
|
item.extraPrice = yuanToFen(item.extraPrice)
|
||||||
|
})
|
||||||
|
data.templateFree.forEach((item) => {
|
||||||
|
item.freePrice = yuanToFen(item.freePrice)
|
||||||
|
})
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await DeliveryExpressTemplateApi.createDeliveryExpressTemplate(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await DeliveryExpressTemplateApi.updateDeliveryExpressTemplate(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
chargeMode: 1,
|
||||||
|
templateCharge: [
|
||||||
|
{
|
||||||
|
areaIds: [1],
|
||||||
|
startCount: 2,
|
||||||
|
startPrice: 5,
|
||||||
|
extraCount: 5,
|
||||||
|
extraPrice: 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
templateFree: [],
|
||||||
|
sort: 0
|
||||||
|
}
|
||||||
|
columnTitle.value = columnTitleMap.get(1)
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
/** 配送计费方法改变 */
|
||||||
|
const changeChargeMode = (chargeMode: number) => {
|
||||||
|
columnTitle.value = columnTitleMap.get(chargeMode)
|
||||||
|
}
|
||||||
|
const defaultArea = [{ id: 1, name: '全国', disabled: false }]
|
||||||
|
|
||||||
|
/** 初始化数据 */
|
||||||
|
const initData = async () => {
|
||||||
|
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
|
||||||
|
// formLoading.value = true
|
||||||
|
// try {
|
||||||
|
// const data = await getAreaTree()
|
||||||
|
// areaTree = data
|
||||||
|
// console.log('areaTree', areaTree)
|
||||||
|
// } finally {
|
||||||
|
// formLoading.value = false
|
||||||
|
// }
|
||||||
|
//表头标题和计费方式的映射
|
||||||
|
columnTitleMap.set(1, {
|
||||||
|
startCountTitle: '首件',
|
||||||
|
extraCountTitle: '续件',
|
||||||
|
freeCountTitle: '包邮件数'
|
||||||
|
})
|
||||||
|
columnTitleMap.set(2, {
|
||||||
|
startCountTitle: '首件重量(kg)',
|
||||||
|
extraCountTitle: '续件重量(kg)',
|
||||||
|
freeCountTitle: '包邮重量(kg)'
|
||||||
|
})
|
||||||
|
columnTitleMap.set(3, {
|
||||||
|
startCountTitle: '首件体积(m³)',
|
||||||
|
extraCountTitle: '续件体积(m³)',
|
||||||
|
freeCountTitle: '包邮体积(m³)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 懒加载运费区域树 */
|
||||||
|
const loadChargeArea = async (node, resolve) => {
|
||||||
|
//已选区域需要禁止再次选择
|
||||||
|
const areaIds = []
|
||||||
|
formData.value.templateCharge.forEach((item) => {
|
||||||
|
if (item.areaIds.length > 0) {
|
||||||
|
item.areaIds.forEach((areaId) => areaIds.push(areaId))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (node.isLeaf) return resolve([])
|
||||||
|
const length = node.data.length
|
||||||
|
if (length === 0) {
|
||||||
|
const data = cloneDeep(defaultArea)
|
||||||
|
const item = data[0]
|
||||||
|
if (areaIds.includes(item.id)) {
|
||||||
|
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||||
|
//item.disabled = true
|
||||||
|
}
|
||||||
|
resolve(data)
|
||||||
|
} else {
|
||||||
|
const id = node.data.id
|
||||||
|
const data = await getChildrenArea(id)
|
||||||
|
data.forEach((item) => {
|
||||||
|
if (areaIds.includes(item.id)) {
|
||||||
|
//item.disabled = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resolve(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 懒加载包邮区域树 */
|
||||||
|
const loadFreeArea = async (node, resolve) => {
|
||||||
|
if (node.isLeaf) return resolve([])
|
||||||
|
//已选区域需要禁止再次选择
|
||||||
|
const areaIds = []
|
||||||
|
formData.value.templateFree.forEach((item) => {
|
||||||
|
if (item.areaIds.length > 0) {
|
||||||
|
item.areaIds.forEach((areaId) => areaIds.push(areaId))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const length = node.data.length
|
||||||
|
if (length === 0) {
|
||||||
|
// 为空,从全国开始选择。全国 id == 1
|
||||||
|
const data = cloneDeep(defaultArea)
|
||||||
|
const item = data[0]
|
||||||
|
if (areaIds.includes(item.id)) {
|
||||||
|
//item.disabled = true
|
||||||
|
}
|
||||||
|
resolve(data)
|
||||||
|
} else {
|
||||||
|
const id = node.data.id
|
||||||
|
const data = await getChildrenArea(id)
|
||||||
|
//已选区域需要禁止再次选择
|
||||||
|
data.forEach((item) => {
|
||||||
|
if (areaIds.includes(item.id)) {
|
||||||
|
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
|
||||||
|
//item.disabled = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
resolve(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** 添加计费区域 */
|
||||||
|
const addChargeArea = () => {
|
||||||
|
const data = formData.value
|
||||||
|
data.templateCharge.push({
|
||||||
|
areaIds: [],
|
||||||
|
startCount: 1,
|
||||||
|
startPrice: 1,
|
||||||
|
extraCount: 1,
|
||||||
|
extraPrice: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** 删除计费区域 */
|
||||||
|
const deleteChargeArea = (index) => {
|
||||||
|
const data = formData.value
|
||||||
|
data.templateCharge.splice(index, 1)
|
||||||
|
}
|
||||||
|
/** 添加包邮区域 */
|
||||||
|
const addFreeArea = () => {
|
||||||
|
const data = formData.value
|
||||||
|
data.templateFree.push({
|
||||||
|
areaIds: [],
|
||||||
|
freeCount: 1,
|
||||||
|
freePrice: 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** 删除包邮区域 */
|
||||||
|
const deleteFreeArea = (index) => {
|
||||||
|
const data = formData.value
|
||||||
|
data.templateFree.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
initData()
|
||||||
|
})
|
||||||
|
</script>
|
160
src/views/mall/trade/delivery/expressTemplate/index.vue
Normal file
160
src/views/mall/trade/delivery/expressTemplate/index.vue
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<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="chargeMode">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.chargeMode"
|
||||||
|
placeholder="计费方式"
|
||||||
|
clearable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||||
|
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['trade:delivery:express-template:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" 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="模板名称" prop="name" />
|
||||||
|
<el-table-column label="计费方式" prop="chargeMode" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.EXPRESS_CHARGE_MODE" :value="scope.row.chargeMode" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="排序" prop="sort" />
|
||||||
|
<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:express-template:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['trade:delivery:express-template:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<ExpressTemplateForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="DeliveryExpressTemplate">
|
||||||
|
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'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
name: '',
|
||||||
|
chargeMode: undefined
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await DeliveryExpressTemplateApi.getDeliveryExpressTemplatePage(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 DeliveryExpressTemplateApi.deleteDeliveryExpressTemplate(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
@ -122,7 +122,9 @@ const open = async (type: string, id?: number, dictType?: string) => {
|
|||||||
dialogTitle.value = t('action.' + type)
|
dialogTitle.value = t('action.' + type)
|
||||||
formType.value = type
|
formType.value = type
|
||||||
resetForm()
|
resetForm()
|
||||||
formData.value.dictType = dictType
|
if (dictType) {
|
||||||
|
formData.value.dictType = dictType
|
||||||
|
}
|
||||||
// 修改时,设置数据
|
// 修改时,设置数据
|
||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
@ -2,36 +2,36 @@
|
|||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-form
|
<el-form
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
ref="queryFormRef"
|
||||||
:inline="true"
|
:inline="true"
|
||||||
|
:model="queryParams"
|
||||||
|
class="-mb-15px"
|
||||||
label-width="68px"
|
label-width="68px"
|
||||||
>
|
>
|
||||||
<el-form-item label="字典名称" prop="name">
|
<el-form-item label="字典名称" prop="name">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
placeholder="请输入字典名称"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入字典名称"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="字典类型" prop="type">
|
<el-form-item label="字典类型" prop="type">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.type"
|
v-model="queryParams.type"
|
||||||
placeholder="请输入字典类型"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入字典类型"
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="queryParams.status"
|
v-model="queryParams.status"
|
||||||
placeholder="请选择字典状态"
|
|
||||||
clearable
|
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择字典状态"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
@ -44,33 +44,41 @@
|
|||||||
<el-form-item label="创建时间" prop="createTime">
|
<el-form-item label="创建时间" prop="createTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryParams.createTime"
|
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')]"
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||||
class="!w-240px"
|
class="!w-240px"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
type="daterange"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<el-button @click="handleQuery">
|
||||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
<el-button
|
搜索
|
||||||
type="primary"
|
</el-button>
|
||||||
plain
|
<el-button @click="resetQuery">
|
||||||
@click="openForm('create')"
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
v-hasPermi="['system:dict:create']"
|
重置
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
v-hasPermi="['system:dict:create']"
|
||||||
plain
|
plain
|
||||||
@click="handleExport"
|
type="primary"
|
||||||
:loading="exportLoading"
|
@click="openForm('create')"
|
||||||
v-hasPermi="['system:dict:export']"
|
|
||||||
>
|
>
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
|
新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:dict:export']"
|
||||||
|
:loading="exportLoading"
|
||||||
|
plain
|
||||||
|
type="success"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<Icon class="mr-5px" icon="ep:download" />
|
||||||
|
导出
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -79,29 +87,29 @@
|
|||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list">
|
<el-table v-loading="loading" :data="list">
|
||||||
<el-table-column label="字典编号" align="center" prop="id" />
|
<el-table-column align="center" label="字典编号" prop="id" />
|
||||||
<el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip />
|
<el-table-column align="center" label="字典名称" prop="name" show-overflow-tooltip />
|
||||||
<el-table-column label="字典类型" align="center" prop="type" width="300" />
|
<el-table-column align="center" label="字典类型" prop="type" width="300" />
|
||||||
<el-table-column label="状态" align="center" prop="status">
|
<el-table-column align="center" label="状态" prop="status">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="备注" align="center" prop="remark" />
|
<el-table-column align="center" label="备注" prop="remark" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="创建时间"
|
|
||||||
:formatter="dateFormatter"
|
:formatter="dateFormatter"
|
||||||
align="center"
|
align="center"
|
||||||
|
label="创建时间"
|
||||||
prop="createTime"
|
prop="createTime"
|
||||||
width="180"
|
width="180"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column align="center" label="操作">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
|
v-hasPermi="['system:dict:update']"
|
||||||
link
|
link
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openForm('update', scope.row.id)"
|
@click="openForm('update', scope.row.id)"
|
||||||
v-hasPermi="['system:dict:update']"
|
|
||||||
>
|
>
|
||||||
修改
|
修改
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -109,10 +117,10 @@
|
|||||||
<el-button link type="primary">数据</el-button>
|
<el-button link type="primary">数据</el-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
<el-button
|
<el-button
|
||||||
|
v-hasPermi="['system:dict:delete']"
|
||||||
link
|
link
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDelete(scope.row.id)"
|
@click="handleDelete(scope.row.id)"
|
||||||
v-hasPermi="['system:dict:delete']"
|
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -121,9 +129,9 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination
|
<Pagination
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
v-model:limit="queryParams.pageSize"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
:total="total"
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
@ -132,12 +140,13 @@
|
|||||||
<DictTypeForm ref="formRef" @success="getList" />
|
<DictTypeForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="SystemDictType">
|
<script lang="ts" name="SystemDictType" setup>
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import * as DictTypeApi from '@/api/system/dict/dict.type'
|
import * as DictTypeApi from '@/api/system/dict/dict.type'
|
||||||
import DictTypeForm from './DictTypeForm.vue'
|
import DictTypeForm from './DictTypeForm.vue'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
<el-select v-model="formData.type" clearable placeholder="请选择公告类型">
|
<el-select v-model="formData.type" clearable placeholder="请选择公告类型">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)"
|
||||||
:key="parseInt(dict.value)"
|
:key="parseInt(dict.value as any)"
|
||||||
:label="dict.label"
|
:label="dict.label"
|
||||||
:value="parseInt(dict.value)"
|
:value="parseInt(dict.value as any)"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -27,9 +27,9 @@
|
|||||||
<el-select v-model="formData.status" clearable placeholder="请选择状态">
|
<el-select v-model="formData.status" clearable placeholder="请选择状态">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="parseInt(dict.value)"
|
:key="parseInt(dict.value as any)"
|
||||||
:label="dict.label"
|
:label="dict.label"
|
||||||
:value="parseInt(dict.value)"
|
:value="parseInt(dict.value as any)"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
@ -44,7 +44,6 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" name="SystemNotifyTemplateSendForm" setup>
|
<script lang="ts" name="SystemNotifyTemplateSendForm" setup>
|
||||||
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
|
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import * as NotifyTemplateApi from '@/api/system/notify/template'
|
import * as NotifyTemplateApi from '@/api/system/notify/template'
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
@ -102,8 +101,8 @@ const submitForm = async () => {
|
|||||||
// 提交请求
|
// 提交请求
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = formData.value as SmsTemplateApi.SendSmsReqVO
|
const data = formData.value as unknown as NotifyTemplateApi.NotifySendReqVO
|
||||||
const logId = await SmsTemplateApi.sendSms(data)
|
const logId = await NotifyTemplateApi.sendNotify(data)
|
||||||
if (logId) {
|
if (logId) {
|
||||||
message.success('提交发送成功!发送结果,见发送日志编号:' + logId)
|
message.success('提交发送成功!发送结果,见发送日志编号:' + logId)
|
||||||
}
|
}
|
||||||
@ -121,7 +120,7 @@ const resetForm = () => {
|
|||||||
mobile: '',
|
mobile: '',
|
||||||
templateCode: '',
|
templateCode: '',
|
||||||
templateParams: new Map()
|
templateParams: new Map()
|
||||||
}
|
} as any
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
<el-button
|
<el-button
|
||||||
link
|
link
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleForceLogout(scope.row.id)"
|
@click="handleForceLogout(scope.row.accessToken)"
|
||||||
v-hasPermi="['system:oauth2-token:delete']"
|
v-hasPermi="['system:oauth2-token:delete']"
|
||||||
>
|
>
|
||||||
强退
|
强退
|
||||||
@ -142,12 +142,12 @@ const resetQuery = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 强制退出操作 */
|
/** 强制退出操作 */
|
||||||
const handleForceLogout = async (id: number) => {
|
const handleForceLogout = async (accessToken: string) => {
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.confirm('是否要强制退出用户')
|
await message.confirm('是否要强制退出用户')
|
||||||
// 发起删除
|
// 发起删除
|
||||||
await OAuth2AccessTokenApi.deleteAccessToken(id)
|
await OAuth2AccessTokenApi.deleteAccessToken(accessToken)
|
||||||
message.success(t('common.success'))
|
message.success(t('common.success'))
|
||||||
// 刷新列表
|
// 刷新列表
|
||||||
await getList()
|
await getList()
|
||||||
|
@ -117,7 +117,7 @@ const resetForm = () => {
|
|||||||
sort: undefined,
|
sort: undefined,
|
||||||
status: CommonStatusEnum.ENABLE,
|
status: CommonStatusEnum.ENABLE,
|
||||||
remark: ''
|
remark: ''
|
||||||
}
|
} as any
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -2,7 +2,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
plugins: ['stylelint-order'],
|
plugins: ['stylelint-order'],
|
||||||
customSyntax: 'postcss-html',
|
customSyntax: 'postcss-html',
|
||||||
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
|
extends: ['stylelint-config-standard'],
|
||||||
rules: {
|
rules: {
|
||||||
'selector-pseudo-class-no-unknown': [
|
'selector-pseudo-class-no-unknown': [
|
||||||
true,
|
true,
|
||||||
|
Loading…
Reference in New Issue
Block a user