mirror of
				https://gitee.com/hhyykk/ipms-sjy-ui.git
				synced 2025-11-04 04:08:44 +08:00 
			
		
		
		
	Merge remote-tracking branch 'yudao/dev' into dev
This commit is contained in:
		
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@@ -11,6 +11,7 @@
 | 
			
		||||
 | 
			
		||||
* nodejs > 16.0.0 && pnpm > 7.30.0
 | 
			
		||||
* 演示地址【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>
 | 
			
		||||
* 启动文档:<https://doc.iocoder.cn/quick-start/>
 | 
			
		||||
* 视频教程:<https://doc.iocoder.cn/video/>
 | 
			
		||||
@@ -19,8 +20,8 @@
 | 
			
		||||
 | 
			
		||||
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
 | 
			
		||||
 | 
			
		||||
* 采用 [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)
 | 
			
		||||
* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 实现
 | 
			
		||||
* 改换 saas,自动引入等功能
 | 
			
		||||
* 使用 Element Plus 免费开源的中后台模版,具备如下特性:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
@@ -38,11 +39,11 @@
 | 
			
		||||
| 框架                                                                   | 说明               | 版本     |
 | 
			
		||||
|----------------------------------------------------------------------|------------------|--------|
 | 
			
		||||
| [Vue](https://staging-cn.vuejs.org/)                                 | Vue 框架           | 3.2.47 |
 | 
			
		||||
| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具          | 4.1.4  |
 | 
			
		||||
| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus     | 2.2.34 |
 | 
			
		||||
| [TypeScript](https://www.typescriptlang.org/docs/)                   | JavaScript 的超集   | 4.9.5  |
 | 
			
		||||
| [pinia](https://pinia.vuejs.org/)                                    | Vue 存储库 替代 vuex5 | 2.0.33 |
 | 
			
		||||
| [vueuse](https://vueuse.org/)                                        | 常用工具集            | 9.13.0 |
 | 
			
		||||
| [Vite](https://cn.vitejs.dev//)                                      | 开发与构建工具          | 4.3.1  |
 | 
			
		||||
| [Element Plus](https://element-plus.org/zh-CN/)                      | Element Plus     | 2.3.3 |
 | 
			
		||||
| [TypeScript](https://www.typescriptlang.org/docs/)                   | JavaScript 的超集   | 5.0.4  |
 | 
			
		||||
| [pinia](https://pinia.vuejs.org/)                                    | Vue 存储库 替代 vuex5 | 2.0.35 |
 | 
			
		||||
| [vueuse](https://vueuse.org/)                                        | 常用工具集            | 10.1.0 |
 | 
			
		||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化              | 9.2.2  |
 | 
			
		||||
| [vue-router](https://router.vuejs.org/)                              | Vue 路由           | 4.1.6  |
 | 
			
		||||
| [windicss](https://cn.windicss.org/)                                 | 下一代工具优先的 CSS 框架  | 3.5.6  |
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ const include = [
 | 
			
		||||
  'cropperjs',
 | 
			
		||||
  'lodash-es',
 | 
			
		||||
  'nprogress',
 | 
			
		||||
  'animate.css',
 | 
			
		||||
  'web-storage-cache',
 | 
			
		||||
  '@iconify/iconify',
 | 
			
		||||
  '@vueuse/core',
 | 
			
		||||
@@ -33,38 +32,58 @@ const include = [
 | 
			
		||||
  'element-plus/es',
 | 
			
		||||
  'element-plus/es/locale/lang/zh-cn',
 | 
			
		||||
  'element-plus/es/locale/lang/en',
 | 
			
		||||
  'element-plus/es/components/backtop/style/index',
 | 
			
		||||
  'element-plus/es/components/form/style/index',
 | 
			
		||||
  'element-plus/es/components/radio-group/style/index',
 | 
			
		||||
  'element-plus/es/components/radio/style/index',
 | 
			
		||||
  'element-plus/es/components/checkbox/style/index',
 | 
			
		||||
  'element-plus/es/components/checkbox-group/style/index',
 | 
			
		||||
  'element-plus/es/components/switch/style/index',
 | 
			
		||||
  'element-plus/es/components/time-picker/style/index',
 | 
			
		||||
  'element-plus/es/components/date-picker/style/index',
 | 
			
		||||
  'element-plus/es/components/col/style/index',
 | 
			
		||||
  'element-plus/es/components/form-item/style/index',
 | 
			
		||||
  'element-plus/es/components/alert/style/index',
 | 
			
		||||
  'element-plus/es/components/breadcrumb/style/index',
 | 
			
		||||
  'element-plus/es/components/select/style/index',
 | 
			
		||||
  'element-plus/es/components/input/style/index',
 | 
			
		||||
  'element-plus/es/components/breadcrumb-item/style/index',
 | 
			
		||||
  'element-plus/es/components/tag/style/index',
 | 
			
		||||
  'element-plus/es/components/pagination/style/index',
 | 
			
		||||
  'element-plus/es/components/table/style/index',
 | 
			
		||||
  'element-plus/es/components/table-column/style/index',
 | 
			
		||||
  'element-plus/es/components/card/style/index',
 | 
			
		||||
  'element-plus/es/components/row/style/index',
 | 
			
		||||
  'element-plus/es/components/button/style/index',
 | 
			
		||||
  'element-plus/es/components/menu/style/index',
 | 
			
		||||
  'element-plus/es/components/sub-menu/style/index',
 | 
			
		||||
  'element-plus/es/components/menu-item/style/index',
 | 
			
		||||
  'element-plus/es/components/option/style/index',
 | 
			
		||||
  'element-plus/es/components/dropdown/style/index',
 | 
			
		||||
  'element-plus/es/components/dropdown-menu/style/index',
 | 
			
		||||
  'element-plus/es/components/dropdown-item/style/index',
 | 
			
		||||
  'element-plus/es/components/skeleton/style/index',
 | 
			
		||||
 | 
			
		||||
  'element-plus/es/components/backtop/style/css',
 | 
			
		||||
  'element-plus/es/components/form/style/css',
 | 
			
		||||
  'element-plus/es/components/radio-group/style/css',
 | 
			
		||||
  'element-plus/es/components/radio/style/css',
 | 
			
		||||
  'element-plus/es/components/checkbox/style/css',
 | 
			
		||||
  'element-plus/es/components/checkbox-group/style/css',
 | 
			
		||||
  'element-plus/es/components/switch/style/css',
 | 
			
		||||
  'element-plus/es/components/time-picker/style/css',
 | 
			
		||||
  'element-plus/es/components/date-picker/style/css',
 | 
			
		||||
  'element-plus/es/components/descriptions/style/css',
 | 
			
		||||
  'element-plus/es/components/descriptions-item/style/css',
 | 
			
		||||
  'element-plus/es/components/link/style/css',
 | 
			
		||||
  'element-plus/es/components/tooltip/style/css',
 | 
			
		||||
  'element-plus/es/components/drawer/style/css',
 | 
			
		||||
  'element-plus/es/components/dialog/style/css',
 | 
			
		||||
  'element-plus/es/components/checkbox-button/style/css',
 | 
			
		||||
  'element-plus/es/components/option-group/style/css',
 | 
			
		||||
  'element-plus/es/components/radio-button/style/css',
 | 
			
		||||
  'element-plus/es/components/cascader/style/css',
 | 
			
		||||
  'element-plus/es/components/color-picker/style/css',
 | 
			
		||||
  'element-plus/es/components/input-number/style/css',
 | 
			
		||||
  'element-plus/es/components/rate/style/css',
 | 
			
		||||
  'element-plus/es/components/select-v2/style/css',
 | 
			
		||||
  'element-plus/es/components/tree-select/style/css',
 | 
			
		||||
  'element-plus/es/components/slider/style/css',
 | 
			
		||||
  'element-plus/es/components/time-select/style/css',
 | 
			
		||||
  'element-plus/es/components/autocomplete/style/css',
 | 
			
		||||
  'element-plus/es/components/image-viewer/style/css',
 | 
			
		||||
  'element-plus/es/components/upload/style/css',
 | 
			
		||||
  'element-plus/es/components/col/style/css',
 | 
			
		||||
  '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/backtop/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-item/style/css',
 | 
			
		||||
  'element-plus/es/components/image/style/css',
 | 
			
		||||
  'element-plus/es/components/tag/style/css',
 | 
			
		||||
  'element-plus/es/components/dialog/style/css',
 | 
			
		||||
  'element-plus/es/components/form/style/css',
 | 
			
		||||
  'element-plus/es/components/form-item/style/css',
 | 
			
		||||
  'element-plus/es/components/card/style/css',
 | 
			
		||||
  'element-plus/es/components/tooltip/style/css',
 | 
			
		||||
  'element-plus/es/components/radio-group/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'
 | 
			
		||||
  'element-plus/es/components/collapse-transition/style/css',
 | 
			
		||||
  'element-plus/es/components/timeline/style/css',
 | 
			
		||||
  'element-plus/es/components/timeline-item/style/css',
 | 
			
		||||
  'element-plus/es/components/collapse/style/css',
 | 
			
		||||
  'element-plus/es/components/collapse-item/style/css',
 | 
			
		||||
  'element-plus/es/components/button-group/style/css',
 | 
			
		||||
  'element-plus/es/components/text/style/css'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
const exclude = ['@iconify/json']
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										67
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								package.json
									
									
									
									
									
								
							@@ -33,12 +33,12 @@
 | 
			
		||||
    "@form-create/element-ui": "^3.1.17",
 | 
			
		||||
    "@iconify/iconify": "^3.1.0",
 | 
			
		||||
    "@videojs-player/vue": "^1.0.0",
 | 
			
		||||
    "@vueuse/core": "^9.13.0",
 | 
			
		||||
    "@vueuse/core": "^10.1.0",
 | 
			
		||||
    "@wangeditor/editor": "^5.1.23",
 | 
			
		||||
    "@wangeditor/editor-for-vue": "^5.1.10",
 | 
			
		||||
    "@zxcvbn-ts/core": "^2.2.1",
 | 
			
		||||
    "animate.css": "^4.1.1",
 | 
			
		||||
    "axios": "^1.3.5",
 | 
			
		||||
    "axios": "^1.3.6",
 | 
			
		||||
    "benz-amr-recorder": "^1.1.5",
 | 
			
		||||
    "bpmn-js-token-simulation": "^0.10.0",
 | 
			
		||||
    "camunda-bpmn-moddle": "^7.0.1",
 | 
			
		||||
@@ -46,19 +46,19 @@
 | 
			
		||||
    "crypto-js": "^4.1.1",
 | 
			
		||||
    "dayjs": "^1.11.7",
 | 
			
		||||
    "diagram-js": "^11.6.0",
 | 
			
		||||
    "echarts": "^5.4.1",
 | 
			
		||||
    "echarts": "^5.4.2",
 | 
			
		||||
    "echarts-wordcloud": "^2.1.0",
 | 
			
		||||
    "element-plus": "2.3.3",
 | 
			
		||||
    "fast-xml-parser": "^4.1.3",
 | 
			
		||||
    "fast-xml-parser": "^4.2.2",
 | 
			
		||||
    "highlight.js": "^11.7.0",
 | 
			
		||||
    "intro.js": "^7.0.1",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "min-dash": "^4.0.0",
 | 
			
		||||
    "min-dash": "^4.1.0",
 | 
			
		||||
    "mitt": "^3.0.0",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.0.34",
 | 
			
		||||
    "qrcode": "^1.5.1",
 | 
			
		||||
    "pinia": "^2.0.35",
 | 
			
		||||
    "qrcode": "^1.5.3",
 | 
			
		||||
    "qs": "^6.11.1",
 | 
			
		||||
    "steady-xml": "^0.1.0",
 | 
			
		||||
    "url": "^0.11.0",
 | 
			
		||||
@@ -73,61 +73,60 @@
 | 
			
		||||
    "xml-js": "^1.6.11"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@commitlint/cli": "^17.5.0",
 | 
			
		||||
    "@commitlint/config-conventional": "^17.4.4",
 | 
			
		||||
    "@iconify/json": "^2.2.38",
 | 
			
		||||
    "@commitlint/cli": "^17.6.1",
 | 
			
		||||
    "@commitlint/config-conventional": "^17.6.1",
 | 
			
		||||
    "@iconify/json": "^2.2.54",
 | 
			
		||||
    "@intlify/unplugin-vue-i18n": "^0.10.0",
 | 
			
		||||
    "@purge-icons/generated": "^0.9.0",
 | 
			
		||||
    "@types/intro.js": "^5.1.1",
 | 
			
		||||
    "@types/lodash-es": "^4.17.7",
 | 
			
		||||
    "@types/node": "^18.15.5",
 | 
			
		||||
    "@types/node": "^18.16.0",
 | 
			
		||||
    "@types/nprogress": "^0.2.0",
 | 
			
		||||
    "@types/qrcode": "^1.5.0",
 | 
			
		||||
    "@types/qs": "^6.9.7",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.56.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.56.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.59.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.59.0",
 | 
			
		||||
    "@vitejs/plugin-legacy": "^4.0.2",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.1.0",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.0.1",
 | 
			
		||||
    "autoprefixer": "^10.4.14",
 | 
			
		||||
    "bpmn-js": "^8.9.0",
 | 
			
		||||
    "bpmn-js-properties-panel": "^0.46.0",
 | 
			
		||||
    "consola": "^2.15.3",
 | 
			
		||||
    "eslint": "^8.36.0",
 | 
			
		||||
    "consola": "^3.1.0",
 | 
			
		||||
    "eslint": "^8.39.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-vue": "^9.9.0",
 | 
			
		||||
    "lint-staged": "^13.2.0",
 | 
			
		||||
    "postcss": "^8.4.21",
 | 
			
		||||
    "eslint-plugin-vue": "^9.11.0",
 | 
			
		||||
    "lint-staged": "^13.2.1",
 | 
			
		||||
    "postcss": "^8.4.23",
 | 
			
		||||
    "postcss-html": "^1.5.0",
 | 
			
		||||
    "postcss-scss": "^4.0.6",
 | 
			
		||||
    "prettier": "^2.8.6",
 | 
			
		||||
    "rimraf": "^4.4.1",
 | 
			
		||||
    "rollup": "^3.20.0",
 | 
			
		||||
    "sass": "^1.59.3",
 | 
			
		||||
    "stylelint": "^15.3.0",
 | 
			
		||||
    "prettier": "^2.8.8",
 | 
			
		||||
    "rimraf": "^5.0.0",
 | 
			
		||||
    "rollup": "^3.20.7",
 | 
			
		||||
    "sass": "^1.62.0",
 | 
			
		||||
    "stylelint": "^15.6.0",
 | 
			
		||||
    "stylelint-config-html": "^1.1.0",
 | 
			
		||||
    "stylelint-config-prettier": "^9.0.5",
 | 
			
		||||
    "stylelint-config-recommended": "^11.0.0",
 | 
			
		||||
    "stylelint-config-standard": "^31.0.0",
 | 
			
		||||
    "stylelint-config-recommended": "^12.0.0",
 | 
			
		||||
    "stylelint-config-standard": "^33.0.0",
 | 
			
		||||
    "stylelint-order": "^6.0.3",
 | 
			
		||||
    "terser": "^5.16.6",
 | 
			
		||||
    "typescript": "5.0.2",
 | 
			
		||||
    "unplugin-auto-import": "^0.15.1",
 | 
			
		||||
    "unplugin-element-plus": "^0.7.0",
 | 
			
		||||
    "terser": "^5.17.1",
 | 
			
		||||
    "typescript": "5.0.4",
 | 
			
		||||
    "unplugin-auto-import": "^0.15.3",
 | 
			
		||||
    "unplugin-element-plus": "^0.7.1",
 | 
			
		||||
    "unplugin-vue-components": "^0.24.1",
 | 
			
		||||
    "vite": "4.2.1",
 | 
			
		||||
    "vite": "4.3.1",
 | 
			
		||||
    "vite-plugin-compression": "^0.5.1",
 | 
			
		||||
    "vite-plugin-ejs": "^1.6.4",
 | 
			
		||||
    "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-svg-icons": "^2.0.1",
 | 
			
		||||
    "vite-plugin-top-level-await": "^1.3.0",
 | 
			
		||||
    "vite-plugin-vue-setup-extend-plus": "^0.1.0",
 | 
			
		||||
    "vite-plugin-windicss": "^1.8.10",
 | 
			
		||||
    "vue-tsc": "^1.2.0",
 | 
			
		||||
    "vue-tsc": "^1.4.4",
 | 
			
		||||
    "windicss": "^3.5.6"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
 
 | 
			
		||||
@@ -54,3 +54,8 @@ export const getBrand = (id: number) => {
 | 
			
		||||
export const getBrandParam = (params: PageParam) => {
 | 
			
		||||
  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) => {
 | 
			
		||||
  return request.get({ url: '/product/property/get-value-list', params })
 | 
			
		||||
export const getPropertyListAndValue = (data: any) => {
 | 
			
		||||
  return request.post({ url: '/product/property/get-value-list', data })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ------------------------ 属性值 -------------------
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,92 @@
 | 
			
		||||
import request from '@/config/axios'
 | 
			
		||||
 | 
			
		||||
// 创建商品 SPU
 | 
			
		||||
export function createSpu(data) {
 | 
			
		||||
  return request.post({
 | 
			
		||||
    url: '/product/spu/create',
 | 
			
		||||
    data: data
 | 
			
		||||
  })
 | 
			
		||||
export interface Property {
 | 
			
		||||
  propertyId?: number // 属性编号
 | 
			
		||||
  propertyName?: string // 属性名称
 | 
			
		||||
  valueId?: number // 属性值编号
 | 
			
		||||
  valueName?: string // 属性值名称
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 更新商品 SPU
 | 
			
		||||
export function updateSpu(data) {
 | 
			
		||||
  return request.put({
 | 
			
		||||
    url: '/product/spu/update',
 | 
			
		||||
    data: data
 | 
			
		||||
  })
 | 
			
		||||
// TODO puhui999:是不是直接叫 Sku 更简洁一点哈。type 待后面,总感觉有个类型?
 | 
			
		||||
export interface SkuType {
 | 
			
		||||
  id?: number // 商品 SKU 编号
 | 
			
		||||
  spuId?: number // SPU 编号
 | 
			
		||||
  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
 | 
			
		||||
export function deleteSpu(id) {
 | 
			
		||||
  return request.delete({
 | 
			
		||||
    url: `/product/spu/delete?id=${id}`
 | 
			
		||||
  })
 | 
			
		||||
// TODO puhui999:是不是直接叫 Spu 更简洁一点哈。type 待后面,总感觉有个类型?
 | 
			
		||||
export interface SpuType {
 | 
			
		||||
  id?: number
 | 
			
		||||
  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 详情
 | 
			
		||||
export function getSpuDetail(id) {
 | 
			
		||||
  return request.get({
 | 
			
		||||
    url: `/product/spu/get-detail?id=${id}`
 | 
			
		||||
  })
 | 
			
		||||
// 获得 Spu 列表
 | 
			
		||||
export const getSpuPage = (params: PageParam) => {
 | 
			
		||||
  return request.get({ url: '/product/spu/page', params })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获得商品 SPU 分页
 | 
			
		||||
export function getSpuPage(query) {
 | 
			
		||||
  return request.get({
 | 
			
		||||
    url: '/product/spu/page',
 | 
			
		||||
    params: query
 | 
			
		||||
  })
 | 
			
		||||
// 获得 Spu 列表 tabsCount
 | 
			
		||||
export const getTabsCount = () => {
 | 
			
		||||
  return request.get({ url: '/product/spu/get-count' })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获得商品 SPU 精简列表
 | 
			
		||||
export function getSpuSimpleList() {
 | 
			
		||||
  return request.get({
 | 
			
		||||
    url: '/product/spu/get-simple-list'
 | 
			
		||||
  })
 | 
			
		||||
// 创建商品 Spu
 | 
			
		||||
export const createSpu = (data: SpuType) => {
 | 
			
		||||
  return request.post({ url: '/product/spu/create', data })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 更新商品 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' })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getChildrenArea = async (id: number) => {
 | 
			
		||||
  return await request.get({ url: '/system/area/get-children?id=' + id })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getAreaListByIds = async (ids) => {
 | 
			
		||||
  return await request.get({ url: '/system/area/get-by-ids?ids=' + ids })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获得 IP 对应的地区名
 | 
			
		||||
export const getAreaByIp = async (ip: string) => {
 | 
			
		||||
  return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
 | 
			
		||||
 
 | 
			
		||||
@@ -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 })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,6 @@ export const getAccessTokenPage = (params: PageParam) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除 token
 | 
			
		||||
export const deleteAccessToken = (accessToken: number) => {
 | 
			
		||||
export const deleteAccessToken = (accessToken: string) => {
 | 
			
		||||
  return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import type { Slots } from 'vue'
 | 
			
		||||
import { getSlot } from '@/utils/tsxHelper'
 | 
			
		||||
import { PlaceholderMoel } from './types'
 | 
			
		||||
import { PlaceholderModel } from './types'
 | 
			
		||||
import { FormSchema } from '@/types/form'
 | 
			
		||||
import { ColProps } from '@/types/components'
 | 
			
		||||
 | 
			
		||||
@@ -10,7 +10,7 @@ import { ColProps } from '@/types/components'
 | 
			
		||||
 * @returns 返回提示信息对象
 | 
			
		||||
 * @description 用于自动设置placeholder
 | 
			
		||||
 */
 | 
			
		||||
export const setTextPlaceholder = (schema: FormSchema): PlaceholderMoel => {
 | 
			
		||||
export const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {
 | 
			
		||||
  const { t } = useI18n()
 | 
			
		||||
  const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']
 | 
			
		||||
  const selectMap = ['Select', 'SelectV2', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']
 | 
			
		||||
@@ -108,8 +108,8 @@ export const setItemComponentSlots = (
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param schema Form表单结构化数组
 | 
			
		||||
 * @param formModel FormMoel
 | 
			
		||||
 * @returns FormMoel
 | 
			
		||||
 * @param formModel FormModel
 | 
			
		||||
 * @returns FormModel
 | 
			
		||||
 * @description 生成对应的formModel
 | 
			
		||||
 */
 | 
			
		||||
export const initModel = (schema: FormSchema[], formModel: Recordable) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { FormSchema } from '@/types/form'
 | 
			
		||||
 | 
			
		||||
export interface PlaceholderMoel {
 | 
			
		||||
export interface PlaceholderModel {
 | 
			
		||||
  placeholder?: string
 | 
			
		||||
  startPlaceholder?: string
 | 
			
		||||
  endPlaceholder?: string
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="upload-box">
 | 
			
		||||
    <el-upload
 | 
			
		||||
      :action="updateUrl"
 | 
			
		||||
      list-type="picture-card"
 | 
			
		||||
      :class="['upload', drag ? 'no-border' : '']"
 | 
			
		||||
      v-model:file-list="fileList"
 | 
			
		||||
      :multiple="true"
 | 
			
		||||
      :limit="limit"
 | 
			
		||||
      :headers="uploadHeaders"
 | 
			
		||||
      :accept="fileType.join(',')"
 | 
			
		||||
      :action="updateUrl"
 | 
			
		||||
      :before-upload="beforeUpload"
 | 
			
		||||
      :class="['upload', drag ? 'no-border' : '']"
 | 
			
		||||
      :drag="drag"
 | 
			
		||||
      :headers="uploadHeaders"
 | 
			
		||||
      :limit="limit"
 | 
			
		||||
      :multiple="true"
 | 
			
		||||
      :on-error="uploadError"
 | 
			
		||||
      :on-exceed="handleExceed"
 | 
			
		||||
      :on-success="uploadSuccess"
 | 
			
		||||
      :on-error="uploadError"
 | 
			
		||||
      :drag="drag"
 | 
			
		||||
      :accept="fileType.join(',')"
 | 
			
		||||
      list-type="picture-card"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="upload-empty">
 | 
			
		||||
        <slot name="empty">
 | 
			
		||||
@@ -40,15 +40,15 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-image-viewer
 | 
			
		||||
      v-if="imgViewVisible"
 | 
			
		||||
      @close="imgViewVisible = false"
 | 
			
		||||
      :url-list="[viewImageUrl]"
 | 
			
		||||
      @close="imgViewVisible = false"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="UploadImgs">
 | 
			
		||||
<script lang="ts" name="UploadImgs" setup>
 | 
			
		||||
import { PropType } from 'vue'
 | 
			
		||||
import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
 | 
			
		||||
import { ElNotification } from 'element-plus'
 | 
			
		||||
import type { UploadProps, UploadFile, UploadUserFile } from 'element-plus'
 | 
			
		||||
 | 
			
		||||
import { propTypes } from '@/utils/propTypes'
 | 
			
		||||
import { getAccessToken, getTenantId } from '@/utils/auth'
 | 
			
		||||
@@ -88,8 +88,19 @@ const uploadHeaders = ref({
 | 
			
		||||
  '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 文件上传之前判断
 | 
			
		||||
 * @param rawFile 上传的文件
 | 
			
		||||
@@ -116,9 +127,11 @@ const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
 | 
			
		||||
interface UploadEmits {
 | 
			
		||||
  (e: 'update:modelValue', value: UploadUserFile[]): void
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<UploadEmits>()
 | 
			
		||||
const uploadSuccess = (response, uploadFile: UploadFile) => {
 | 
			
		||||
  if (!response) return
 | 
			
		||||
  // TODO 多图上传组件成功后只是把保存成功后的url替换掉组件选图时的文件路径,所以返回的fileList包含的是一个包含文件信息的对象列表
 | 
			
		||||
  uploadFile.url = response.data
 | 
			
		||||
  emit('update:modelValue', fileList.value)
 | 
			
		||||
  message.success('上传成功')
 | 
			
		||||
@@ -159,35 +172,40 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.is-error {
 | 
			
		||||
  .upload {
 | 
			
		||||
    :deep(.el-upload--picture-card),
 | 
			
		||||
    :deep(.el-upload-dragger) {
 | 
			
		||||
      border: 1px dashed var(--el-color-danger) !important;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        border-color: var(--el-color-primary) !important;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:deep(.disabled) {
 | 
			
		||||
  .el-upload--picture-card,
 | 
			
		||||
  .el-upload-dragger {
 | 
			
		||||
    cursor: not-allowed;
 | 
			
		||||
    background: var(--el-disabled-bg-color) !important;
 | 
			
		||||
    border: 1px dashed var(--el-border-color-darker);
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      border-color: var(--el-border-color-darker) !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-box {
 | 
			
		||||
  .no-border {
 | 
			
		||||
    :deep(.el-upload--picture-card) {
 | 
			
		||||
      border: none !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :deep(.upload) {
 | 
			
		||||
    .el-upload-dragger {
 | 
			
		||||
      display: flex;
 | 
			
		||||
@@ -199,14 +217,17 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      border: 1px dashed var(--el-border-color-darker);
 | 
			
		||||
      border-radius: v-bind(borderRadius);
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        border: 1px dashed var(--el-color-primary);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-upload-dragger.is-dragover {
 | 
			
		||||
      background-color: var(--el-color-primary-light-9);
 | 
			
		||||
      border: 2px dashed var(--el-color-primary) !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-upload-list__item,
 | 
			
		||||
    .el-upload--picture-card {
 | 
			
		||||
      width: v-bind(width);
 | 
			
		||||
@@ -214,11 +235,13 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
      background-color: transparent;
 | 
			
		||||
      border-radius: v-bind(borderRadius);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .upload-image {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      object-fit: contain;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .upload-handle {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
@@ -233,6 +256,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
      background: rgb(0 0 0 / 60%);
 | 
			
		||||
      opacity: 0;
 | 
			
		||||
      transition: var(--el-transition-duration-fast);
 | 
			
		||||
 | 
			
		||||
      .handle-icon {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
@@ -240,15 +264,18 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        padding: 0 6%;
 | 
			
		||||
        color: aliceblue;
 | 
			
		||||
 | 
			
		||||
        .el-icon {
 | 
			
		||||
          margin-bottom: 15%;
 | 
			
		||||
          font-size: 140%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        span {
 | 
			
		||||
          font-size: 100%;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-upload-list__item {
 | 
			
		||||
      &:hover {
 | 
			
		||||
        .upload-handle {
 | 
			
		||||
@@ -256,6 +283,7 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .upload-empty {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
@@ -263,12 +291,14 @@ const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      line-height: 30px;
 | 
			
		||||
      color: var(--el-color-info);
 | 
			
		||||
 | 
			
		||||
      .el-icon {
 | 
			
		||||
        font-size: 28px;
 | 
			
		||||
        color: var(--el-text-color-secondary);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-upload__tip {
 | 
			
		||||
    line-height: 15px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@ import { Layout } from '@/utils/routerHelper'
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n()
 | 
			
		||||
/**
 | 
			
		||||
* redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击
 | 
			
		||||
* name:'router-name'          设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
 | 
			
		||||
* meta : {
 | 
			
		||||
 * redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击
 | 
			
		||||
 * name:'router-name'          设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
 | 
			
		||||
 * meta : {
 | 
			
		||||
    hidden: true              当设置 true 的时候该路由不会再侧边栏出现 如404,login等页面(默认 false)
 | 
			
		||||
 | 
			
		||||
    alwaysShow: true          当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式,
 | 
			
		||||
@@ -31,7 +31,7 @@ const { t } = useI18n()
 | 
			
		||||
 | 
			
		||||
    canTo: true               设置为true即使hidden为true,也依然可以进行路由跳转(默认 false)
 | 
			
		||||
  }
 | 
			
		||||
**/
 | 
			
		||||
 **/
 | 
			
		||||
const remainingRouter: AppRouteRecordRaw[] = [
 | 
			
		||||
  {
 | 
			
		||||
    path: '/redirect',
 | 
			
		||||
@@ -345,6 +345,42 @@ const remainingRouter: AppRouteRecordRaw[] = [
 | 
			
		||||
        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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解决表格内容超过表格总宽度后,横向滚动条前端顶不到表格边缘的问题
 | 
			
		||||
.el-scrollbar__bar {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: flex-start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* nprogress 适配 element-plus 的主题色 */
 | 
			
		||||
#nprogress {
 | 
			
		||||
  & .bar {
 | 
			
		||||
 
 | 
			
		||||
@@ -155,3 +155,57 @@ export const fileSizeFormatter = (row, column, cellValue) => {
 | 
			
		||||
  const sizeStr = size.toFixed(2) //保留的小数位数
 | 
			
		||||
  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 = {
 | 
			
		||||
  children: 'children',
 | 
			
		||||
  label: 'name',
 | 
			
		||||
  value: 'id'
 | 
			
		||||
  value: 'id',
 | 
			
		||||
  isLeaf: 'leaf'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
 | 
			
		||||
 
 | 
			
		||||
@@ -87,7 +87,7 @@
 | 
			
		||||
    <MyProcessViewer
 | 
			
		||||
      key="designer"
 | 
			
		||||
      v-model="bpmnXML"
 | 
			
		||||
      :value="bpmnXML"
 | 
			
		||||
      :value="bpmnXML as any"
 | 
			
		||||
      v-bind="bpmnControlForm"
 | 
			
		||||
      :prefix="bpmnControlForm.prefix"
 | 
			
		||||
    />
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ const formRules = reactive({
 | 
			
		||||
  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
 | 
			
		||||
})
 | 
			
		||||
const formRef = ref() // 表单 Ref
 | 
			
		||||
const userList = ref([]) // 用户列表
 | 
			
		||||
const userList = ref<any[]>([]) // 用户列表
 | 
			
		||||
 | 
			
		||||
/** 打开弹窗 */
 | 
			
		||||
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 UserApi from '@/api/system/user'
 | 
			
		||||
import UserGroupForm from './UserGroupForm.vue'
 | 
			
		||||
import { UserVO } from '@/api/system/user'
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
 | 
			
		||||
@@ -131,7 +132,7 @@ const queryParams = reactive({
 | 
			
		||||
  createTime: []
 | 
			
		||||
})
 | 
			
		||||
const queryFormRef = ref() // 搜索的表单
 | 
			
		||||
const userList = ref([]) // 用户列表
 | 
			
		||||
const userList = ref<UserVO[]>([]) // 用户列表
 | 
			
		||||
 | 
			
		||||
/** 查询列表 */
 | 
			
		||||
const getList = async () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
    <!-- 流程属性器,负责编辑每个流程节点的属性 -->
 | 
			
		||||
    <MyProcessPenal
 | 
			
		||||
      key="penal"
 | 
			
		||||
      :bpmnModeler="modeler"
 | 
			
		||||
      :bpmnModeler="modeler as any"
 | 
			
		||||
      :prefix="controlForm.prefix"
 | 
			
		||||
      class="process-panel"
 | 
			
		||||
      :model="model"
 | 
			
		||||
 
 | 
			
		||||
@@ -219,7 +219,7 @@
 | 
			
		||||
    <MyProcessViewer
 | 
			
		||||
      key="designer"
 | 
			
		||||
      v-model="bpmnXML"
 | 
			
		||||
      :value="bpmnXML"
 | 
			
		||||
      :value="bpmnXML as any"
 | 
			
		||||
      v-bind="bpmnControlForm"
 | 
			
		||||
      :prefix="bpmnControlForm.prefix"
 | 
			
		||||
    />
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ const props = defineProps({
 | 
			
		||||
  id: propTypes.number.def(undefined)
 | 
			
		||||
})
 | 
			
		||||
const detailLoading = ref(false) // 表单的加载中
 | 
			
		||||
const detailData = ref({}) // 详情数据
 | 
			
		||||
const detailData = ref<any>({}) // 详情数据
 | 
			
		||||
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
 | 
			
		||||
 | 
			
		||||
/** 获得数据 */
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-card>
 | 
			
		||||
    <!-- 流程图预览 -->
 | 
			
		||||
    <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
 | 
			
		||||
    <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="BpmProcessInstanceCreate">
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,12 @@
 | 
			
		||||
import FcDesigner from '@form-create/designer'
 | 
			
		||||
import { useClipboard } from '@vueuse/core'
 | 
			
		||||
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 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) => {
 | 
			
		||||
  // 处理语言和代码
 | 
			
		||||
  let language = 'json'
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,14 @@ import { useClipboard } from '@vueuse/core'
 | 
			
		||||
import { handleTree2 } from '@/utils/tree'
 | 
			
		||||
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 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 language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
 | 
			
		||||
  const result = hljs.highlight(language, item.code || '', true)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <doc-alert title="Redis 缓存" url="https://doc.iocoder.cn/redis-cache/" />
 | 
			
		||||
  <doc-alert title="本地缓存" url="https://doc.iocoder.cn/local-cache/" />
 | 
			
		||||
 | 
			
		||||
  <el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <!-- 基本信息 -->
 | 
			
		||||
@@ -51,127 +50,224 @@
 | 
			
		||||
      <!-- 命令统计 -->
 | 
			
		||||
      <el-col :span="12" class="mt-3">
 | 
			
		||||
        <el-card :gutter="12" shadow="hover">
 | 
			
		||||
          <div ref="commandStatsRef" class="h-88"></div>
 | 
			
		||||
          <Echart :options="commandStatsRefChika" :height="420" />
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <!-- 内存使用量统计 -->
 | 
			
		||||
      <el-col :span="12" class="mt-3">
 | 
			
		||||
        <el-card class="ml-3" :gutter="12" shadow="hover">
 | 
			
		||||
          <div ref="usedmemory" class="h-88"></div>
 | 
			
		||||
          <Echart :options="usedmemoryEchartChika" :height="420" />
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-scrollbar>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="InfraRedis">
 | 
			
		||||
import * as echarts from 'echarts'
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import echarts from '@/plugins/echarts'
 | 
			
		||||
import { GaugeChart } from 'echarts/charts'
 | 
			
		||||
import { ToolboxComponent } from 'echarts/components'
 | 
			
		||||
import * as RedisApi from '@/api/infra/redis'
 | 
			
		||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
 | 
			
		||||
 | 
			
		||||
const cache = ref<RedisMonitorInfoVO>()
 | 
			
		||||
 | 
			
		||||
// 基本信息
 | 
			
		||||
const readRedisInfo = async () => {
 | 
			
		||||
  const data = await RedisApi.getCache()
 | 
			
		||||
  cache.value = data
 | 
			
		||||
  loadEchartOptions(data.commandStats)
 | 
			
		||||
}
 | 
			
		||||
// 图表
 | 
			
		||||
const commandStatsRef = ref<HTMLElement>()
 | 
			
		||||
const usedmemory = ref<HTMLDivElement>()
 | 
			
		||||
 | 
			
		||||
const loadEchartOptions = (stats) => {
 | 
			
		||||
  const commandStats = [] as any[]
 | 
			
		||||
  const nameList = [] as string[]
 | 
			
		||||
  stats.forEach((row) => {
 | 
			
		||||
    commandStats.push({
 | 
			
		||||
      name: row.command,
 | 
			
		||||
      value: row.calls
 | 
			
		||||
    })
 | 
			
		||||
    nameList.push(row.command)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const commandStatsInstance = echarts.init(commandStatsRef.value!, 'macarons')
 | 
			
		||||
 | 
			
		||||
  commandStatsInstance.setOption({
 | 
			
		||||
    title: {
 | 
			
		||||
      text: '命令统计',
 | 
			
		||||
      left: 'center'
 | 
			
		||||
    },
 | 
			
		||||
    tooltip: {
 | 
			
		||||
      trigger: 'item',
 | 
			
		||||
      formatter: '{a} <br/>{b} : {c} ({d}%)'
 | 
			
		||||
    },
 | 
			
		||||
    legend: {
 | 
			
		||||
      type: 'scroll',
 | 
			
		||||
      orient: 'vertical',
 | 
			
		||||
      right: 30,
 | 
			
		||||
      top: 10,
 | 
			
		||||
      bottom: 20,
 | 
			
		||||
      data: nameList,
 | 
			
		||||
      textStyle: {
 | 
			
		||||
        color: '#a1a1a1'
 | 
			
		||||
// 内存使用情况
 | 
			
		||||
const usedmemoryEchartChika = reactive({
 | 
			
		||||
  title: {
 | 
			
		||||
    // 仪表盘标题。
 | 
			
		||||
    text: '内存使用情况',
 | 
			
		||||
    left: 'center',
 | 
			
		||||
    show: true, // 是否显示标题,默认 true。
 | 
			
		||||
    offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
 | 
			
		||||
    color: 'yellow', // 文字的颜色,默认 #333。
 | 
			
		||||
    fontSize: 20 // 文字的字体大小,默认 15。
 | 
			
		||||
  },
 | 
			
		||||
  toolbox: {
 | 
			
		||||
    show: false,
 | 
			
		||||
    feature: {
 | 
			
		||||
      restore: { show: true },
 | 
			
		||||
      saveAsImage: { show: true }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  series: [
 | 
			
		||||
    {
 | 
			
		||||
      name: '峰值',
 | 
			
		||||
      type: 'gauge',
 | 
			
		||||
      min: 0,
 | 
			
		||||
      max: 50,
 | 
			
		||||
      splitNumber: 10,
 | 
			
		||||
      //这是指针的颜色
 | 
			
		||||
      color: '#F5C74E',
 | 
			
		||||
      radius: '85%',
 | 
			
		||||
      center: ['50%', '50%'],
 | 
			
		||||
      startAngle: 225,
 | 
			
		||||
      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],
 | 
			
		||||
        center: ['40%', '60%'],
 | 
			
		||||
        data: commandStats,
 | 
			
		||||
        roseType: 'radius',
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 指令使用情况
 | 
			
		||||
const commandStatsRefChika = reactive({
 | 
			
		||||
  title: {
 | 
			
		||||
    text: '命令统计',
 | 
			
		||||
    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: {
 | 
			
		||||
          show: true
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          label: {
 | 
			
		||||
            show: true
 | 
			
		||||
          },
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            shadowBlur: 10,
 | 
			
		||||
            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({
 | 
			
		||||
    title: {
 | 
			
		||||
      text: '内存使用情况',
 | 
			
		||||
      left: 'center'
 | 
			
		||||
    },
 | 
			
		||||
    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: '内存消耗'
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  })
 | 
			
		||||
/** 加载数据 */
 | 
			
		||||
const getSummary = () => {
 | 
			
		||||
  // 初始化命令图表
 | 
			
		||||
  initCommandStatsChart()
 | 
			
		||||
  usedMemoryInstance()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
  // 加载数据
 | 
			
		||||
  getSummary()
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,42 +2,49 @@
 | 
			
		||||
  <!-- 搜索工作栏 -->
 | 
			
		||||
  <ContentWrap>
 | 
			
		||||
    <el-form
 | 
			
		||||
      class="-mb-15px"
 | 
			
		||||
      :model="queryParams"
 | 
			
		||||
      ref="queryFormRef"
 | 
			
		||||
      :inline="true"
 | 
			
		||||
      :model="queryParams"
 | 
			
		||||
      class="-mb-15px"
 | 
			
		||||
      label-width="68px"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form-item label="名称" prop="name">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="queryParams.name"
 | 
			
		||||
          placeholder="请输入名称"
 | 
			
		||||
          clearable
 | 
			
		||||
          @keyup.enter="handleQuery"
 | 
			
		||||
          class="!w-240px"
 | 
			
		||||
          clearable
 | 
			
		||||
          placeholder="请输入名称"
 | 
			
		||||
          @keyup.enter="handleQuery"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="创建时间" prop="createTime">
 | 
			
		||||
        <el-date-picker
 | 
			
		||||
          v-model="queryParams.createTime"
 | 
			
		||||
          value-format="YYYY-MM-DD HH:mm:ss"
 | 
			
		||||
          type="daterange"
 | 
			
		||||
          start-placeholder="开始日期"
 | 
			
		||||
          end-placeholder="结束日期"
 | 
			
		||||
          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
 | 
			
		||||
          class="!w-240px"
 | 
			
		||||
          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 icon="ep:search" class="mr-5px" /> 搜索</el-button>
 | 
			
		||||
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
 | 
			
		||||
        <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:property:create']"
 | 
			
		||||
          plain
 | 
			
		||||
          type="primary"
 | 
			
		||||
          @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-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
@@ -46,23 +53,23 @@
 | 
			
		||||
  <!-- 列表 -->
 | 
			
		||||
  <ContentWrap>
 | 
			
		||||
    <el-table v-loading="loading" :data="list">
 | 
			
		||||
      <el-table-column label="编号" align="center" prop="id" />
 | 
			
		||||
      <el-table-column label="名称" align="center" />
 | 
			
		||||
      <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
 | 
			
		||||
      <el-table-column align="center" label="编号" prop="id" />
 | 
			
		||||
      <el-table-column align="center" label="名称" prop="name" />
 | 
			
		||||
      <el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
 | 
			
		||||
      <el-table-column
 | 
			
		||||
        label="创建时间"
 | 
			
		||||
        :formatter="dateFormatter"
 | 
			
		||||
        align="center"
 | 
			
		||||
        label="创建时间"
 | 
			
		||||
        prop="createTime"
 | 
			
		||||
        width="180"
 | 
			
		||||
        :formatter="dateFormatter"
 | 
			
		||||
      />
 | 
			
		||||
      <el-table-column label="操作" align="center">
 | 
			
		||||
      <el-table-column align="center" label="操作">
 | 
			
		||||
        <template #default="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
            v-hasPermi="['product:property:update']"
 | 
			
		||||
            link
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="openForm('update', scope.row.id)"
 | 
			
		||||
            v-hasPermi="['product:property:update']"
 | 
			
		||||
          >
 | 
			
		||||
            编辑
 | 
			
		||||
          </el-button>
 | 
			
		||||
@@ -70,10 +77,10 @@
 | 
			
		||||
            <router-link :to="'/property/value/' + scope.row.id">属性值</router-link>
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button
 | 
			
		||||
            v-hasPermi="['product:property:delete']"
 | 
			
		||||
            link
 | 
			
		||||
            type="danger"
 | 
			
		||||
            @click="handleDelete(scope.row.id)"
 | 
			
		||||
            v-hasPermi="['product:property:delete']"
 | 
			
		||||
          >
 | 
			
		||||
            删除
 | 
			
		||||
          </el-button>
 | 
			
		||||
@@ -82,9 +89,9 @@
 | 
			
		||||
    </el-table>
 | 
			
		||||
    <!-- 分页 -->
 | 
			
		||||
    <Pagination
 | 
			
		||||
      :total="total"
 | 
			
		||||
      v-model:page="queryParams.pageNo"
 | 
			
		||||
      v-model:limit="queryParams.pageSize"
 | 
			
		||||
      v-model:page="queryParams.pageNo"
 | 
			
		||||
      :total="total"
 | 
			
		||||
      @pagination="getList"
 | 
			
		||||
    />
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
@@ -92,10 +99,11 @@
 | 
			
		||||
  <!-- 表单弹窗:添加/修改 -->
 | 
			
		||||
  <PropertyForm ref="formRef" @success="getList" />
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts" name="ProductProperty">
 | 
			
		||||
<script lang="ts" name="ProductProperty" setup>
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
import * as PropertyApi from '@/api/mall/product/property'
 | 
			
		||||
import PropertyForm from './PropertyForm.vue'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
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)
 | 
			
		||||
  formType.value = type
 | 
			
		||||
  resetForm()
 | 
			
		||||
  formData.value.dictType = dictType
 | 
			
		||||
  if (dictType) {
 | 
			
		||||
    formData.value.dictType = dictType
 | 
			
		||||
  }
 | 
			
		||||
  // 修改时,设置数据
 | 
			
		||||
  if (id) {
 | 
			
		||||
    formLoading.value = true
 | 
			
		||||
 
 | 
			
		||||
@@ -2,36 +2,36 @@
 | 
			
		||||
  <!-- 搜索工作栏 -->
 | 
			
		||||
  <ContentWrap>
 | 
			
		||||
    <el-form
 | 
			
		||||
      class="-mb-15px"
 | 
			
		||||
      :model="queryParams"
 | 
			
		||||
      ref="queryFormRef"
 | 
			
		||||
      :inline="true"
 | 
			
		||||
      :model="queryParams"
 | 
			
		||||
      class="-mb-15px"
 | 
			
		||||
      label-width="68px"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form-item label="字典名称" prop="name">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="queryParams.name"
 | 
			
		||||
          placeholder="请输入字典名称"
 | 
			
		||||
          clearable
 | 
			
		||||
          @keyup.enter="handleQuery"
 | 
			
		||||
          class="!w-240px"
 | 
			
		||||
          clearable
 | 
			
		||||
          placeholder="请输入字典名称"
 | 
			
		||||
          @keyup.enter="handleQuery"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="字典类型" prop="type">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="queryParams.type"
 | 
			
		||||
          placeholder="请输入字典类型"
 | 
			
		||||
          clearable
 | 
			
		||||
          @keyup.enter="handleQuery"
 | 
			
		||||
          class="!w-240px"
 | 
			
		||||
          clearable
 | 
			
		||||
          placeholder="请输入字典类型"
 | 
			
		||||
          @keyup.enter="handleQuery"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="状态" prop="status">
 | 
			
		||||
        <el-select
 | 
			
		||||
          v-model="queryParams.status"
 | 
			
		||||
          placeholder="请选择字典状态"
 | 
			
		||||
          clearable
 | 
			
		||||
          class="!w-240px"
 | 
			
		||||
          clearable
 | 
			
		||||
          placeholder="请选择字典状态"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
 | 
			
		||||
@@ -44,33 +44,41 @@
 | 
			
		||||
      <el-form-item label="创建时间" prop="createTime">
 | 
			
		||||
        <el-date-picker
 | 
			
		||||
          v-model="queryParams.createTime"
 | 
			
		||||
          value-format="yyyy-MM-dd HH:mm:ss"
 | 
			
		||||
          type="daterange"
 | 
			
		||||
          start-placeholder="开始日期"
 | 
			
		||||
          end-placeholder="结束日期"
 | 
			
		||||
          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
 | 
			
		||||
          class="!w-240px"
 | 
			
		||||
          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 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="['system:dict:create']"
 | 
			
		||||
        >
 | 
			
		||||
          <Icon icon="ep:plus" class="mr-5px" /> 新增
 | 
			
		||||
        <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
 | 
			
		||||
          type="success"
 | 
			
		||||
          v-hasPermi="['system:dict:create']"
 | 
			
		||||
          plain
 | 
			
		||||
          @click="handleExport"
 | 
			
		||||
          :loading="exportLoading"
 | 
			
		||||
          v-hasPermi="['system:dict:export']"
 | 
			
		||||
          type="primary"
 | 
			
		||||
          @click="openForm('create')"
 | 
			
		||||
        >
 | 
			
		||||
          <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-form-item>
 | 
			
		||||
    </el-form>
 | 
			
		||||
@@ -79,29 +87,29 @@
 | 
			
		||||
  <!-- 列表 -->
 | 
			
		||||
  <ContentWrap>
 | 
			
		||||
    <el-table v-loading="loading" :data="list">
 | 
			
		||||
      <el-table-column label="字典编号" align="center" prop="id" />
 | 
			
		||||
      <el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip />
 | 
			
		||||
      <el-table-column label="字典类型" align="center" prop="type" width="300" />
 | 
			
		||||
      <el-table-column label="状态" align="center" prop="status">
 | 
			
		||||
      <el-table-column align="center" label="字典编号" prop="id" />
 | 
			
		||||
      <el-table-column align="center" label="字典名称" prop="name" show-overflow-tooltip />
 | 
			
		||||
      <el-table-column align="center" label="字典类型" prop="type" width="300" />
 | 
			
		||||
      <el-table-column align="center" label="状态" 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="remark" />
 | 
			
		||||
      <el-table-column align="center" label="备注" prop="remark" />
 | 
			
		||||
      <el-table-column
 | 
			
		||||
        label="创建时间"
 | 
			
		||||
        :formatter="dateFormatter"
 | 
			
		||||
        align="center"
 | 
			
		||||
        label="创建时间"
 | 
			
		||||
        prop="createTime"
 | 
			
		||||
        width="180"
 | 
			
		||||
      />
 | 
			
		||||
      <el-table-column label="操作" align="center">
 | 
			
		||||
      <el-table-column align="center" label="操作">
 | 
			
		||||
        <template #default="scope">
 | 
			
		||||
          <el-button
 | 
			
		||||
            v-hasPermi="['system:dict:update']"
 | 
			
		||||
            link
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="openForm('update', scope.row.id)"
 | 
			
		||||
            v-hasPermi="['system:dict:update']"
 | 
			
		||||
          >
 | 
			
		||||
            修改
 | 
			
		||||
          </el-button>
 | 
			
		||||
@@ -109,10 +117,10 @@
 | 
			
		||||
            <el-button link type="primary">数据</el-button>
 | 
			
		||||
          </router-link>
 | 
			
		||||
          <el-button
 | 
			
		||||
            v-hasPermi="['system:dict:delete']"
 | 
			
		||||
            link
 | 
			
		||||
            type="danger"
 | 
			
		||||
            @click="handleDelete(scope.row.id)"
 | 
			
		||||
            v-hasPermi="['system:dict:delete']"
 | 
			
		||||
          >
 | 
			
		||||
            删除
 | 
			
		||||
          </el-button>
 | 
			
		||||
@@ -121,9 +129,9 @@
 | 
			
		||||
    </el-table>
 | 
			
		||||
    <!-- 分页 -->
 | 
			
		||||
    <Pagination
 | 
			
		||||
      :total="total"
 | 
			
		||||
      v-model:page="queryParams.pageNo"
 | 
			
		||||
      v-model:limit="queryParams.pageSize"
 | 
			
		||||
      v-model:page="queryParams.pageNo"
 | 
			
		||||
      :total="total"
 | 
			
		||||
      @pagination="getList"
 | 
			
		||||
    />
 | 
			
		||||
  </ContentWrap>
 | 
			
		||||
@@ -132,12 +140,13 @@
 | 
			
		||||
  <DictTypeForm ref="formRef" @success="getList" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" name="SystemDictType">
 | 
			
		||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 | 
			
		||||
<script lang="ts" name="SystemDictType" setup>
 | 
			
		||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 | 
			
		||||
import { dateFormatter } from '@/utils/formatTime'
 | 
			
		||||
import * as DictTypeApi from '@/api/system/dict/dict.type'
 | 
			
		||||
import DictTypeForm from './DictTypeForm.vue'
 | 
			
		||||
import download from '@/utils/download'
 | 
			
		||||
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
const { t } = useI18n() // 国际化
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,9 @@
 | 
			
		||||
        <el-select v-model="formData.type" clearable placeholder="请选择公告类型">
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)"
 | 
			
		||||
            :key="parseInt(dict.value)"
 | 
			
		||||
            :key="parseInt(dict.value as any)"
 | 
			
		||||
            :label="dict.label"
 | 
			
		||||
            :value="parseInt(dict.value)"
 | 
			
		||||
            :value="parseInt(dict.value as any)"
 | 
			
		||||
          />
 | 
			
		||||
        </el-select>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
@@ -27,9 +27,9 @@
 | 
			
		||||
        <el-select v-model="formData.status" clearable placeholder="请选择状态">
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
 | 
			
		||||
            :key="parseInt(dict.value)"
 | 
			
		||||
            :key="parseInt(dict.value as any)"
 | 
			
		||||
            :label="dict.label"
 | 
			
		||||
            :value="parseInt(dict.value)"
 | 
			
		||||
            :value="parseInt(dict.value as any)"
 | 
			
		||||
          />
 | 
			
		||||
        </el-select>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,6 @@
 | 
			
		||||
  </Dialog>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" name="SystemNotifyTemplateSendForm" setup>
 | 
			
		||||
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
 | 
			
		||||
import * as UserApi from '@/api/system/user'
 | 
			
		||||
import * as NotifyTemplateApi from '@/api/system/notify/template'
 | 
			
		||||
const message = useMessage() // 消息弹窗
 | 
			
		||||
@@ -102,8 +101,8 @@ const submitForm = async () => {
 | 
			
		||||
  // 提交请求
 | 
			
		||||
  formLoading.value = true
 | 
			
		||||
  try {
 | 
			
		||||
    const data = formData.value as SmsTemplateApi.SendSmsReqVO
 | 
			
		||||
    const logId = await SmsTemplateApi.sendSms(data)
 | 
			
		||||
    const data = formData.value as unknown as NotifyTemplateApi.NotifySendReqVO
 | 
			
		||||
    const logId = await NotifyTemplateApi.sendNotify(data)
 | 
			
		||||
    if (logId) {
 | 
			
		||||
      message.success('提交发送成功!发送结果,见发送日志编号:' + logId)
 | 
			
		||||
    }
 | 
			
		||||
@@ -121,7 +120,7 @@ const resetForm = () => {
 | 
			
		||||
    mobile: '',
 | 
			
		||||
    templateCode: '',
 | 
			
		||||
    templateParams: new Map()
 | 
			
		||||
  }
 | 
			
		||||
  } as any
 | 
			
		||||
  formRef.value?.resetFields()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,7 @@
 | 
			
		||||
          <el-button
 | 
			
		||||
            link
 | 
			
		||||
            type="danger"
 | 
			
		||||
            @click="handleForceLogout(scope.row.id)"
 | 
			
		||||
            @click="handleForceLogout(scope.row.accessToken)"
 | 
			
		||||
            v-hasPermi="['system:oauth2-token:delete']"
 | 
			
		||||
          >
 | 
			
		||||
            强退
 | 
			
		||||
@@ -142,12 +142,12 @@ const resetQuery = () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 强制退出操作 */
 | 
			
		||||
const handleForceLogout = async (id: number) => {
 | 
			
		||||
const handleForceLogout = async (accessToken: string) => {
 | 
			
		||||
  try {
 | 
			
		||||
    // 删除的二次确认
 | 
			
		||||
    await message.confirm('是否要强制退出用户')
 | 
			
		||||
    // 发起删除
 | 
			
		||||
    await OAuth2AccessTokenApi.deleteAccessToken(id)
 | 
			
		||||
    await OAuth2AccessTokenApi.deleteAccessToken(accessToken)
 | 
			
		||||
    message.success(t('common.success'))
 | 
			
		||||
    // 刷新列表
 | 
			
		||||
    await getList()
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ const resetForm = () => {
 | 
			
		||||
    sort: undefined,
 | 
			
		||||
    status: CommonStatusEnum.ENABLE,
 | 
			
		||||
    remark: ''
 | 
			
		||||
  }
 | 
			
		||||
  } as any
 | 
			
		||||
  formRef.value?.resetFields()
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  plugins: ['stylelint-order'],
 | 
			
		||||
  customSyntax: 'postcss-html',
 | 
			
		||||
  extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
 | 
			
		||||
  extends: ['stylelint-config-standard'],
 | 
			
		||||
  rules: {
 | 
			
		||||
    'selector-pseudo-class-no-unknown': [
 | 
			
		||||
      true,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user