TASK
3
.env.production
Normal file
@@ -0,0 +1,3 @@
|
||||
NODE_ENV = 'production'
|
||||
VUE_APP_BASE_URL = 'https://www.aida.com.hk'
|
||||
|
||||
2
.env.test
Normal file
@@ -0,0 +1,2 @@
|
||||
NODE_ENV = 'production'
|
||||
VUE_APP_BASE_URL = 'http://18.167.251.121:5568'
|
||||
72
.eslintrc.js
Normal file
@@ -0,0 +1,72 @@
|
||||
module.exports = {
|
||||
root: true, // 停止在父级目录中寻找
|
||||
env: {
|
||||
es6: true, // 启用 ES6 语法支持以及新的 ES6 全局变量或类型
|
||||
node: true // Node.js 全局变量和 Node.js 作用域
|
||||
},
|
||||
extends: ['plugin:vue/essential'],
|
||||
rules: {
|
||||
'no-alert': 0, // 禁止使用alert confirm prompt
|
||||
'no-console': 0, // 禁止使用console
|
||||
'no-debugger': 0, // 禁止使用debugger
|
||||
'prefer-const': 0, // 建议使用 const 关闭
|
||||
'no-dupe-keys': 2, // 在创建对象字面量时不允许键重复 {a:1,a:1}
|
||||
'no-dupe-args': 2, // 函数参数不能重复
|
||||
'no-duplicate-imports': [
|
||||
1,
|
||||
{
|
||||
includeExports: true
|
||||
}
|
||||
], // 不允许重复导入
|
||||
|
||||
'no-duplicate-case': 2, // switch中的case标签不能重复
|
||||
'padded-blocks': 0, // 块语句内行首行尾是否要空行
|
||||
'space-after-keywords': [0, 'always'], // 关键字后面是否要空一格
|
||||
'space-before-blocks': [0, 'always'], // 不以新行开始的块{前面要不要有空格
|
||||
'space-before-function-paren': [0, 'always'], // 函数定义时括号前面要不要有空格
|
||||
'space-in-parens': [0, 'never'], // 小括号里面要不要有空格
|
||||
'space-infix-ops': 0, // 中缀操作符周围要不要有空格
|
||||
eqeqeq: 0, // 必须使用全等
|
||||
'no-var': 0, // 禁用var,用let和const代替
|
||||
'no-inline-comments': 0, // 禁止行内备注
|
||||
indent: 0,
|
||||
'vue/script-indent': 0,
|
||||
'vue/require-prop-type-constructor': 0,
|
||||
'vue/no-use-v-if-with-v-for': 0,
|
||||
'no-trailing-spaces': 0, // 一行结束后面不要有空格
|
||||
'no-multiple-empty-lines': 0, // [1, {"max": 2}],空行最多不能超过2行
|
||||
'no-extra-boolean-cast': 0, // 禁止不必要的bool转换
|
||||
'valid-jsdoc': 0,
|
||||
'one-var': 0, // 连续声明
|
||||
semi: 0, // 语句强制分号结尾
|
||||
'semi-spacing': [0, { before: false, after: true }], // 分号前后空格
|
||||
'no-new': 0, // 禁止在使用new构造一个实例后不赋值
|
||||
'no-extra-semi': 0, // 禁止多余的冒号
|
||||
'keyword-spacing': 0,
|
||||
'arrow-parens': 0, // 箭头函数用小括号括起来 - 关闭
|
||||
'generator-star-spacing': 0, // 生成器函数*的前后空格
|
||||
'no-mixed-operators': 0,
|
||||
'eol-last': 0, // 文件以单一的换行符结束 - 关闭
|
||||
'object-curly-spacing': 0, // 大括号内是否允许不必要的空格
|
||||
'no-callback-literal': 0,
|
||||
'multiline-ternary': 0,
|
||||
'no-self-assign':'off',
|
||||
'vue/multi-word-component-names': "off",
|
||||
"vue/no-v-model-argument": "off",
|
||||
},
|
||||
plugins:[
|
||||
'vue'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
"requireConfigFile": false,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
dist.rar
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
36
README.en.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# aida_manage
|
||||
|
||||
#### Description
|
||||
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
21602
package-lock.json
generated
Normal file
68
package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "aida",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"build:test": "vue-cli-service build --mode test",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design-vue/use": "^0.0.1-alpha.10",
|
||||
"@vue/compiler-sfc": "^3.2.47",
|
||||
"ant-design-vue": "^3.2.12",
|
||||
"axios": "^0.27.2",
|
||||
"core-js": "^3.8.3",
|
||||
"js-base64": "^3.7.5",
|
||||
"md5": "^2.3.0",
|
||||
"moment": "^2.29.4",
|
||||
"vue": "^3.2.13",
|
||||
"vue-class-component": "^8.0.0-0",
|
||||
"vue-router": "^4.0.3",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@vue/eslint-config-typescript": "^9.1.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.0.0",
|
||||
"style-resources-loader": "^1.5.0",
|
||||
"typescript": "~4.5.5",
|
||||
"vue-cli-plugin-style-resources-loader": "^0.1.5",
|
||||
"vue-lazyload": "^3.0.0-rc.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
20
public/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>Mixi</title>
|
||||
<link href="https://fonts.font.im/css?family=Roboto:400,500,700,700i" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/color-thief/2.3.0/color-thief.umd.js"></script>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
11
src/App.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</style>
|
||||
115
src/assets/iconfont/iconfont.css
Normal file
@@ -0,0 +1,115 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3922178 */
|
||||
src: url('iconfont.woff2?t=1678713716640') format('woff2'),
|
||||
url('iconfont.woff?t=1678713716640') format('woff'),
|
||||
url('iconfont.ttf?t=1678713716640') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-tuichu1:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.icon-guanbi:before {
|
||||
content: "\eca0";
|
||||
}
|
||||
|
||||
.icon-shanchu:before {
|
||||
content: "\e663";
|
||||
}
|
||||
|
||||
.icon-tianjiatupian_huaban:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.icon-kaiguanswitch-jibenzhuangtai-dianliangzhuangtai:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.icon-gengduo:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.icon-31tishi:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.icon-biaoqian:before {
|
||||
content: "\e63d";
|
||||
}
|
||||
|
||||
.icon-a-gengduocaidangongneng:before {
|
||||
content: "\e689";
|
||||
}
|
||||
|
||||
.icon-icon-test:before {
|
||||
content: "\e668";
|
||||
}
|
||||
|
||||
.icon-xinzengyonghu:before {
|
||||
content: "\e634";
|
||||
}
|
||||
|
||||
.icon-xialajiantouxiao:before {
|
||||
content: "\e87e";
|
||||
}
|
||||
|
||||
.icon-8:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-lujing:before {
|
||||
content: "\e687";
|
||||
}
|
||||
|
||||
.icon-xinzengshangpin:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
|
||||
.icon-shangpinliebiao:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.icon-a-zhihuimendianduomendian:before {
|
||||
content: "\e67d";
|
||||
}
|
||||
|
||||
.icon-shangpinguanli:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.icon-xiaoshoubaobiao:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.icon-crmkehuguanli:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.icon-mendianguanli:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.icon-gongzuotai:before {
|
||||
content: "\e64a";
|
||||
}
|
||||
|
||||
.icon-xitongguanli:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.icon-weibiaoti:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.icon-yonghuming:before {
|
||||
content: "\e6aa";
|
||||
}
|
||||
|
||||
1
src/assets/iconfont/iconfont.js
Normal file
184
src/assets/iconfont/iconfont.json
Normal file
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"id": "3922178",
|
||||
"name": "mixi",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "8229473",
|
||||
"name": "退出",
|
||||
"font_class": "tuichu1",
|
||||
"unicode": "e612",
|
||||
"unicode_decimal": 58898
|
||||
},
|
||||
{
|
||||
"icon_id": "6616997",
|
||||
"name": "关闭",
|
||||
"font_class": "guanbi",
|
||||
"unicode": "eca0",
|
||||
"unicode_decimal": 60576
|
||||
},
|
||||
{
|
||||
"icon_id": "7450626",
|
||||
"name": "删除",
|
||||
"font_class": "shanchu",
|
||||
"unicode": "e663",
|
||||
"unicode_decimal": 58979
|
||||
},
|
||||
{
|
||||
"icon_id": "12753044",
|
||||
"name": "添加图片",
|
||||
"font_class": "tianjiatupian_huaban",
|
||||
"unicode": "e62c",
|
||||
"unicode_decimal": 58924
|
||||
},
|
||||
{
|
||||
"icon_id": "8897231",
|
||||
"name": "开关 switch-基本状态-关闭状态",
|
||||
"font_class": "kaiguanswitch-jibenzhuangtai-dianliangzhuangtai",
|
||||
"unicode": "e641",
|
||||
"unicode_decimal": 58945
|
||||
},
|
||||
{
|
||||
"icon_id": "22334201",
|
||||
"name": "更多",
|
||||
"font_class": "gengduo",
|
||||
"unicode": "e609",
|
||||
"unicode_decimal": 58889
|
||||
},
|
||||
{
|
||||
"icon_id": "201572",
|
||||
"name": "3.1-提示",
|
||||
"font_class": "31tishi",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "1305451",
|
||||
"name": "标签",
|
||||
"font_class": "biaoqian",
|
||||
"unicode": "e63d",
|
||||
"unicode_decimal": 58941
|
||||
},
|
||||
{
|
||||
"icon_id": "27226073",
|
||||
"name": "更多,菜单,功能",
|
||||
"font_class": "a-gengduocaidangongneng",
|
||||
"unicode": "e689",
|
||||
"unicode_decimal": 59017
|
||||
},
|
||||
{
|
||||
"icon_id": "1904339",
|
||||
"name": "头像 男孩",
|
||||
"font_class": "icon-test",
|
||||
"unicode": "e668",
|
||||
"unicode_decimal": 58984
|
||||
},
|
||||
{
|
||||
"icon_id": "9012695",
|
||||
"name": "新增用户",
|
||||
"font_class": "xinzengyonghu",
|
||||
"unicode": "e634",
|
||||
"unicode_decimal": 58932
|
||||
},
|
||||
{
|
||||
"icon_id": "2076220",
|
||||
"name": " 下拉箭头小",
|
||||
"font_class": "xialajiantouxiao",
|
||||
"unicode": "e87e",
|
||||
"unicode_decimal": 59518
|
||||
},
|
||||
{
|
||||
"icon_id": "11125634",
|
||||
"name": "门店",
|
||||
"font_class": "8",
|
||||
"unicode": "e624",
|
||||
"unicode_decimal": 58916
|
||||
},
|
||||
{
|
||||
"icon_id": "12010939",
|
||||
"name": "商品列表",
|
||||
"font_class": "lujing",
|
||||
"unicode": "e687",
|
||||
"unicode_decimal": 59015
|
||||
},
|
||||
{
|
||||
"icon_id": "17458088",
|
||||
"name": "新增商品",
|
||||
"font_class": "xinzengshangpin",
|
||||
"unicode": "e72c",
|
||||
"unicode_decimal": 59180
|
||||
},
|
||||
{
|
||||
"icon_id": "26247290",
|
||||
"name": "商品列表",
|
||||
"font_class": "shangpinliebiao",
|
||||
"unicode": "e61e",
|
||||
"unicode_decimal": 58910
|
||||
},
|
||||
{
|
||||
"icon_id": "26578334",
|
||||
"name": "智慧门店(多门店)",
|
||||
"font_class": "a-zhihuimendianduomendian",
|
||||
"unicode": "e67d",
|
||||
"unicode_decimal": 59005
|
||||
},
|
||||
{
|
||||
"icon_id": "1277993",
|
||||
"name": "商品管理",
|
||||
"font_class": "shangpinguanli",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "1964730",
|
||||
"name": "销售报表",
|
||||
"font_class": "xiaoshoubaobiao",
|
||||
"unicode": "e61c",
|
||||
"unicode_decimal": 58908
|
||||
},
|
||||
{
|
||||
"icon_id": "9748102",
|
||||
"name": "crm客户管理",
|
||||
"font_class": "crmkehuguanli",
|
||||
"unicode": "e648",
|
||||
"unicode_decimal": 58952
|
||||
},
|
||||
{
|
||||
"icon_id": "12250139",
|
||||
"name": "门店管理",
|
||||
"font_class": "mendianguanli",
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "20022023",
|
||||
"name": "工作台",
|
||||
"font_class": "gongzuotai",
|
||||
"unicode": "e64a",
|
||||
"unicode_decimal": 58954
|
||||
},
|
||||
{
|
||||
"icon_id": "27253679",
|
||||
"name": "系统管理",
|
||||
"font_class": "xitongguanli",
|
||||
"unicode": "e614",
|
||||
"unicode_decimal": 58900
|
||||
},
|
||||
{
|
||||
"icon_id": "6400627",
|
||||
"name": "密码(1)",
|
||||
"font_class": "weibiaoti",
|
||||
"unicode": "e616",
|
||||
"unicode_decimal": 58902
|
||||
},
|
||||
{
|
||||
"icon_id": "20853365",
|
||||
"name": "用户名",
|
||||
"font_class": "yonghuming",
|
||||
"unicode": "e6aa",
|
||||
"unicode_decimal": 59050
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src/assets/iconfont/iconfont.ttf
Normal file
BIN
src/assets/iconfont/iconfont.woff
Normal file
BIN
src/assets/iconfont/iconfont.woff2
Normal file
17
src/assets/iconfont2/iconfont.css
Normal file
@@ -0,0 +1,17 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id */
|
||||
src: url('iconfont.ttf?t=1709533486156') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-xiazaiwenjian:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
BIN
src/assets/iconfont2/iconfont.ttf
Normal file
BIN
src/assets/images/blue_bg.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/assets/images/green_bg.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
src/assets/images/header.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/assets/images/home_logo.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/images/loading.gif
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
src/assets/images/login_logo.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
src/assets/images/no_permission.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
src/assets/images/null_img.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/images/orange_bg.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/images/pink_bg.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/assets/images/purple_bg.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
src/assets/images/worktable_logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
197
src/assets/style/style.less
Normal file
@@ -0,0 +1,197 @@
|
||||
html,body,#app{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.list_page{
|
||||
padding-left: 28px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.list_page_content{
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px 0 50px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.list_top_content{
|
||||
padding: 0 35px 0 28px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.list_top_search_content{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list_top_button_content{
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.list_top_button_content{
|
||||
width: 200px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.list_table_content{
|
||||
width: 100%;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.edit_modal_component{
|
||||
|
||||
.modal_button_list{
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.primary_button{
|
||||
background: #343579;
|
||||
border-color:#343579;
|
||||
}
|
||||
.ant-btn-primary:hover, .ant-btn-primary:focus{
|
||||
border-color: #343579;
|
||||
}
|
||||
|
||||
.default_button{
|
||||
background: #E4E5EB;
|
||||
}
|
||||
|
||||
.btn-margin-r-20{
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.btn-margin-t-35{
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.confirm_style{
|
||||
|
||||
.ant-btn-primary{
|
||||
background: #343579;
|
||||
}
|
||||
}
|
||||
|
||||
.form_item_block{
|
||||
position: relative;
|
||||
|
||||
.form_title_button_list{
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.form_item_module_title{
|
||||
font-size: 18px;
|
||||
line-height: 18px;
|
||||
font-weight: normal;
|
||||
color: #030303;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sec_form_block{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form_item_title{
|
||||
color: #030303;
|
||||
font-size: 18px;
|
||||
line-height: 1.5715;
|
||||
overflow: hidden;
|
||||
|
||||
&.required::before{
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
color: #ff4d4f;
|
||||
font-size: 14px;
|
||||
font-family: SimSun, sans-serif;
|
||||
line-height: 1;
|
||||
content: '*';
|
||||
}
|
||||
|
||||
.title_right_button{
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-title{
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #030303;
|
||||
}
|
||||
|
||||
.ant-form-item-label > label{
|
||||
font-size: 18px;
|
||||
color: #030303;
|
||||
}
|
||||
|
||||
.scroll_style{
|
||||
&::-webkit-scrollbar-button:single-button {
|
||||
// background-color: #ffffff;
|
||||
display: block;
|
||||
border-style: solid;
|
||||
height: 13px;
|
||||
width: 14px;
|
||||
}
|
||||
&::-webkit-scrollbar-button:single-button:vertical:decrement {
|
||||
border-width: 0 8px 8px 8px;
|
||||
border-color: transparent transparent #555555 transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-button:single-button:vertical:decrement:hover {
|
||||
border-color: transparent transparent #777777 transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-button:single-button:vertical:increment {
|
||||
border-width: 0 8px 8px 8px;
|
||||
border-color: #555555 transparent transparent transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-button:vertical:single-button:increment:hover {
|
||||
border-color: #777777 transparent transparent transparent;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
/*滚动条整体样式*/
|
||||
width: 14px; /*高宽分别对应横竖滚动条的尺寸*/
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
/*滚动条里面小方块*/
|
||||
background: #c2c2c2;
|
||||
opacity: 0.8;
|
||||
border-radius: 7px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
// background: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-upload-list-picture-card-container{
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
//蒙层样式
|
||||
.mark_loading{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
74
src/component/filterComponent.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="filter_component">
|
||||
<div class="filter_component_content">
|
||||
<span class="filter_title" :style="titleStyle">{{title}}:</span>
|
||||
<div class="slot_content" :style="slotStyle">
|
||||
<slot class="slot_style"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref } from "vue";
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name:'filterComponent',
|
||||
props:{
|
||||
title:{
|
||||
type:String
|
||||
},
|
||||
titleStyle:{
|
||||
type:Object
|
||||
},
|
||||
slotStyle:{
|
||||
type:Object
|
||||
}
|
||||
},
|
||||
setup(){
|
||||
return {
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.filter_component{
|
||||
display: inline-block;
|
||||
margin-right: 40px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.filter_component_content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.filter_title{
|
||||
font-size: 16px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #030303;
|
||||
margin-right: 15px;
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
min-width: 150px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.slot_content{
|
||||
width: 280px;
|
||||
.ant-picker-large{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
385
src/component/productComponent/addProduct.vue
Normal file
@@ -0,0 +1,385 @@
|
||||
<template>
|
||||
<div class="add_product_modal">
|
||||
<a-modal class="add_product_modal_component"
|
||||
v-model:visible="addProductModal"
|
||||
:footer="null"
|
||||
title="Add Product"
|
||||
width="680px"
|
||||
:closable="false"
|
||||
:destroyOnClose="true"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
:keyboard="false"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-form-item label="Add picture" name="pictureList">
|
||||
<div class="picture_list">
|
||||
<div class="picture_list_block" v-show="showPicture.length">
|
||||
<div class="pricture_item_block" v-for="pic in showPicture" :key="pic">
|
||||
<img class="pricture_item" :src="pic?.resData.pictureUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pricture_item_block add_pic_button" @click="openUploadModal">
|
||||
<div class="add_pic_button_content">
|
||||
<span class="icon iconfont add_pic_button_icon icon-tianjiatupian_huaban"></span>
|
||||
<div>{{showPicture.length ? '继续上传' :'上传'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="Label attributes" name="productLabel">
|
||||
<a-select v-model:value="formState.productLabel" mode="multiple" size="large" optionFilterProp="label" :options="productLabelList" showArrow></a-select>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="Price" name="price">
|
||||
<a-input-number
|
||||
style="width:100%"
|
||||
v-model:value="formState.price"
|
||||
:formatter="value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
|
||||
:parser="value => value.replace(/\$\s?|(,*)/g, '')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Store Inventory" name="storeInventory">
|
||||
<div class="store_inventory_block">
|
||||
<div class="store_inventory_content">{{storeNameList}}</div>
|
||||
<a-button class="primary_button" type="primary" @click="selectStore">Choose store</a-button>
|
||||
</div>
|
||||
</a-form-item> -->
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeProduct">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" :loading="submitLoading" @click="submitAddProduct">Submit</a-button>
|
||||
</div>
|
||||
|
||||
<!-- design collection的进度蒙层 start-->
|
||||
<div class="progress_mark" v-show="showDesignMark">
|
||||
<div class="mark_content">
|
||||
<a-progress type="circle" :percent="designProgress" :width="200"/>
|
||||
<div class="mark_content_spin">
|
||||
<a-spin :indicator="indicator"/>
|
||||
</div>
|
||||
<div class="upload_tip" v-show="uploadType == 'Attribute'">Recognizing garment, please wait.</div>
|
||||
<div class="upload_tip" v-show="uploadType == 'Collocation'">Generating outfit, please wait.</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- design collection的进度蒙层 end-->
|
||||
</a-modal>
|
||||
|
||||
<ProductPicUpload ref="productPicUploadModal" @uploadPicChange="uploadPicChange"></ProductPicUpload>
|
||||
<AddStoreModal ref="addStoreModalComponent" @storeChange="storeChange"></AddStoreModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,h,ref,reactive,toRefs,onMounted, computed,createVNode } from "vue";
|
||||
import ProductPicUpload from '@/component/productComponent/productPicUpload.vue'
|
||||
import AddStoreModal from '@/component/productComponent/addStoreModa.vue'
|
||||
import { Https } from "@/tool/https";
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import {unique} from '@/tool/util'
|
||||
import { WarningOutlined,LoadingOutlined } from '@ant-design/icons-vue';
|
||||
export default defineComponent({
|
||||
name:'AddProduct',
|
||||
components:{ProductPicUpload,AddStoreModal},
|
||||
emits: ['confirmSubmitAdd'],
|
||||
props:{
|
||||
productLabelList:{
|
||||
type:Array,
|
||||
}
|
||||
},
|
||||
setup(props,{ emit }){
|
||||
let formRef = ref();
|
||||
let indicator :any = h(LoadingOutlined, {
|
||||
style: {
|
||||
fontSize: '24px',
|
||||
},
|
||||
spin: true,
|
||||
})
|
||||
let addProductModal = ref(false)
|
||||
let showDesignMark = ref(false)
|
||||
let designProgress:any = ref(0)
|
||||
let uploadType:any = ref('Attribute')
|
||||
let addStoreModal = ref(false)
|
||||
let productPicUploadModal:any = ref(null)
|
||||
let formState:any = ref({
|
||||
pictureList: [],
|
||||
productLabel:[],
|
||||
price:'',
|
||||
storeInventory:'',
|
||||
});
|
||||
let submitLoading = ref(false)
|
||||
let addStoreModalComponent:any = ref(null)
|
||||
let storeNameList:any = ref('')
|
||||
let showPicture = computed(()=> formState.value.pictureList.length > 4 ? formState.value.pictureList.slice(0,4) : formState.value.pictureList)
|
||||
|
||||
|
||||
let rules = {
|
||||
pictureList: [
|
||||
{ required: true, message: 'Please upload product image', trigger: 'blur' },
|
||||
],
|
||||
productLabel: [
|
||||
{ required: true, message: 'Please select product type', trigger: 'blur' },
|
||||
],
|
||||
price: [
|
||||
{ required: true, message: 'Please input price', trigger: 'blur' },
|
||||
],
|
||||
storeInventory:[
|
||||
{ required: true, message: 'Please select store',trigger: 'blur' },
|
||||
]
|
||||
}
|
||||
|
||||
let showModal = (data:any) =>{
|
||||
addProductModal.value = true
|
||||
}
|
||||
|
||||
|
||||
let closeProduct = () =>{
|
||||
Modal.confirm({
|
||||
title: "Are you sure you want to close the popup? Closing the popup will clear the set data.",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
formState.value = {
|
||||
pictureList: '',
|
||||
productLabel:[],
|
||||
storeInventory:'',
|
||||
}
|
||||
clearChildData()
|
||||
storeNameList.value = ''
|
||||
addProductModal.value = false
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
let openUploadModal = () =>{
|
||||
productPicUploadModal.value.openPictureModal()
|
||||
}
|
||||
|
||||
let selectStore = () =>{
|
||||
addStoreModalComponent.value.openAddStoreModal({type:'add'})
|
||||
}
|
||||
|
||||
let uploadPicChange = (event:any) =>{
|
||||
formState.value.pictureList = event.fileList
|
||||
formRef.value.validateFields(['pictureList'])
|
||||
}
|
||||
|
||||
let storeChange = (event:any) =>{
|
||||
formState.value.storeInventory = event
|
||||
|
||||
storeNameList.value = event.map((v:any) => v.storeName).join(',')
|
||||
formRef.value.validateFields(['storeInventory'])
|
||||
}
|
||||
|
||||
let submitAddProduct = () =>{
|
||||
formRef.value.validate().then(()=>{
|
||||
submit()
|
||||
})
|
||||
let submit = () =>{
|
||||
let pictureList = unique(formState.value.pictureList, 'md5')
|
||||
let productIds = pictureList.map((v:any)=>{
|
||||
return v.resData.productId
|
||||
})
|
||||
let data = {
|
||||
labelIds:formState.value.productLabel,
|
||||
productIds:productIds,
|
||||
// price:formState.value.price,
|
||||
// storeInfos:formState.value.storeInventory,
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
submitLoading.value = true
|
||||
showDesignMark.value = true
|
||||
Https.axiosPost(Https.httpUrls.batchUploadProductRelation, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
submitLoading.value = false
|
||||
showDesignMark.value = false
|
||||
emit('confirmSubmitAdd')
|
||||
formState.value = {
|
||||
pictureList: '',
|
||||
productLabel:[],
|
||||
storeInventory:'',
|
||||
}
|
||||
clearChildData()
|
||||
addProductModal.value = false
|
||||
storeNameList.value = ''
|
||||
designProgress.value = 0
|
||||
uploadType.value = 'Attribute'
|
||||
}
|
||||
}
|
||||
).catch((rv:any)=>{
|
||||
submitLoading.value = false
|
||||
showDesignMark.value = false
|
||||
designProgress.value = 0
|
||||
uploadType.value = 'Attribute'
|
||||
});
|
||||
submitProcess()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
let submitProcess = () =>{
|
||||
Https.axiosPost(Https.httpUrls.countProductUpdateProcess, {}).then(
|
||||
(rv: any) => {
|
||||
if(rv.status != 1 && showDesignMark.value){
|
||||
let designProgressNew:any = rv.process * 100
|
||||
designProgress.value = parseInt(designProgressNew)
|
||||
uploadType.value = rv.uploadType
|
||||
setTimeout(()=>{
|
||||
submitProcess()
|
||||
},2000)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//清除子组件的数据
|
||||
let clearChildData = () =>{
|
||||
productPicUploadModal.value.clearPicData()
|
||||
addStoreModalComponent.value.clearStoreData()
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
formRef,
|
||||
indicator,
|
||||
addProductModal,
|
||||
showDesignMark,
|
||||
designProgress,
|
||||
uploadType,
|
||||
addStoreModal,
|
||||
productPicUploadModal,
|
||||
submitLoading,
|
||||
showModal,
|
||||
storeNameList,
|
||||
formState,
|
||||
showPicture,
|
||||
rules,
|
||||
closeProduct,
|
||||
openUploadModal,
|
||||
selectStore,
|
||||
addStoreModalComponent,
|
||||
uploadPicChange,
|
||||
storeChange,
|
||||
submitAddProduct,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
||||
.add_product_modal_component{
|
||||
.picture_list{
|
||||
display: flex;
|
||||
|
||||
.picture_list_block{
|
||||
display: flex;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.pricture_item_block{
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: -13px;
|
||||
|
||||
&.add_pic_button{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.add_pic_button_content{
|
||||
text-align: center;
|
||||
color: #C7CDD5;
|
||||
font-size: 16px;
|
||||
|
||||
.add_pic_button_icon{
|
||||
font-size: 36px;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child{
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pricture_item{
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress_mark{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.mark_content{
|
||||
text-align: center;
|
||||
|
||||
.mark_content_spin{
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.upload_tip{
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.store_inventory_block{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 46px;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 0 12px;
|
||||
|
||||
.store_inventory_content{
|
||||
width: 500px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add_store_modal_component{
|
||||
|
||||
.modal_button_list{
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.store_input{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
290
src/component/productComponent/addStoreModa.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<div class="add_product_modal">
|
||||
<a-modal class="add_store_modal_component"
|
||||
v-model:visible="addStoreModal"
|
||||
:footer="null"
|
||||
title="Choose store"
|
||||
width="1080px"
|
||||
:destroyOnClose="true"
|
||||
:closable="false"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
>
|
||||
<a-table :columns="columns" :data-source="storeList" bordered
|
||||
:pagination="false" :scroll="{y: 400 }">
|
||||
<template v-slot:bodyCell="{column,record}">
|
||||
<template v-if="column.dataIndex === 'storeName'">
|
||||
<a-checkbox v-model:checked="record.storeCheck">{{record.storeName}}</a-checkbox>
|
||||
</template>
|
||||
<template v-for="item in storeNameData">
|
||||
<template v-if="column.dataIndex === item">
|
||||
<a-input class="store_input" v-model:value="record[item]" type="number" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- <template v-slot:bodyCell="{column,record}" >
|
||||
<template v-if="column.dataIndex === 'storeName'">
|
||||
<a-checkbox v-model:checked="record.storeCheck">{{record.storeName}}</a-checkbox>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'XS'">
|
||||
<a-input class="store_input" v-model:value="record.XS" type="number" :min="0"/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'S'">
|
||||
<a-input class="store_input" v-model:value="record.S" type="number" :min="0"/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'M'">
|
||||
<a-input class="store_input" v-model:value="record.M" type="number" :min="0"/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'L'">
|
||||
<a-input class="store_input" v-model:value="record.L" type="number" :min="0"/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'XL'">
|
||||
<a-input class="store_input" v-model:value="record.XL" type="number" :min="0"/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'XXL'">
|
||||
<a-input class="store_input" v-model:value="record.XXL" type="number" :min="0"/>
|
||||
</template>
|
||||
</template> -->
|
||||
</a-table>
|
||||
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeProduct">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="submitStore">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,onMounted, computed,createVNode } from "vue";
|
||||
import { Https } from "@/tool/https";
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
export default defineComponent({
|
||||
name:'AddStoreModal',
|
||||
emits: ['storeChange'],
|
||||
setup(props,{ emit }){
|
||||
let addStoreModal = ref(false)
|
||||
let addStoreType = ref('add')
|
||||
|
||||
let columns = reactive([
|
||||
{ title: 'Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',width:"200px" },
|
||||
{
|
||||
title: 'Inventory',
|
||||
align:'center',
|
||||
ellipsis: true,
|
||||
dataIndex: 'inventory',
|
||||
key: 'inventory',
|
||||
children: [
|
||||
// {title: 'XS',dataIndex: 'XS',key: 'XS', align:'center', },
|
||||
// {title: 'S',dataIndex: 'S',key: 'S', align:'center',},
|
||||
// {title: 'M',dataIndex: 'M',key: 'M', align:'center',},
|
||||
// {title: 'L',dataIndex: 'L',key: 'L', align:'center',},
|
||||
// {title: 'XL',dataIndex: 'XL',key: 'XL', align:'center',},
|
||||
// {title: 'XXL',dataIndex: 'XXL',key: 'XXL', align:'center',},
|
||||
],
|
||||
},
|
||||
])
|
||||
let storeList:any = ref([])
|
||||
let backupStoreList:any = ref([])//提交时备份的数据
|
||||
let storeNameData:any = ref([])
|
||||
let openAddStoreModal = async (data:any) =>{
|
||||
if(data.type === 'add'){
|
||||
if(backupStoreList.value.length){
|
||||
storeList.value = JSON.parse(JSON.stringify(backupStoreList.value))
|
||||
}
|
||||
}else{
|
||||
let storeIds:any = []
|
||||
for(let item of data.storeData){
|
||||
storeIds.push(item.storeId)
|
||||
}
|
||||
let children:any = []
|
||||
data.storeNameData.forEach((item:any) => {
|
||||
children.push({
|
||||
title:item,
|
||||
dataIndex:item,
|
||||
key:item,
|
||||
align:'center'
|
||||
})
|
||||
});
|
||||
columns[1].children = children
|
||||
storeNameData.value = data.storeNameData
|
||||
await storeQueryAll()
|
||||
storeList.value = backupStoreList.value.filter((v:any) => storeIds.indexOf(v.id) == -1)
|
||||
console.log(storeList.value);
|
||||
|
||||
}
|
||||
addStoreModal.value = true
|
||||
addStoreType.value = data.type
|
||||
}
|
||||
|
||||
|
||||
let submitStore= () => {
|
||||
if(addStoreType.value === 'add'){
|
||||
addStore()
|
||||
}else{
|
||||
editStore()
|
||||
}
|
||||
}
|
||||
|
||||
let addStore = () =>{
|
||||
let field:any = JSON.parse(JSON.stringify(storeNameData.value))
|
||||
let storeInfos = []
|
||||
for(let v of storeList.value){
|
||||
if(v.storeCheck){
|
||||
let data:any = {
|
||||
stock:[],
|
||||
storeId:v.id,
|
||||
storeName:v.storeName,
|
||||
}
|
||||
for(let key in v){
|
||||
if(field.indexOf(key) > -1){
|
||||
let sizeData:any = {
|
||||
num:v[key],
|
||||
size:key
|
||||
}
|
||||
data.stock.push(sizeData)
|
||||
}
|
||||
}
|
||||
storeInfos.push(data)
|
||||
}
|
||||
}
|
||||
backupStoreList.value = JSON.parse(JSON.stringify(storeList.value))
|
||||
emit('storeChange',storeInfos)
|
||||
addStoreModal.value = false
|
||||
}
|
||||
|
||||
let editStore = () =>{
|
||||
let storeInfos = storeList.value.filter((v:any) => v.storeCheck)
|
||||
emit('storeChange',storeInfos)
|
||||
addStoreModal.value = false
|
||||
}
|
||||
|
||||
let closeProduct = () =>{
|
||||
Modal.confirm({
|
||||
title: "To confirm the cancellation of setting store information? Cancellation will clear the setting data for this time.",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
storeList.value = JSON.parse(JSON.stringify(backupStoreList.value))
|
||||
addStoreModal.value = false
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let storeQueryAll = async () =>{
|
||||
let storeData:any = {
|
||||
storeCheck:false,
|
||||
}
|
||||
await new Promise((resolve,reject)=>{
|
||||
storeNameData.value.forEach((item:any) => {
|
||||
storeData[item] = 0
|
||||
});
|
||||
console.log(storeData);
|
||||
|
||||
Https.axiosPost(Https.httpUrls.queryProductStore,{}).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
storeList.value = rv.map((v:any)=>{
|
||||
let data = {
|
||||
...v,
|
||||
storeName:v.name,
|
||||
storeId:v.id,
|
||||
...storeData,
|
||||
}
|
||||
return data
|
||||
})
|
||||
backupStoreList.value = JSON.parse(JSON.stringify(storeList.value))
|
||||
resolve('')
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
let clearStoreData = () =>{
|
||||
storeQueryAll()
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
// storeQueryAll()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
addStoreModal,
|
||||
storeNameData,
|
||||
columns,
|
||||
storeList,
|
||||
openAddStoreModal,
|
||||
closeProduct,
|
||||
submitStore,
|
||||
clearStoreData,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
||||
.add_product_modal_component{
|
||||
.picture_list{
|
||||
display: flex;
|
||||
|
||||
.pricture_item_block{
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: -30px;
|
||||
|
||||
&.add_pic_button{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add_pic_button_content{
|
||||
text-align: center;
|
||||
color: #C7CDD5;
|
||||
font-size: 16px;
|
||||
|
||||
.add_pic_button_icon{
|
||||
font-size: 36px;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child{
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pricture_item{
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add_store_modal_component{
|
||||
|
||||
.modal_button_list{
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.store_input{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
769
src/component/productComponent/editProduct.vue
Normal file
@@ -0,0 +1,769 @@
|
||||
<template>
|
||||
<div class="add_product_modal">
|
||||
<a-modal class="add_product_modal_component"
|
||||
wrapClassName="edit_modal"
|
||||
:destroyOnClose="true"
|
||||
v-model:visible="editProductModal"
|
||||
:footer="null"
|
||||
title="Eidt Product"
|
||||
width="900px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
@cancel="closeProduct"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-form-item label="Product image" name="pictureUrl">
|
||||
<div class="product_master_diagram">
|
||||
<a-upload
|
||||
:action="uploadUrl + '/api/product/uploadFile'"
|
||||
class="edit_product_pic"
|
||||
list-type="picture-card"
|
||||
:data="{
|
||||
...upload,
|
||||
id:productDetail.id
|
||||
}"
|
||||
:headers="{Authorization:token}"
|
||||
v-model:file-list="fileList"
|
||||
:maxCount="1"
|
||||
accept=".jpg,.png,.jpeg,.bmp"
|
||||
@change="(file)=>fileUploadChange(file)"
|
||||
>
|
||||
<img class="product_master_img" :src="formState.pictureUrl">
|
||||
</a-upload>
|
||||
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="Product label(s)" name="productLabel">
|
||||
<a-select v-model:value="formState.productLabel" mode="multiple" size="large" :options="productLabelList" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)" optionFilterProp="label"></a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="Price" name="price">
|
||||
<a-input-number
|
||||
style="width:100%"
|
||||
v-model:value="formState.price"
|
||||
:formatter="value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
|
||||
:parser="value => value.replace(/\$\s?|(,*)/g, '')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<div class="form_item_block">
|
||||
<!-- <a-form-item label="库存信息" name="inventoryData"></a-form-item> -->
|
||||
<div class="form_item_title required">
|
||||
Inventory
|
||||
<!-- <div class="title_right_button">
|
||||
<a-button class="primary_button" type="primary" @click="addStore">+ Add stores</a-button>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="form_edit_table">
|
||||
<a-table :columns="columns" :data-source="formState.storeList" bordered
|
||||
:pagination="false" :scroll="{y: 400 }">
|
||||
<template v-slot:bodyCell="{column,record}">
|
||||
<template v-for="item in formState.storeNameList">
|
||||
<template v-if="column.dataIndex === item">
|
||||
<a-input class="store_input" v-model:value="record[item]" type="number" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- <template v-slot:bodyCell="{column,record}" >
|
||||
<template v-if="column.dataIndex === 'XS'">
|
||||
<a-input class="store_input" v-model:value="record.XS" type="number" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'S'">
|
||||
<a-input class="store_input" v-model:value="record.S" type="number" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'M'">
|
||||
<a-input class="store_input" v-model:value="record.M" type="number" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'L'">
|
||||
<a-input class="store_input" v-model:value="record.L" type="number" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'XL'">
|
||||
<a-input class="store_input" v-model:value="record.XL" type="number" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'XXL'">
|
||||
<a-input class="store_input" v-model:value="record.XXL" type="number" />
|
||||
</template>
|
||||
</template> -->
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form_item_block">
|
||||
<div class="form_item_module_title">Label attributes</div>
|
||||
<a-form-item label="Select category" name="productCategory">
|
||||
<a-select v-model:value="formState.productCategory" size="large" :options="productCategoryList" placeholder="Please select" @change="productCategoryChange" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)" optionFilterProp="label"></a-select>
|
||||
</a-form-item>
|
||||
|
||||
<div class="sec_form_block">
|
||||
<div class="form_item_title margin_bottom_10">Attribute(s)</div>
|
||||
<div class="attribute_block" v-if="productAttributeList.length">
|
||||
<div class="attribute_block_item" v-for="attr in productAttributeList" :key="attr.id">
|
||||
<div class="attribute_block_item_content">
|
||||
<div class="attribute_item_title">{{attr.labelType}}</div>
|
||||
<a-select class="attribute_item_select" allowClear v-model:value="attr.value" size="large" :options="attr.attributeValueList" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)"></a-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="null_data_block">
|
||||
<img class="null_data_img" src="@/assets/images/null_img.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form_item_block">
|
||||
<!-- <a-form-item label="库存信息" name="inventoryData"></a-form-item> -->
|
||||
<div class="form_item_title">
|
||||
Mix and Match(es)
|
||||
<div class="title_right_button">
|
||||
<a-button class="primary_button" type="primary" @click="addLook">+ Add Look</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product_match_block" v-show="formState.assortmentList.length">
|
||||
<div class="product_match_item" v-for="(match,index) in formState.assortmentList" :key="match" v-show="index<5 || assortmentShowMore">
|
||||
<div class="product_match_item_header">
|
||||
<div>Look{{index+1}}</div>
|
||||
<div class="item_header_button_list">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" @click="deleteSingleAllMatch(index)">Delete</a-button>
|
||||
<a-button class="primary_button" type="primary" @click="addProductMatch(match,index)" v-show="match.length<6">Add Item</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="product_match_item_content">
|
||||
<div class="match_item_img_block" v-for="(img,imgIndex) in match" :key="img.id">
|
||||
<img :src="img.generatePictureUrl">
|
||||
<div class="delete_img_block" @click="deleteSignleMatchImg(index,imgIndex)">
|
||||
<span class="icon iconfont delete_icon icon-guanbi"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-button class="primary_button show_more_button" type="primary" @click="assortmentListShowMore()" v-show="formState.assortmentList.length>5">{{assortmentShowMore?'Hide':'See More'}}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeProduct">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="submitProduct" :loading="submitLoading">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<ProductMatchModal ref="ProductMatchModalComponent" :productCategoryList="productCategoryList" @selectMatchFile="selectMatchFile"></ProductMatchModal>
|
||||
<AddStoreModal ref="addStoreModalComponent" @storeChange="storeChange"></AddStoreModal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,onMounted, computed, nextTick } from "vue";
|
||||
import ProductMatchModal from '@/component/productComponent/productMatchModal.vue'
|
||||
import AddStoreModal from '@/component/productComponent/addStoreModa.vue'
|
||||
import {getCookie} from '@/tool/cookie'
|
||||
import {getUploadUrl} from '@/tool/util'
|
||||
import { Https } from "@/tool/https";
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
export default defineComponent({
|
||||
name:'EditProduct',
|
||||
components:{ProductMatchModal,AddStoreModal},
|
||||
props:{
|
||||
productLabelList:{
|
||||
type:Array,
|
||||
}
|
||||
},
|
||||
emits:['refreshList'],
|
||||
setup(props,{ emit }){
|
||||
let formRef = ref();
|
||||
let submitLoading:any = ref(false)
|
||||
let addStoreModalComponent:any = ref(null)
|
||||
let uploadUrl:any = ref('')
|
||||
let fileList:any = ref([])
|
||||
let token:any = ref('')
|
||||
let upload:any = reactive({
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
})
|
||||
let editProductModal = ref(false)
|
||||
let productPicSeletcModal:any = ref()
|
||||
let ProductMatchModalComponent:any =ref()
|
||||
let productDetail:any =ref({})
|
||||
let formState:any = ref({
|
||||
pictureName:'',
|
||||
pictureUrl:'',
|
||||
productLabel:[],
|
||||
inventoryData:[],
|
||||
storeList:[
|
||||
],
|
||||
storeNameList:[],
|
||||
productCategory:[],
|
||||
productAttribute:[],
|
||||
assortmentList:[]
|
||||
});
|
||||
let productCategoryList:any = ref([])
|
||||
let productAttributeList = ref([])
|
||||
let columns = reactive([
|
||||
{ title: 'Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',width:"200px" },
|
||||
{
|
||||
title: 'Inventory',
|
||||
align:'center',
|
||||
ellipsis: true,
|
||||
dataIndex: 'inventory',
|
||||
key: 'inventory',
|
||||
children: [
|
||||
// {title: 'XS',dataIndex: 'XS',key: 'XS', align:'center', },
|
||||
// {title: 'S',dataIndex: 'S',key: 'S', align:'center',},
|
||||
// {title: 'M',dataIndex: 'M',key: 'M', align:'center',},
|
||||
// {title: 'L',dataIndex: 'L',key: 'L', align:'center',},
|
||||
// {title: 'XL',dataIndex: 'XL',key: 'XL', align:'center',},
|
||||
// {title: 'XXL',dataIndex: 'XXL',key: 'XXL', align:'center',},
|
||||
],
|
||||
},
|
||||
])
|
||||
let addMatchIndex:number = 0
|
||||
|
||||
let rules = {
|
||||
pictureUrl: [
|
||||
{ required: true, trigger: 'blur' },
|
||||
],
|
||||
productLabel: [
|
||||
{ required: true, message: 'Please select product label', trigger: 'blur' },
|
||||
],
|
||||
inventoryData:[
|
||||
{ required: true, trigger: 'blur' },
|
||||
],
|
||||
productCategory:[
|
||||
{ required: true, message: 'Please select', trigger: 'blur' },
|
||||
],
|
||||
productAttribute:[
|
||||
{ required: true, message: 'Please select', trigger: 'blur' },
|
||||
]
|
||||
}
|
||||
|
||||
let assortmentShowMore = ref(false)
|
||||
let getPopupContainer = (triggerNode:any) =>{
|
||||
return triggerNode.parentNode || document.body
|
||||
}
|
||||
|
||||
|
||||
let closeProduct = () =>{
|
||||
formState.value = {
|
||||
pictureName:'',
|
||||
pictureUrl:'',
|
||||
productLabel:[],
|
||||
inventoryData:[],
|
||||
storeList:[],
|
||||
productCategory:[],
|
||||
productAttribute:[],
|
||||
assortmentList:[],
|
||||
storeNameList:[],
|
||||
}
|
||||
productAttributeList.value =[]
|
||||
editProductModal.value = false
|
||||
addStoreModalComponent.value.clearStoreData()
|
||||
}
|
||||
|
||||
let showModal = (data:any) =>{
|
||||
let record = JSON.parse(JSON.stringify(data.record))
|
||||
let store = getStoreInfo(record.storeInfoList)
|
||||
let labelItemList = JSON.parse(JSON.stringify(data.labelItemList))
|
||||
editProductModal.value = true
|
||||
productDetail.value = record
|
||||
formState.value = {
|
||||
pictureName:record.pictureName,
|
||||
pictureUrl:record.pictureUrl,
|
||||
productLabel:record.productLabelInfo.map((v:any)=>v.id),
|
||||
price:record.price,
|
||||
productCategory:record.attributeItemInfo.labelItem,
|
||||
storeList:store.storeList,
|
||||
storeNameList:store.storeNameList,
|
||||
assortmentList:record.assortmentList,
|
||||
}
|
||||
productCategoryList.value = getProductCategoryList(labelItemList)
|
||||
productAttributeList.value = getProductAttributeList()
|
||||
formState.value.productAttribute = productAttributeList.value
|
||||
}
|
||||
|
||||
let getProductAttributeList = () => {
|
||||
let attributeList = productCategoryList.value.filter((v:any) => v.labelItem == formState.value.productCategory)
|
||||
if(attributeList && attributeList.length){
|
||||
attributeList = attributeList[0]
|
||||
}else{
|
||||
return []
|
||||
}
|
||||
let attributeValueList = attributeList.attributeTypeList.map((v:any) =>{
|
||||
v.value = ''
|
||||
for(let item of productDetail.value.attributeItemInfo.attributeTypeList){
|
||||
if(v.labelType === item.labelType){
|
||||
v.value = item.attributeValueList.join(',')
|
||||
}
|
||||
|
||||
}
|
||||
return v
|
||||
})
|
||||
return attributeValueList
|
||||
}
|
||||
|
||||
let getProductCategoryList =(labelItemList:any)=>{
|
||||
let newLabelItemList = []
|
||||
let labelItemListNew = JSON.parse(JSON.stringify(labelItemList))
|
||||
for(let item of labelItemListNew){
|
||||
item.attributeTypeList = item.attributeTypeList.map((v:any) =>{
|
||||
let data = {
|
||||
...v,
|
||||
value:v.value || [],
|
||||
attributeValueList:v.attributeValueList.map((v:any)=>{
|
||||
let valueData = {
|
||||
value:v,
|
||||
label:v,
|
||||
}
|
||||
return valueData
|
||||
})
|
||||
|
||||
}
|
||||
return data
|
||||
})
|
||||
newLabelItemList.push(item)
|
||||
}
|
||||
return newLabelItemList
|
||||
}
|
||||
|
||||
let getStoreInfo = (storeInfo:any) =>{
|
||||
let storeList = []
|
||||
let storeNameList:any = []
|
||||
for(let item of storeInfo){
|
||||
let sizeData:any = {}
|
||||
for(let size of item.stock){
|
||||
sizeData[size.size] = size.num
|
||||
}
|
||||
let data = {
|
||||
storeName:item.storeName,
|
||||
storeId:item.storeId,
|
||||
...sizeData,
|
||||
}
|
||||
storeList.push(data)
|
||||
}
|
||||
storeInfo.forEach((item:any) => {
|
||||
item.stock.forEach((stockItem:any) => {
|
||||
storeNameList.push(stockItem.size);
|
||||
});
|
||||
});
|
||||
|
||||
// 排序并去重
|
||||
storeNameList = storeNameList.sort().filter((value:any, index:any, array:any) => {
|
||||
return array.indexOf(value) === index;
|
||||
});
|
||||
let children:any = []
|
||||
storeNameList.forEach((item:any) => {
|
||||
children.push({
|
||||
title:item,
|
||||
dataIndex:item,
|
||||
key:item,
|
||||
align:'center'
|
||||
})
|
||||
});
|
||||
columns[1].children = children
|
||||
return {storeList,storeNameList}
|
||||
}
|
||||
|
||||
let openUploadModal = () =>{
|
||||
productPicSeletcModal.value.openPictureModal()
|
||||
}
|
||||
|
||||
//删除单条的全部搭配
|
||||
let deleteSingleAllMatch = (index:number) =>{
|
||||
formState.value.assortmentList.splice(index,1)
|
||||
}
|
||||
|
||||
let deleteSignleMatchImg = (matchindex:any,index:number) =>{
|
||||
if(formState.value.assortmentList[matchindex].length == 1){
|
||||
deleteSingleAllMatch(matchindex)
|
||||
}else{
|
||||
formState.value.assortmentList[matchindex].splice(index,1)
|
||||
}
|
||||
}
|
||||
|
||||
let addProductMatch = (match:any,index:number) =>{
|
||||
addMatchIndex = index
|
||||
let assortmentIdList = match.map((v:any)=>{
|
||||
return v.id
|
||||
})
|
||||
ProductMatchModalComponent.value.openMatchModal(assortmentIdList,productCategoryList.value)
|
||||
}
|
||||
|
||||
let submitStore= () => {
|
||||
}
|
||||
|
||||
let productCategoryChange = (value:any,selectedOptions:any) => {
|
||||
let attributeList = selectedOptions ? selectedOptions.attributeTypeList :[]
|
||||
productAttributeList.value = attributeList
|
||||
formState.value.productAttribute = attributeList
|
||||
}
|
||||
|
||||
let fileUploadChange = (data:any) =>{
|
||||
let file = data.file
|
||||
if(file.status === 'done'){
|
||||
let res = JSON.parse(file.xhr.response)
|
||||
if(res.data){
|
||||
productDetail.value.md5 = res.data.md5
|
||||
formState.value.pictureUrl = res.data.pictureUrl
|
||||
}else{
|
||||
message.error(res.errMsg)
|
||||
}
|
||||
}else if(file.status === 'error'){
|
||||
message.error(file.name + 'upload failed')
|
||||
}
|
||||
}
|
||||
|
||||
let addStore = () =>{
|
||||
let data = {
|
||||
type:'edit',
|
||||
storeData:formState.value.storeList,
|
||||
storeNameData:formState.value.storeNameList,
|
||||
}
|
||||
addStoreModalComponent.value.openAddStoreModal(data)
|
||||
}
|
||||
|
||||
let storeChange = (event:any) =>{
|
||||
formState.value.storeList = formState.value.storeList.concat(event)
|
||||
}
|
||||
|
||||
let submitProduct = () =>{
|
||||
formRef.value.validate().then(()=>{
|
||||
submit()
|
||||
})
|
||||
let submit = () =>{
|
||||
let data = {
|
||||
id:productDetail.value.id,
|
||||
pictureName:formState.value.pictureName,
|
||||
pictureUrl:formState.value.pictureUrl,
|
||||
price:formState.value.price,
|
||||
productLabelInfo:getSubmitProductLable(),
|
||||
storeInfoList:getStoreList(),
|
||||
attributeItemInfo:getAttributeValueList(),
|
||||
assortmentList:getAssortmentList(),
|
||||
md5:productDetail.value.md5,
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
submitLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.productEdit, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
submitLoading.value = false
|
||||
message.success('Edit success')
|
||||
emit('refreshList')
|
||||
closeProduct()
|
||||
}
|
||||
}
|
||||
).catch(rv=>{
|
||||
submitLoading.value = false
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let getAttributeValueList = () =>{
|
||||
let data:any = {
|
||||
labelItem:formState.value.productCategory
|
||||
}
|
||||
let attributeTypeList = formState.value.productAttribute.map((v:any)=>{
|
||||
let newData = {
|
||||
id:v.id,
|
||||
labelType:v.labelType,
|
||||
attributeValueList:v.value && v.value.length ? [v.value] : []
|
||||
}
|
||||
return newData
|
||||
})
|
||||
|
||||
data.attributeTypeList = attributeTypeList
|
||||
return data
|
||||
}
|
||||
|
||||
let getStoreList = () =>{
|
||||
let field:any = formState.value.storeNameList
|
||||
let storeInfos = []
|
||||
for(let v of formState.value.storeList){
|
||||
let data:any = {
|
||||
stock:[],
|
||||
storeId:v.storeId,
|
||||
storeName:v.storeName
|
||||
}
|
||||
for(let key in v){
|
||||
if(field.indexOf(key) > -1){
|
||||
let sizeData:any = {
|
||||
num:v[key],
|
||||
size:key
|
||||
}
|
||||
data.stock.push(sizeData)
|
||||
}
|
||||
}
|
||||
storeInfos.push(data)
|
||||
}
|
||||
return storeInfos
|
||||
}
|
||||
|
||||
let getSubmitProductLable = () =>{
|
||||
let labelList:any = props.productLabelList?.filter((v:any)=>formState.value.productLabel.indexOf(v.id) > -1)
|
||||
labelList = labelList.map((v:any) => {
|
||||
return {
|
||||
id:v.id,
|
||||
name:v.name,
|
||||
type:v.type,
|
||||
}
|
||||
})
|
||||
labelList = labelList.length ? labelList :productDetail.value.productLabelInfo
|
||||
return labelList
|
||||
}
|
||||
|
||||
let getAssortmentList = () =>{
|
||||
let assortmentList = formState.value.assortmentList.map((v:any,index:any)=>{
|
||||
let data = v
|
||||
for(let item of data){
|
||||
item.coverSort = index + 1
|
||||
}
|
||||
return data
|
||||
})
|
||||
return assortmentList
|
||||
}
|
||||
|
||||
|
||||
//选择搭配的方法
|
||||
let selectMatchFile = (file:any) =>{
|
||||
let data = {
|
||||
generatePictureUrl:file.pictureUrl,
|
||||
price:file.price,
|
||||
productId:file.productId,
|
||||
}
|
||||
formState.value.assortmentList[addMatchIndex].push(data)
|
||||
}
|
||||
|
||||
//增加搭配
|
||||
let addLook = () =>{
|
||||
let formStateNew:any = productDetail.value
|
||||
let data = [{
|
||||
generatePictureUrl:formStateNew.pictureUrl,
|
||||
price:formStateNew.price,
|
||||
productId:formStateNew.id,
|
||||
}]
|
||||
formState.value.assortmentList.push(data)
|
||||
assortmentShowMore.value = true
|
||||
nextTick(()=>{
|
||||
document.getElementsByClassName('edit_modal')[0].scrollTo(0, document.getElementsByClassName('edit_modal')[0].scrollHeight)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
let assortmentListShowMore = () =>{
|
||||
assortmentShowMore.value = !assortmentShowMore.value
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
uploadUrl.value = getUploadUrl()
|
||||
token.value = getCookie('token') || ''
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
formRef,
|
||||
submitLoading,
|
||||
addStoreModalComponent,
|
||||
uploadUrl,
|
||||
upload,
|
||||
token,
|
||||
fileList,
|
||||
editProductModal,
|
||||
columns,
|
||||
productDetail,
|
||||
productPicSeletcModal,
|
||||
ProductMatchModalComponent,
|
||||
formState,
|
||||
productCategoryList,
|
||||
productAttributeList,
|
||||
rules,
|
||||
getPopupContainer,
|
||||
closeProduct,
|
||||
showModal,
|
||||
openUploadModal,
|
||||
deleteSingleAllMatch,
|
||||
deleteSignleMatchImg,
|
||||
addProductMatch,
|
||||
submitStore,
|
||||
productCategoryChange,
|
||||
fileUploadChange,
|
||||
addStore,
|
||||
storeChange,
|
||||
submitProduct,
|
||||
selectMatchFile,
|
||||
addLook,
|
||||
assortmentShowMore,
|
||||
assortmentListShowMore
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
|
||||
.add_product_modal_component{
|
||||
|
||||
.product_master_diagram{
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
margin: 10px auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.product_master_img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.store_input{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.attribute_block{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding-top: 30px;
|
||||
|
||||
.attribute_block_item{
|
||||
padding: 0 22px;
|
||||
display: inline-block;
|
||||
margin-bottom: 30px;
|
||||
width: 50%;
|
||||
|
||||
.attribute_block_item_content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.attribute_item_title{
|
||||
width: 120px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.attribute_item_select{
|
||||
max-width: 275px;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.null_data_block{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
|
||||
.null_data_img{
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.product_match_block{
|
||||
margin-top: 20px;
|
||||
|
||||
.product_match_item{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 0 16px 26px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.product_match_item_header{
|
||||
height: 83px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
color: #030303;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.product_match_item_content{
|
||||
display: flex;
|
||||
|
||||
.match_item_img_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 120px;
|
||||
height: 90px;
|
||||
margin-right: 20px;
|
||||
position: relative;
|
||||
border: 1px solid #DFDFDF;
|
||||
|
||||
img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.delete_img_block{
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: -10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #000000;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.delete_icon{
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form_edit_table{
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.margin_bottom_10{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.show_more_button{
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<style lang="less">
|
||||
.edit_product_pic{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.ant-upload-list{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.ant-upload-select-picture-card{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
529
src/component/productComponent/productDetailModal.vue
Normal file
@@ -0,0 +1,529 @@
|
||||
<template>
|
||||
<a-modal class="product_detail_modal_component"
|
||||
v-model:visible="productDetailModal"
|
||||
:footer="null"
|
||||
title="商品详情"
|
||||
width="1150px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
>
|
||||
<div class="productDetail_modal">
|
||||
<div class="productDetail_modal_cotent">
|
||||
<div class="productDetail_modal_cotent_center">
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Product image</div>
|
||||
</div>
|
||||
<div class="product_master_diagram">
|
||||
<img class="product_master_img" :src="formState.pictureUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Product label(s)</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="productCategory_block">
|
||||
<div class="produce_label_item" :style="{background:colorMap[label.type]}" v-for="label in productLabelList" :key="label.id">{{label.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Pirce</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="productCategory_block">${{formState.price}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Inventory</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<a-table class="form_width_100" :columns="columns" :data-source="formState.storeList" bordered
|
||||
:pagination="false" :scroll="{y: 400 }">
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Label attributes</div>
|
||||
</div>
|
||||
<div class="detail_item_sec_title">类目</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="productCategory_block">{{formState.productCategory}}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="detail_item_sec_title sec_title_margin">Attribute(s)</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="attribute_block" v-if="formState.productAttribute?.length">
|
||||
<div class="attribute_block_item" v-for="attr in formState.productAttribute" :key="attr.value">
|
||||
<div class="attribute_block_item_content">
|
||||
<div class="attribute_item_title">{{attr.labelType}}</div>
|
||||
<div class="attribute_item_value" :title="attr.attributeValueList.join(',')">{{attr.attributeValueList.join(',')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="null_data_block">
|
||||
<img class="null_data_img" src="@/assets/images/null_img.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Mix and Match(es)</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="product_match_block">
|
||||
<div class="product_match_item" v-for="(match,index) in formState.assortmentList" :key="match.title" v-show="index<5 || assortmentShowMore">
|
||||
<div class="product_match_item_header">
|
||||
<div>Look{{index+1}}</div>
|
||||
</div>
|
||||
<div class="product_match_item_content">
|
||||
<div class="match_item_img_content" v-for="img in match" :key="img">
|
||||
<div class="match_item_img_block">
|
||||
<img :src="img.generatePictureUrl">
|
||||
</div>
|
||||
<div class="product_price">${{img.price}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-button class="primary_button show_more_button" type="primary" @click="assortmentListShowMore()" v-show="formState.assortmentList.length>5">{{assortmentShowMore?'Hide':'See More'}}</a-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="loading_marking" v-show="loading">
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</a-modal>
|
||||
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent, h,reactive,ref,onMounted,toRefs } from 'vue'
|
||||
import { message, Upload } from 'ant-design-vue';
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { Https } from "@/tool/https";
|
||||
export default defineComponent({
|
||||
name:'productDetailModal',
|
||||
setup(){
|
||||
let loading = ref(false)
|
||||
let productDetailModal = ref(false)
|
||||
let productDetail:any = ref([])
|
||||
let formState:any = ref({
|
||||
productPic:'',
|
||||
productLabel:[],
|
||||
inventoryData:[],
|
||||
storeList:[],
|
||||
storeNameList:[],
|
||||
productCategory:'',
|
||||
productAttribute:[],
|
||||
assortmentList:[]
|
||||
});
|
||||
let assortmentShowMore = ref(false)
|
||||
let productCategoryList:any = ref([])
|
||||
let productAttributeList = ref([])
|
||||
let columns = reactive([
|
||||
{ title: 'Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',width:"200px" },
|
||||
{
|
||||
title: 'Inventory',
|
||||
align:'center',
|
||||
ellipsis: true,
|
||||
dataIndex: 'inventory',
|
||||
key: 'inventory',
|
||||
children: [
|
||||
{title: 'XS',dataIndex: 'XS',key: 'XS', align:'center', },
|
||||
{title: 'S',dataIndex: 'S',key: 'S', align:'center',},
|
||||
{title: 'M',dataIndex: 'M',key: 'M', align:'center',},
|
||||
{title: 'L',dataIndex: 'L',key: 'L', align:'center',},
|
||||
{title: 'XL',dataIndex: 'XL',key: 'XL', align:'center',},
|
||||
{title: 'XXL',dataIndex: 'XXL',key: 'XXL', align:'center',},
|
||||
],
|
||||
},
|
||||
])
|
||||
let colorMap = ref({
|
||||
CUSTOM:'#798BD9',
|
||||
NEW_PRODUCT:'#FFC384',
|
||||
SALE:'#79D97C',
|
||||
})
|
||||
|
||||
|
||||
let productLabelList:any = ref([])
|
||||
|
||||
let getProductLabelList = () =>{
|
||||
Https.axiosPost(Https.httpUrls.queryProductLabel).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
productLabelList.value = rv.filter((v:any) => formState.value.productLabel.indexOf(v.id) > -1)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
).catch(res=>{
|
||||
loading.value = false
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
let getProductDetail = (id:any) =>{
|
||||
return new Promise((resolve:any,rj:any)=>{
|
||||
Https.axiosGet(Https.httpUrls.productDetail+'?id='+id).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let store = getStoreInfo(rv.storeInfoList)
|
||||
formState.value = {
|
||||
pictureUrl:rv.pictureUrl,
|
||||
productLabel:rv.productLabelInfo.map((v:any)=>v.id),
|
||||
price:rv.price,
|
||||
productCategory:rv.attributeItemInfo.labelItem,
|
||||
productAttribute:rv.attributeItemInfo.attributeTypeList,
|
||||
storeList:store.storeList,
|
||||
storeNameList:store.storeNameList,
|
||||
assortmentList:rv.assortmentList,
|
||||
}
|
||||
productDetail.value = rv
|
||||
resolve(rv)
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
).catch(res=>{
|
||||
loading.value = false
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
let getStoreInfo = (storeInfo:any) =>{
|
||||
let storeList = []
|
||||
let storeNameList:any = []
|
||||
for(let item of storeInfo){
|
||||
let sizeData:any = {}
|
||||
for(let size of item.stock){
|
||||
sizeData[size.size] = size.num
|
||||
}
|
||||
let data = {
|
||||
storeName:item.storeName,
|
||||
storeId:item.storeId,
|
||||
...sizeData,
|
||||
}
|
||||
storeList.push(data)
|
||||
}
|
||||
storeInfo.forEach((item:any) => {
|
||||
item.stock.forEach((stockItem:any) => {
|
||||
storeNameList.push(stockItem.size);
|
||||
});
|
||||
});
|
||||
|
||||
// 排序并去重
|
||||
storeNameList = storeNameList.sort().filter((value:any, index:any, array:any) => {
|
||||
return array.indexOf(value) === index;
|
||||
});
|
||||
let children:any = []
|
||||
storeNameList.forEach((item:any) => {
|
||||
children.push({
|
||||
title:item,
|
||||
dataIndex:item,
|
||||
key:item,
|
||||
align:'center'
|
||||
})
|
||||
});
|
||||
columns[1].children = children
|
||||
return {storeList,storeNameList}
|
||||
}
|
||||
let openProductDetailModal = (productId:any) =>{
|
||||
formState.value = {
|
||||
productPic:'',
|
||||
productLabel:[],
|
||||
inventoryData:[],
|
||||
storeList:[],
|
||||
storeNameList:[],
|
||||
productCategory:'',
|
||||
productAttribute:[],
|
||||
assortmentList:[]
|
||||
}
|
||||
productDetailModal.value= true
|
||||
loading.value = true
|
||||
assortmentShowMore.value = false
|
||||
getProductDetail(productId).then(rv=>{
|
||||
getProductLabelList()
|
||||
})
|
||||
}
|
||||
|
||||
let assortmentListShowMore = () =>{
|
||||
assortmentShowMore.value = !assortmentShowMore.value
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
loading,
|
||||
productDetailModal,
|
||||
columns,
|
||||
formState,
|
||||
assortmentShowMore,
|
||||
productCategoryList,
|
||||
productAttributeList,
|
||||
colorMap,
|
||||
productLabelList,
|
||||
openProductDetailModal,
|
||||
assortmentListShowMore
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.product_detail_modal_component{
|
||||
.productDetail_modal{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.productDetail_modal_cotent{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
|
||||
.productDetail_modal_cotent_center{
|
||||
width: 1000px;
|
||||
padding: 20px 0 30px;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.detail_item_block{
|
||||
margin-bottom: 15px;
|
||||
|
||||
.detail_icon{
|
||||
color: #80B8F8;
|
||||
font-size: 24px;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.detail_item_title_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-family: Adobe Heiti Std;
|
||||
font-weight: normal;
|
||||
color: #030303;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.detail_item_content{
|
||||
padding-left: 10px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.produce_label_item{
|
||||
padding: 0 11px;
|
||||
margin: 9px 27px 9px 0;
|
||||
height: 27px;
|
||||
line-height: 27px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.productCategory_block{
|
||||
width: 100%;
|
||||
line-height: 46px;
|
||||
padding-left: 20px;
|
||||
font-size: 18px;
|
||||
color: #030303;
|
||||
height: 46px;
|
||||
border: 1px solid #DFDFDF;
|
||||
}
|
||||
|
||||
.detail_item_sec_title{
|
||||
padding-left: 38px;
|
||||
font-size: 16px;
|
||||
font-family: Adobe Heiti Std;
|
||||
font-weight: normal;
|
||||
color: #030303;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sec_title_margin{
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.attribute_block{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding-top: 30px;
|
||||
|
||||
.attribute_block_item{
|
||||
padding: 0 22px;
|
||||
display: inline-block;
|
||||
margin-bottom: 30px;
|
||||
width: 50%;
|
||||
|
||||
.attribute_block_item_content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.attribute_item_title{
|
||||
width: 120px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.attribute_item_value{
|
||||
width: 200px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border: 1px solid #DFDFDF;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
padding-left: 18px;
|
||||
color: #030303;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.null_data_block{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
|
||||
.null_data_img{
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.product_match_block{
|
||||
|
||||
.product_match_item{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 0 16px 26px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.product_match_item_header{
|
||||
height: 83px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
color: #030303;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.product_match_item_content{
|
||||
display: flex;
|
||||
|
||||
.match_item_img_content{
|
||||
margin-right: 20px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.match_item_img_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 160px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
border: 1px solid #DFDFDF;
|
||||
cursor: pointer;
|
||||
|
||||
img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
|
||||
&:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.product_price{
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
color: #030303;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.show_more_button{
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.form_width_100{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.product_master_diagram{
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
margin: 30px auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.product_master_img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading_marking{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
341
src/component/productComponent/productMatchModal.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<a-modal class="add_match_modal_component"
|
||||
v-model:visible="productMatchModal"
|
||||
:footer="null"
|
||||
title="商品搭配选择"
|
||||
width="1150px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
>
|
||||
<div class="upload_modal_body">
|
||||
<div class="filter_content">
|
||||
<div class="filter_content_block">
|
||||
<filterComponent class="filter_component_style" :title="'Product Type'" :titleStyle="titleStyle" :slotStyle="slotStyle"><a-select v-model:value="labelItem" size="large" style="width:200px" :options="productCategoryList" placeholder="Please select" allowClear @change="productTypeChange"></a-select></filterComponent>
|
||||
<div class="filter_component_style item_subtype_content">
|
||||
<div class="item_subtype_button" @click="changeItemFilter">
|
||||
<span>Item Subtype</span>
|
||||
<span :class="['icon','iconfont','item_subtype_button_icon','icon-xialajiantouxiao', showItemFilter?'turn_round_icon':'']"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="showItemFilter">
|
||||
<filterComponent v-for="item in productAttributeList" :key="item.title" class="filter_component_style" :title="item.labelType" :titleStyle="titleStyle" :slotStyle="slotStyle">
|
||||
<a-select v-model:value="item.value" size="large" style="width:200px" :options="item.attributeValueList" placeholder="Please select" allowClear></a-select>
|
||||
</filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter_content_button_list">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Search</a-button>
|
||||
<a-button class="default_button" size="large">Reset</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal_body_content">
|
||||
<div class="upload_img_body scroll_style" v-show="imageList.length">
|
||||
<div class="upload_file_item" v-for="(file) in imageList" :key="file.id">
|
||||
<div :class="['upload_file_item_content']" @click="selectFile(file)">
|
||||
<!-- <img v-lazy="img.url" class="upload_img"> -->
|
||||
<img :src="file.pictureUrl" class="upload_img">
|
||||
</div>
|
||||
<div class="product_price">${{file.price}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload_img_body null_img_block" v-show="!imageList.length">
|
||||
<img src="@/assets/images/null_img.png">
|
||||
</div>
|
||||
<div class="img_mark_loading" v-show="tableLoading">
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
|
||||
<a-pagination class="pagination_content" show-quick-jumper v-model:current="currentPage" :page-size-options="pageSizeOptions" show-size-changer :page-size="pageSize" :total="total" @change="pageChange" @showSizeChange="onShowSizeChange"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent, h,reactive,ref,onMounted,toRefs } from 'vue'
|
||||
import { message, Upload } from 'ant-design-vue';
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { Https } from "@/tool/https";
|
||||
export default defineComponent({
|
||||
name:'ProductMatchModal',
|
||||
components:{filterComponent},
|
||||
props:{
|
||||
productCategoryList:{
|
||||
type:Array,
|
||||
}
|
||||
},
|
||||
setup(props,{emit}){
|
||||
let productMatchModal = ref(false)
|
||||
let filter = reactive({
|
||||
labelItem:[],
|
||||
labelTypeMap:{},
|
||||
})
|
||||
let tableLoading = ref(false)
|
||||
let imageList:any = ref([])
|
||||
let currentPage = ref(1)
|
||||
let total = ref(0)
|
||||
let pageSize = ref(10)
|
||||
let pageSizeOptions = ref<string[]>(['10', '20', '30', '40', '50']);
|
||||
let titleStyle = ref({textAlign:'left',minWidth:'120px',textOverflow:'ellipsis',width:'120px',overflow:'hidden'})
|
||||
let slotStyle = ref({width:'auto'})
|
||||
let showItemFilter = ref(false)
|
||||
let subTypeList:any = ref([])
|
||||
let productAttributeList:any = ref([])
|
||||
let assortmentIdList:any = []
|
||||
|
||||
|
||||
let openMatchModal = (idList:any) =>{
|
||||
productMatchModal.value = true
|
||||
productAttributeList.value = []
|
||||
filter.labelItem = []
|
||||
filter.labelTypeMap = {}
|
||||
currentPage.value = 1
|
||||
imageList.value = []
|
||||
assortmentIdList = idList
|
||||
getProductAssortemlist()
|
||||
}
|
||||
|
||||
let pageChange = (e:any) =>{
|
||||
currentPage.value = e
|
||||
getProductAssortemlist()
|
||||
}
|
||||
|
||||
let onShowSizeChange = (current: number, size: number) =>{
|
||||
pageSize.value = size
|
||||
getProductAssortemlist()
|
||||
}
|
||||
|
||||
let productTypeChange = (value:any,selectedOptions:any) => {
|
||||
let attributeList = selectedOptions ? selectedOptions.attributeTypeList :[]
|
||||
productAttributeList.value = JSON.parse(JSON.stringify(attributeList))
|
||||
}
|
||||
|
||||
|
||||
let changeItemFilter = () =>{
|
||||
showItemFilter.value = !showItemFilter.value
|
||||
}
|
||||
|
||||
let selectFile = (file:any) =>{
|
||||
emit('selectMatchFile',file)
|
||||
productMatchModal.value = false
|
||||
}
|
||||
|
||||
let getLabelTypeMap = () =>{
|
||||
let labelTypeMap:any = {}
|
||||
for(let item of productAttributeList.value){
|
||||
if(item.value && item.value.length){
|
||||
labelTypeMap[item.labelType] = [item.value]
|
||||
}
|
||||
}
|
||||
return labelTypeMap
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getProductAssortemlist()
|
||||
}
|
||||
|
||||
let getProductAssortemlist = () =>{
|
||||
let data = {
|
||||
assortmentIdList:assortmentIdList,
|
||||
labelItem:filter.labelItem && filter.labelItem.length ? filter.labelItem : '',
|
||||
labelTypeMap:getLabelTypeMap(),
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.queryProductAssortmentPage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
imageList.value = rv.content
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
productMatchModal,
|
||||
tableLoading,
|
||||
imageList,
|
||||
currentPage,
|
||||
total,
|
||||
pageSize,
|
||||
pageSizeOptions,
|
||||
titleStyle,
|
||||
slotStyle,
|
||||
showItemFilter,
|
||||
subTypeList,
|
||||
productAttributeList,
|
||||
openMatchModal,
|
||||
pageChange,
|
||||
onShowSizeChange,
|
||||
productTypeChange,
|
||||
changeItemFilter,
|
||||
selectFile,
|
||||
searchList,
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.add_match_modal_component{
|
||||
|
||||
|
||||
.upload_modal_body{
|
||||
|
||||
.filter_content{
|
||||
|
||||
.filter_content_block{
|
||||
|
||||
.filter_component_style:nth-child(3n){
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.item_subtype_content{
|
||||
width: 335px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
|
||||
.item_subtype_button{
|
||||
width: 160px;
|
||||
height: 46px;
|
||||
border: 1px solid #030303;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #030303;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
|
||||
.item_subtype_button_icon{
|
||||
margin-left: 15px;
|
||||
color: #030303;
|
||||
font-size: 16px;
|
||||
}
|
||||
.turn_round_icon{
|
||||
-moz-transform:rotate(180deg);
|
||||
-webkit-transform:rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
animation-direction: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.filter_content_button_list{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.modal_body_content{
|
||||
margin-top: 40px;
|
||||
padding: 25px;
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
height: 550px;
|
||||
position: relative;
|
||||
|
||||
.upload_img_body{
|
||||
height: calc(100% - 30px);
|
||||
overflow-y: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.null_img_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.img_mark_loading{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(0,0,0,0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.upload_file_item{
|
||||
margin: 0 50px 30px 0;
|
||||
display: inline-block;
|
||||
width: 165px;
|
||||
height: 165px;
|
||||
border: 1px solid #F5F5F5;
|
||||
vertical-align: top;
|
||||
background: rgba(0,0,0,0.2);
|
||||
|
||||
&:nth-child(5n){
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.upload_file_item_content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&.select_file_block{
|
||||
border:solid 1px #000;
|
||||
}
|
||||
|
||||
.upload_img{
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.product_price{
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
color: #030303;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.pagination_content{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
318
src/component/productComponent/productPicUpload.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<a-modal class="add_product_modal_component"
|
||||
v-model:visible="addProductPicModal"
|
||||
:footer="null"
|
||||
title="商品图片上传"
|
||||
width="1150px"
|
||||
:maskClosable="false"
|
||||
:destroyOnClose="true"
|
||||
:centered="true"
|
||||
@cancel="closeModal"
|
||||
>
|
||||
<div class="upload_modal_body">
|
||||
<div class="upload_img_body scroll_style" >
|
||||
<div class="upload_item">
|
||||
|
||||
<div class="upload_file_item" v-for="(file, index) in fileList" :key="file">
|
||||
<div class="upload_file_item_content" v-show="file?.status === 'uploading'">
|
||||
<a-spin :indicator="indicator" tip="Uploading..."/>
|
||||
</div>
|
||||
<div class="upload_file_item_content" v-show="file?.status === 'done'">
|
||||
<img v-lazy="file?.pictureUrl" class="upload_img">
|
||||
<div class="delete_file_block" @click="deleteFile(index)">
|
||||
<span class="icon iconfont icon-shanchu"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload_file_item upload_component" v-show="fileList.length < 100">
|
||||
<a-upload
|
||||
:action="uploadUrl + '/api/product/uploadFileSingle'"
|
||||
class="upload_component_button"
|
||||
list-type="picture-card"
|
||||
:data="{
|
||||
...upload
|
||||
}"
|
||||
:headers="{Authorization:token}"
|
||||
v-model:file-list="fileList"
|
||||
:before-upload="beforeUpload"
|
||||
multiple
|
||||
:maxCount="100"
|
||||
accept=".jpg,.png,.jpeg,.bmp"
|
||||
@change="(file)=>fileUploadChange(file)"
|
||||
>
|
||||
<div class="upload_tip_block" v-show="fileList.length < 100">
|
||||
<span class="icon iconfont add_pic_button_icon icon-tianjiatupian_huaban"></span>
|
||||
</div>
|
||||
</a-upload>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload_max_tip">
|
||||
<WarningFilled class="icon-zhuyi"/>
|
||||
<span>Maximum 100 images can be uploaded, Maximum 2M per image</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="primary_button" type="primary" size="large" @click="closeModal()">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
import { defineComponent, h,reactive,ref,onMounted} from 'vue'
|
||||
import { LoadingOutlined,WarningFilled } from '@ant-design/icons-vue';
|
||||
import {getCookie} from '@/tool/cookie'
|
||||
import {getUploadUrl} from '@/tool/util'
|
||||
import { message, Upload } from 'ant-design-vue';
|
||||
export default defineComponent({
|
||||
name:'ProductPicUpload',
|
||||
components:{WarningFilled},
|
||||
emits: ['uploadPicChange'],
|
||||
setup(props,{ emit }){
|
||||
let addProductPicModal = ref(false)
|
||||
let fileList:any = ref([])
|
||||
let templateModal:any = ref(false)
|
||||
let templateFileList:any = ref([])
|
||||
let upload:any = reactive({
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
})
|
||||
let beforeUploadIndex:any = 0
|
||||
let successFileList:any = ref([])
|
||||
let token:any = ref('')
|
||||
let indicator :any = h(LoadingOutlined, {
|
||||
style: {
|
||||
fontSize: '24px',
|
||||
},
|
||||
spin: true,
|
||||
})
|
||||
let uploadUrl:any = ref('')
|
||||
|
||||
let fileUploadChange = (data:any) =>{
|
||||
let file = data.file
|
||||
if(file.status === 'done'){
|
||||
let res = file.response
|
||||
if(res.data){
|
||||
file.pictureUrl = res.data.pictureUrl
|
||||
file.md5 = res.data.md5
|
||||
file.resData = res.data
|
||||
successFileList.value = fileList.value.filter((v:any)=> v.status === 'done')
|
||||
emit('uploadPicChange', {fileList:successFileList.value})
|
||||
}else{
|
||||
let index = -1
|
||||
fileList.value.forEach((ele:any,index1:any) => {
|
||||
if(file.uid === ele.uid){
|
||||
index = index1
|
||||
}
|
||||
});
|
||||
if(index > -1){
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
message.error(file.name + ' ' + res.errMsg)
|
||||
}
|
||||
|
||||
}else if(file.status === 'error'){
|
||||
let index = -1
|
||||
fileList.value.forEach((ele:any,index1:any) => {
|
||||
if(file.uid === ele.uid){
|
||||
index = index1
|
||||
}
|
||||
});
|
||||
if(index > -1){
|
||||
fileList.value.splice(index, 1)
|
||||
}
|
||||
message.error(file.name + 'upload failed')
|
||||
}
|
||||
}
|
||||
|
||||
let beforeUpload = (file:any,fileList:any) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/bmp';
|
||||
beforeUploadIndex = beforeUploadIndex +1
|
||||
console.log(file, 66)
|
||||
if(fileList.length > 10 && beforeUploadIndex == fileList.length){
|
||||
message.error('You can only upload 10 pictures at a time!');
|
||||
}
|
||||
if (!isJpgOrPng) {
|
||||
message.error('You can only upload Image file!');
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 5;
|
||||
if (!isLt2M) {
|
||||
message.error('Image must smaller than 5MB!');
|
||||
}
|
||||
if(beforeUploadIndex == fileList.length) {
|
||||
beforeUploadIndex = 0
|
||||
}
|
||||
return (isJpgOrPng && isLt2M && fileList.length <= 10) || Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
let deleteFile = (index:any) =>{
|
||||
fileList.value.splice(index,1)
|
||||
successFileList.value = fileList.value
|
||||
emit('uploadPicChange', {fileList:successFileList.value})
|
||||
}
|
||||
|
||||
let openPictureModal = () =>{
|
||||
addProductPicModal.value = true
|
||||
}
|
||||
|
||||
let closeModal = () =>{
|
||||
addProductPicModal.value = false
|
||||
}
|
||||
|
||||
let clearPicData = () =>{
|
||||
fileList.value = []
|
||||
successFileList.value =[]
|
||||
}
|
||||
|
||||
|
||||
onMounted(()=>{
|
||||
token.value = getCookie('token') || '',
|
||||
uploadUrl.value = getUploadUrl()
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
addProductPicModal,
|
||||
fileList,
|
||||
templateModal,
|
||||
templateFileList,
|
||||
upload,
|
||||
successFileList,
|
||||
indicator,
|
||||
token,
|
||||
uploadUrl,
|
||||
fileUploadChange,
|
||||
beforeUpload,
|
||||
deleteFile,
|
||||
openPictureModal,
|
||||
closeModal,
|
||||
clearPicData
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.add_product_modal_component{
|
||||
|
||||
|
||||
.upload_modal_body{
|
||||
height: 600px;
|
||||
|
||||
.upload_img_body{
|
||||
height: calc(100% - 30px);
|
||||
overflow-y: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.upload_file_item{
|
||||
margin: 0 17px 17px 0;
|
||||
display: inline-block;
|
||||
width: 165px;
|
||||
height: 165px;
|
||||
border: 1px solid #F5F5F5;
|
||||
vertical-align: top;
|
||||
|
||||
&.upload_component{
|
||||
background: #FFFFFF;
|
||||
|
||||
.upload_tip_block{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.add_pic_button_icon{
|
||||
font-size: 44px;
|
||||
color: #C6CBD3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.upload_file_item_content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
&:hover .delete_file_block{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.upload_img{
|
||||
display: block;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.delete_file_block{
|
||||
display: none;
|
||||
width: 36px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
background: #000000;
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.upload_max_tip{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #030303;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.icon-zhuyi{
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal_button_list{
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<style lang="less">
|
||||
.upload_component_button{
|
||||
width: 165px;
|
||||
height: 165px;
|
||||
|
||||
.ant-upload-list{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.ant-upload-select-picture-card{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
18
src/main.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import './assets/iconfont/iconfont.css'
|
||||
import './assets/iconfont2/iconfont.css'
|
||||
import './assets/iconfont/iconfont.js'
|
||||
import 'ant-design-vue/dist/antd.css';
|
||||
import Antd from 'ant-design-vue';
|
||||
import './assets/style/style.less'
|
||||
import VueLazyload from "vue-lazyload";
|
||||
|
||||
let loadingParam = {
|
||||
loading: require('./assets/images/loading.gif'),
|
||||
attempt: 1
|
||||
}
|
||||
|
||||
createApp(App).use(store).use(router).use(Antd).use(VueLazyload,loadingParam).mount('#app')
|
||||
134
src/router/index.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const _import = (path : string) => defineAsyncComponent(() => import(`../views/${path}.vue`));
|
||||
const _import_custom = (path : string) => defineAsyncComponent(() => import(`../views/${path}`));
|
||||
import {getCookie} from '@/tool/cookie'
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: "/",
|
||||
// redirect重定向
|
||||
redirect: "/login"
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: _import('LoginPage')
|
||||
},
|
||||
{
|
||||
path: '/noPermission',
|
||||
name: 'noPermission',
|
||||
component: _import('NoPermissionPage')
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: _import('HomeView'),
|
||||
children:[
|
||||
{
|
||||
path:'worktable',
|
||||
name:'worktable',
|
||||
component: _import_custom('childView/worktable.vue'),
|
||||
},
|
||||
{
|
||||
path:'storemanage',
|
||||
name:'storeManage',
|
||||
component: _import_custom('childView/storeManage.vue'),
|
||||
},
|
||||
{
|
||||
path:'labelmanage',
|
||||
name:'labelManage',
|
||||
component: _import_custom('childView/labelManage.vue'),
|
||||
},
|
||||
{
|
||||
path:'productmanage',
|
||||
name:'productManage',
|
||||
component: _import_custom('childView/productManage.vue'),
|
||||
},
|
||||
{
|
||||
path:'productDetail',
|
||||
name:'productDetail',
|
||||
component: _import_custom('childView/productDetail.vue'),
|
||||
},
|
||||
{
|
||||
path:'usermanage',
|
||||
name:'userManage',
|
||||
component: _import_custom('childView/systemSetting/userManage.vue'),
|
||||
},
|
||||
{
|
||||
path:'rolemanage',
|
||||
name:'roleManage',
|
||||
component: _import_custom('childView/systemSetting/roleManage.vue'),
|
||||
},
|
||||
{
|
||||
path:'exportExcil',
|
||||
name:'exportExcil',
|
||||
component: _import_custom('childView/exportExcil.vue'),
|
||||
},
|
||||
// {
|
||||
// path:'excil1',
|
||||
// name:'excil1',
|
||||
// component: _import_custom('childView/exportExcil/userManage.vue'),
|
||||
// },
|
||||
// {
|
||||
// path:'excil2',
|
||||
// name:'excil2',
|
||||
// component: _import_custom('childView/exportExcil/roleManage.vue'),
|
||||
// },
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) =>{
|
||||
let routeRoleMap:any = sessionStorage.getItem('menuList')
|
||||
routeRoleMap = JSON.parse(routeRoleMap) || []
|
||||
let routeList = ['/login','/home/productDetail','/noPermission']
|
||||
if(!getCookie('token')){
|
||||
next()
|
||||
}
|
||||
|
||||
if(routeList.indexOf(to.path) > -1){
|
||||
next()
|
||||
}else{
|
||||
let status = false
|
||||
let allStatus = false
|
||||
for(let item of routeRoleMap){
|
||||
allStatus = item.isShow
|
||||
if(to.path === item.route && item.isShow){
|
||||
status = true
|
||||
break
|
||||
}
|
||||
if(item.children && item.children.length){
|
||||
for(let child of item.children){
|
||||
allStatus = item.isShow
|
||||
if(to.path === child.route && child.isShow){
|
||||
status = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if(status) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if(!allStatus){
|
||||
next('/login')
|
||||
}
|
||||
if(status){
|
||||
next()
|
||||
}else{
|
||||
next('/noPermission')
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
export default router
|
||||
29
src/store/homeMenu/homeMenu.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {Module} from 'vuex'
|
||||
import {RootState} from '../index'
|
||||
|
||||
interface menuData{
|
||||
menuList:any
|
||||
|
||||
}
|
||||
|
||||
const homeMenu : Module<menuData,RootState> = {
|
||||
state:{
|
||||
menuList:[],
|
||||
|
||||
},
|
||||
mutations:{
|
||||
setMenuList(state,data){
|
||||
state.menuList = data
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
actions:{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default homeMenu
|
||||
19
src/store/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createStore } from 'vuex'
|
||||
import homeMenuModule from './homeMenu/homeMenu'
|
||||
export interface RootState{
|
||||
|
||||
}
|
||||
|
||||
export default createStore<RootState>({
|
||||
state: {
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
actions: {
|
||||
},
|
||||
modules: {
|
||||
homeMenuModule,
|
||||
}
|
||||
})
|
||||
30
src/tool/cookie.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const setCookie = (name,value) => {
|
||||
var Days = 30;
|
||||
var exp = new Date();
|
||||
exp.setTime(exp.getTime() + Days*24*60*60*30);
|
||||
document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
|
||||
}
|
||||
|
||||
const getCookie = (name) => {
|
||||
var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
|
||||
if(arr=document.cookie.match(reg))
|
||||
return unescape(arr[2]);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
function WriteCookie(name) {
|
||||
var now = new Date();
|
||||
now.setMonth( now.getMonth() - 1 );
|
||||
cookievalue = escape(document.myform.customer.value) + ";"
|
||||
|
||||
document.cookie = name + '=' + cookievalue;
|
||||
document.cookie = "expires=" + now.toUTCString() + ";"
|
||||
document.write("Setting Cookies : " + "name=" + cookievalue );
|
||||
}
|
||||
|
||||
export {
|
||||
setCookie,
|
||||
getCookie,
|
||||
WriteCookie
|
||||
}
|
||||
146
src/tool/https.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import axios from 'axios'
|
||||
// import qs from 'qs'
|
||||
// import message from '@/components/public/message/src'
|
||||
|
||||
import router from '@/router/index'
|
||||
import {getCookie} from '@/tool/cookie'
|
||||
// import cookie from '@/tools/cookie.js'
|
||||
|
||||
axios.defaults.timeout = 60000 * 60; //响应时间
|
||||
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; //配置请求头
|
||||
axios.defaults.withCredentials = true; //跨域携带cookie
|
||||
import { message } from 'ant-design-vue';
|
||||
axios.defaults.baseURL = process.env.VUE_APP_BASE_URL; //配置接口地址
|
||||
|
||||
//POST传参序列化(添加请求拦截器)
|
||||
axios.interceptors.request.use((config) => {
|
||||
//在发送请求之前做某件事
|
||||
if(config.method === 'post' || config.method === 'put' || config.method === 'delete'){
|
||||
// config.data = qs.stringify(config.data);
|
||||
// config.data = JSON.stringify(config.data);
|
||||
}
|
||||
|
||||
config.headers.Authorization = getCookie('token');
|
||||
return config;
|
||||
},(error) =>{
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
//返回状态判断(添加响应拦截器)
|
||||
axios.interceptors.response.use((res) =>{
|
||||
if (res.data) {
|
||||
if (res.data.errCode === 0) {
|
||||
return Promise.resolve(res.data.data);
|
||||
|
||||
} else if(!res.data.errMsg){
|
||||
let obj = {
|
||||
data:res.data,
|
||||
type:res.headers['content-type'],
|
||||
name:res.headers['content-disposition'],
|
||||
}
|
||||
return Promise.resolve(obj);
|
||||
}else{
|
||||
message.error(res.data.errMsg)
|
||||
return Promise.reject(res.data);
|
||||
}
|
||||
} else{
|
||||
message.error(res.data.errMsg)
|
||||
return Promise.reject(res.data);
|
||||
}
|
||||
|
||||
}, function(error) {
|
||||
if(error?.response?.status === 401){
|
||||
router.replace('/login')
|
||||
return Promise.reject()
|
||||
}
|
||||
let data_new = error?.response?.data
|
||||
message.error(data_new?.errMsg || 'Error: server exception')
|
||||
return Promise.reject(data_new);
|
||||
|
||||
});
|
||||
|
||||
export const Https = {
|
||||
httpUrls: {
|
||||
interfaceUrl: '',
|
||||
accountLogin:'/api/account/login', //登入
|
||||
accountLogout:'/api/account/logout',//登出
|
||||
queryStorePage:'/api/store/queryStorePage',//门店分页列表
|
||||
sotreSaveOrEdit:'/api/store/saveOrEdit',//新增或编辑门店
|
||||
storeDelete:'/api/store/delete',//删除门店
|
||||
labelQueryStorePage:'/api/label/queryStorePage',//标签分页列表
|
||||
labelSaveOrEdit:'/api/label/saveOrEdit', //新增或编辑标签
|
||||
labelDelete:'/api/label/delete',//删除标签
|
||||
accountQueryUserPage:'/api/account/queryUserPage',//用户分页列表
|
||||
storeQueryAll:'/api/store/queryAll',//下拉-查询所有店铺
|
||||
roleQueryRolePage:'/api/role/queryRolePage', //角色分页列表
|
||||
roleQueryAll:'/api/role/queryAll', // 下拉-查询所有角色
|
||||
accountSaveOrEdit:'/api/account/saveOrEdit',//添加或编辑账号
|
||||
accountEnable:'/api/account/enable', // 禁用-停用账户
|
||||
accountDelete:'/api/account/delete',//删除账户
|
||||
roleQueryPermissionList:'/api/role/queryPermissionList',//查询权限列表
|
||||
roleSaveOrEdit:'/api/role/saveOrEdit', //新增或编辑角色
|
||||
roleDelete:'/api/role/delete', //删除角色
|
||||
queryProductPage:'/api/product/queryProductPage',//商品分页列表
|
||||
queryProductLabel:'/api/label/queryProductLabel',//下拉-查询所有商品标签
|
||||
queryProductAssortmentPage:'/api/product/queryProductAssortmentPage',//商品搭配分页列表
|
||||
attributeQueryAll:'/api/attribute/queryAll',//下拉-查询所有属性值
|
||||
productConfirmUpload:'/api/product/confirmUpload',
|
||||
countWorkBench:'/api/product/countWorkBench',//首页工作台统计
|
||||
batchUploadProductRelation:'/api/product/batchUploadProductRelation',//批量上传商品后传对应的关联信息,新增商品
|
||||
productDelete:'/api/product/delete', //删除商品
|
||||
countProductUpdateProcess:'/api/product/countProductUpdateProcess', //统计商品批量上传进度
|
||||
productEdit:'/api/product/edit',//编辑商品
|
||||
productDetail:'/api/product/detail',//商品详情
|
||||
doOnSale:'/api/product/doOnSale', //上架-下架商品
|
||||
queryUsrPermission:'/api/role/queryUsrPermission',//菜单权限
|
||||
queryProductStore:'/api/store/queryProductStore', //下拉-查询所有商品店铺
|
||||
exportProduct:'/api/product/exportProduct', //商品导出
|
||||
miTuExportPage:'/api/miTuExport/miTuExportPage', //获取导出报表列表
|
||||
miTuExportExport:'/api/miTuExport/export', //商品导出
|
||||
},
|
||||
|
||||
axiosGet(url,config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.get(url,config).then(response => {
|
||||
resolve(response)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
axiosPut(url, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.put(url, data).then(response => {
|
||||
resolve(response)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
axiosPost(url, data,config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(url, data,config).then(response => {
|
||||
resolve(response)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
axiosDelete(url, newData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(url,{data:newData}).then(response => {
|
||||
resolve(response)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
197
src/tool/util.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import * as XLSX from 'xlsx';
|
||||
const isEmail = (email)=>{
|
||||
let reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/
|
||||
let result = reg.test(email)
|
||||
return result
|
||||
}
|
||||
|
||||
const getUploadUrl = () =>{
|
||||
let url = process.env.VUE_APP_BASE_URL || ''
|
||||
return url
|
||||
}
|
||||
|
||||
function dataURLtoBlob(dataurl) {
|
||||
var arr = dataurl.split(',');
|
||||
var mime = arr[0].match(/:(.*?);/)[1];
|
||||
var bstr =atob(arr[1]);
|
||||
var n = bstr.length;
|
||||
var u8arr =new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
return new Blob([u8arr], {type: mime });
|
||||
}
|
||||
|
||||
function blobToFile(blob, fileName){
|
||||
blob.lastModifiedDate =new Date();
|
||||
blob.name = fileName;
|
||||
return blob;
|
||||
}
|
||||
|
||||
function dataURLtoFile(dataurl, filename){
|
||||
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
|
||||
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
|
||||
while(n--){
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
var blob = dataURLtoBlob(dataurl);
|
||||
return blobToFile(blob, filename);
|
||||
}
|
||||
|
||||
const base64toFile = (dataurl, filename = 'file') => {
|
||||
let arr = dataurl.split(',')
|
||||
let mime = arr[0].match(/:(.*?);/)[1]
|
||||
let suffix = mime.split('/')[1]
|
||||
let bstr = atob(arr[1])
|
||||
let n = bstr.length
|
||||
let u8arr = new Uint8Array(n)
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n)
|
||||
}
|
||||
return new File([u8arr], `${filename}.${suffix}`, {
|
||||
type: mime
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function rgbToHsv([R, G, B]) {
|
||||
R /= 255
|
||||
G /= 255
|
||||
B /= 255
|
||||
const max = Math.max(R, G, B)
|
||||
const min = Math.min(R, G, B)
|
||||
const range = max - min
|
||||
let V = max
|
||||
let S = V === 0 ? 0 : range / V
|
||||
let H = 0
|
||||
if (R === V) H = (60 * (G - B)) / range
|
||||
if (G === V) H = 120 + (60 * (B - R)) / range
|
||||
if (B === V) H = 240 + (60 * (R - G)) / range
|
||||
|
||||
if (range === 0) H = 0
|
||||
if (H < 0) H += 360
|
||||
H = (H / 2) * (256 / 180)
|
||||
S *= 255
|
||||
V *= 255
|
||||
|
||||
H = parseInt(H)
|
||||
S = parseInt(S)
|
||||
V = parseInt(V)
|
||||
return [H, S, V]
|
||||
}
|
||||
|
||||
const formatTime = (timestamp, fmt) =>{
|
||||
// date = new Date(), fmt = 'MM/dd/yyyy';
|
||||
let date = new Date();
|
||||
date.setTime(timestamp * 1000);
|
||||
|
||||
if (!fmt) {
|
||||
formatRule ? (fmt = formatRule) : (fmt = "YYYY-MM-DD hh:mm:ss");
|
||||
}
|
||||
// console.log(formatRule)
|
||||
let o = {
|
||||
'M+': date.getMonth() + 1, // 月份
|
||||
'D+': date.getDate(), // 日
|
||||
'h+': date.getHours(), // 小时
|
||||
'm+': date.getMinutes(), // 分
|
||||
's+': date.getSeconds(), // 秒
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
'S+': date.getMilliseconds(), // 毫秒
|
||||
'a': date.getHours() > 12
|
||||
? 'PM'
|
||||
: 'AM' // 上午还是下午
|
||||
};
|
||||
if (/(Y+)/.test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
|
||||
}
|
||||
if(/(a)/.test(fmt)&&o['h+']>12){
|
||||
o['h+'] = o['h+'] - 12
|
||||
}
|
||||
for (let k in o) {
|
||||
if (new RegExp('(' + k + ')').test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1)
|
||||
? (o[k])
|
||||
: (('00' + o[k]).substr(('' + o[k]).length)));
|
||||
}
|
||||
}
|
||||
|
||||
return fmt;
|
||||
}
|
||||
|
||||
const startTime = (time) => {
|
||||
const nowTimeDate = new Date(time)
|
||||
return nowTimeDate.setHours(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
const endTime = (time)=> {
|
||||
const nowTimeDate = new Date(time)
|
||||
return nowTimeDate.setHours(23, 59, 59, 999)
|
||||
}
|
||||
|
||||
const isMoible = () => {
|
||||
let is_mobile = navigator.userAgent.toLowerCase().match(/(ipad|ipod|iphone|android|coolpad|mmp|smartphone|midp|wap|xoom|symbian|j2me|blackberry|wince)/i) != null;
|
||||
if(is_mobile){
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//根据某个字段去重
|
||||
const unique = (arr, val) => {
|
||||
const res = new Map()
|
||||
return arr.filter((item) => !res.has(item[val]) && res.set(item[val], 1))
|
||||
}
|
||||
|
||||
const exportExcil=(data,sheetNames,fileName,configuration)=>{
|
||||
|
||||
const workbook = XLSX.utils.book_new();
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let worksheet = XLSX.utils.aoa_to_sheet(data[i]);
|
||||
// let worksheet = XLSX.utils.json_to_sheet(data[i]);
|
||||
const colWidths = [];
|
||||
for (let y = 0; y < data[i].length; y++) {
|
||||
colWidths.push({ wch: configuration.width });
|
||||
}
|
||||
worksheet['!cols'] = colWidths;
|
||||
const merges = [
|
||||
{ s: { r: 0, c: 0 }, e: { r: 0, c: configuration.titleCell } },
|
||||
]
|
||||
worksheet['!merges'] = merges;
|
||||
const range = XLSX.utils.decode_range(worksheet['!ref']);
|
||||
for (let R = range.s.r; R <= range.e.r; ++R) {
|
||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||
const cellAddress = { r: R, c: C };
|
||||
const cellRef = XLSX.utils.encode_cell(cellAddress);
|
||||
if (!worksheet[cellRef]) continue;
|
||||
console.log(worksheet[cellRef]);
|
||||
worksheet[cellRef].s = { alignment: { horizontal: 'center', vertical: 'center',wrapText: true, } }; // 设置文本水平和垂直居中
|
||||
}
|
||||
}
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, sheetNames[i]);
|
||||
|
||||
}
|
||||
// XLSX.writeFile(workbook, fileName);
|
||||
XLSX_STYLE.write(workbook, {
|
||||
bookType: 'xlsx',
|
||||
bookSST: true,
|
||||
type: 'binary',
|
||||
cellStyles: true,
|
||||
})
|
||||
}
|
||||
|
||||
export{
|
||||
isEmail,
|
||||
getUploadUrl,
|
||||
dataURLtoFile,
|
||||
blobToFile,
|
||||
base64toFile,
|
||||
rgbToHsv,
|
||||
formatTime,
|
||||
dataURLtoBlob,
|
||||
startTime,
|
||||
endTime,
|
||||
isMoible,
|
||||
unique,
|
||||
exportExcil
|
||||
}
|
||||
374
src/views/HomeView.vue
Normal file
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<div class="home_page">
|
||||
<div class="home_menu_content">
|
||||
<img class="home_menu_log" src="@/assets/images/home_logo.png">
|
||||
<a-menu
|
||||
id="dddddd"
|
||||
class="menu_list_content"
|
||||
theme="dark"
|
||||
v-model:openKeys="openKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
mode="inline"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div v-for="(menu) in showMenuList" :key="menu.key" >
|
||||
<a-menu-item :key="menu.key" :name="menu.name" :route="menu.route" v-if="!menu.children">
|
||||
<template #icon><span :class="['icon','iconfont', 'menu_icon', menu.icon]"></span></template>
|
||||
<span class="menu_title">{{menu.name}}</span>
|
||||
</a-menu-item>
|
||||
<a-sub-menu :key="menu.key" v-else>
|
||||
<template #icon>
|
||||
<span :class="['icon','iconfont', 'menu_icon', menu.icon]"></span>
|
||||
</template>
|
||||
<template #expandIcon><span :class="['icon','iconfont', 'menu_icon', menu.expandIcon]"></span></template>
|
||||
<template #title><span class="menu_title">{{menu.name}}</span></template>
|
||||
<div >
|
||||
<a-menu-item v-for="child in menu.children" :key="child.key" :name="child.name" :route="child.route"><span class="menu_title">{{child.name}}</span></a-menu-item>
|
||||
</div>
|
||||
</a-sub-menu>
|
||||
|
||||
</div>
|
||||
</a-menu>
|
||||
</div>
|
||||
|
||||
<div class="home_right_content">
|
||||
<div class="home_content_header">
|
||||
|
||||
<div class="page_name">
|
||||
<div v-show="!routers.length" class="page_name_title">{{nowPageName}}</div>
|
||||
<a-breadcrumb v-show="routers.length" :routes="routers">
|
||||
<template #itemRender="{ route, routes, paths }">
|
||||
<span v-if="routes.indexOf(route) === routes.length - 1">
|
||||
{{ route.breadcrumbName }}
|
||||
</span>
|
||||
<router-link v-else :to="`${paths.join('/')}`">
|
||||
{{ route.breadcrumbName }}
|
||||
</router-link>
|
||||
</template>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
<a-popover v-model:visible="popoverModal" title="" trigger="click">
|
||||
<template #content>
|
||||
<div class="select_item" @click="logout()">
|
||||
<span class="icon iconfont icon-tuichu1"></span><span class="select_item_des">log off</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="user_info" @click="popoverModal = !popoverModal">
|
||||
<img class="header_img" src="@/assets/images/header.png"/>
|
||||
<div class="user_name">{{userInfo.userName}}</div>
|
||||
<span :class="['icon','iconfont','header_icon','icon-xialajiantouxiao', popoverModal?'turn_round_icon':'']"></span>
|
||||
</div>
|
||||
</a-popover>
|
||||
</div>
|
||||
<div class="home_content_body">
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref,reactive,toRefs, onMounted,watch,computed } from "vue";
|
||||
import { useRouter,useRoute } from 'vue-router'
|
||||
import { Https } from "@/tool/https";
|
||||
import {setCookie, getCookie, WriteCookie} from '@/tool/cookie'
|
||||
import { message } from "ant-design-vue";
|
||||
|
||||
export default defineComponent({
|
||||
name:'home',
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const route:any = useRoute()
|
||||
let popoverModal = ref(false)
|
||||
let userInfo:any = ref({})
|
||||
|
||||
watch(route, (newValue:any, oldValue) => {
|
||||
getBreadData(newValue.name)
|
||||
});
|
||||
|
||||
|
||||
|
||||
const state:any = reactive({
|
||||
selectedKeys: ['/home/worktable'],
|
||||
openKeys: [],
|
||||
nowPageName:'Workdesk',//当前页面名称
|
||||
|
||||
|
||||
});
|
||||
|
||||
let menuList:any = ref([])
|
||||
let showMenuList:any = computed(()=>{
|
||||
let menu = []
|
||||
for(let item of menuList.value){
|
||||
if(item.isShow && !item.children){
|
||||
menu.push(item)
|
||||
}else if(item.isShow && item.children){
|
||||
let data = {
|
||||
...item,
|
||||
children:[]
|
||||
}
|
||||
for(let child of item.children){
|
||||
if(child.isShow){
|
||||
data.children.push(child)
|
||||
}
|
||||
}
|
||||
menu.push(data)
|
||||
}
|
||||
}
|
||||
return menu
|
||||
})
|
||||
|
||||
let routers:any = ref([])
|
||||
|
||||
|
||||
let handleClick = (event:any) => {
|
||||
state.selectedKeys = [event.key]
|
||||
state.nowPageName = event.item.name
|
||||
router.push({path:event.item.route})
|
||||
}
|
||||
|
||||
let logout = () =>{
|
||||
let data = {
|
||||
userId:userInfo.value.userId
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.accountLogout, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
router.replace('/login')
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getBreadData = (routeName:string) =>{
|
||||
if(routeName == 'productDetail'){
|
||||
routers.value = [
|
||||
{
|
||||
path: '/productmanage',
|
||||
breadcrumbName: 'Product Management',
|
||||
},
|
||||
{
|
||||
path: '/productDetail',
|
||||
breadcrumbName: 'Product Detail',
|
||||
},
|
||||
]
|
||||
}else{
|
||||
routers.value = []
|
||||
let getRouteInfo = (menuList:any) =>{
|
||||
for(let item of menuList){
|
||||
if(!item.children){
|
||||
if(item.route == route.path){
|
||||
state.nowPageName = item.name
|
||||
state.selectedKeys = [item.key]
|
||||
break
|
||||
}
|
||||
}else{
|
||||
getRouteInfo(item.children)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
getRouteInfo(menuList.value)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let getTitle = (menuList:any,path:string) =>{
|
||||
for(let item of menuList){
|
||||
if(item.route === path){
|
||||
state.nowPageName = item.name
|
||||
}
|
||||
if(item.children){
|
||||
getTitle(item.children,path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
let cookieInfo = getCookie('userInfo') || ''
|
||||
if(cookieInfo){
|
||||
let menuListSession:any = sessionStorage.getItem('menuList')
|
||||
menuList.value = JSON.parse(menuListSession)
|
||||
userInfo.value = JSON.parse(cookieInfo)
|
||||
state.selectedKeys = [route.path]
|
||||
getBreadData(route.name)
|
||||
getTitle(menuList.value, route.path)
|
||||
}else{
|
||||
router.replace('/login') //没登录
|
||||
}
|
||||
|
||||
})
|
||||
return{
|
||||
...toRefs(state),
|
||||
menuList,
|
||||
showMenuList,
|
||||
popoverModal,
|
||||
userInfo,
|
||||
handleClick,
|
||||
logout,
|
||||
routers,
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.home_page{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background: rgba(243,244,248,0.4);
|
||||
|
||||
.home_menu_content{
|
||||
padding-top: 20px;
|
||||
|
||||
.home_menu_log{
|
||||
margin:0 0 27px 28px;
|
||||
width: 144px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.ant-menu-dark.menu_list_content{
|
||||
padding-left: 18px;
|
||||
width: 300px;
|
||||
height: calc(100% - 59px);
|
||||
background: #FFFFFF;
|
||||
flex-shrink: 0;
|
||||
|
||||
.menu_icon{
|
||||
font-size: 24px;
|
||||
color: #808185;
|
||||
}
|
||||
|
||||
.ant-menu-item{
|
||||
padding-left: 31px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 66px;
|
||||
border-radius: 33px 0px 0px 33px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.ant-menu-item-only-child{
|
||||
padding-left: 56px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ant-menu-submenu-title{
|
||||
padding-left: 31px !important;
|
||||
height: 66px;
|
||||
|
||||
}
|
||||
.ant-menu-inline.ant-menu-sub{
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.menu_title{
|
||||
font-size: 18px;
|
||||
font-family: Adobe Heiti Std;
|
||||
font-weight: normal;
|
||||
color: #808185;
|
||||
}
|
||||
.ant-menu-item-selected{
|
||||
background: #F3F4F8;
|
||||
|
||||
.menu_title{
|
||||
color: #343579;
|
||||
}
|
||||
|
||||
.menu_icon{
|
||||
color: #808185;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.home_right_content{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.home_content_header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 66px;
|
||||
padding: 0 28px 0 30px;
|
||||
|
||||
.page_name{
|
||||
|
||||
border-left: solid 4px #161F29;
|
||||
|
||||
.page_name_title,.ant-breadcrumb-link{
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
color: #333333;
|
||||
padding-left: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.user_info{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.header_img{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-right: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.user_name{
|
||||
font-size: 16px;
|
||||
color: #1A1A1A;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header_icon{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.turn_round_icon{
|
||||
-moz-transform:rotate(180deg);
|
||||
-webkit-transform:rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
animation-direction: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.home_content_body{
|
||||
width: 100%;
|
||||
height: calc(100% - 66px);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.select_item{
|
||||
color: #4D4D4D;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select:none;
|
||||
|
||||
|
||||
.iconfont{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.select_item_des{
|
||||
font-size: 13px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
357
src/views/LoginPage.vue
Normal file
@@ -0,0 +1,357 @@
|
||||
<template>
|
||||
<div class="login_page">
|
||||
<div class="login_content">
|
||||
<div class="login_content_body">
|
||||
<img class="login_logo" src='@/assets/images/login_logo.png'>
|
||||
<div class="login_form_content">
|
||||
<img class="login_form_logo" src='@/assets/images/home_logo.png'>
|
||||
<a-input class="login_form_input" size="large" placeholder="account" v-model:value="username">
|
||||
<template #prefix>
|
||||
<span class="icon iconfont icon-yonghuming form_icon_content"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
<a-input type="password" class="login_form_input" size="large" v-model:value="password" placeholder="password" >
|
||||
<template #prefix>
|
||||
<span class="icon iconfont icon-weibiaoti form_icon_content"></span>
|
||||
</template>
|
||||
</a-input>
|
||||
<a-checkbox class="form_checkbox" v-model:checked="isRemember">Remenber Me</a-checkbox>
|
||||
<a-button class="login_button" size="large" type="primary" @click="submitPerLogin">Login</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login_footer_content">copyright ©2023 CodeCreate</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,onMounted,ref,reactive } from "vue";
|
||||
import { Https } from "@/tool/https";
|
||||
import { setCookie } from "@/tool/cookie";
|
||||
import { message } from "ant-design-vue";
|
||||
import { useRouter } from 'vue-router'
|
||||
import { encode, decode } from 'js-base64';
|
||||
|
||||
export default defineComponent({
|
||||
name:'login',
|
||||
setup(){
|
||||
const router = useRouter()
|
||||
let username = ref('')
|
||||
let password = ref('')
|
||||
let isRemember = ref(false)
|
||||
let menuList:any = reactive([{
|
||||
name:'Workdesk',
|
||||
code:'WORK_BENCH',
|
||||
route:'/home/worktable',
|
||||
icon:'icon-gongzuotai',
|
||||
key:'/home/worktable',
|
||||
isShow:false,
|
||||
},
|
||||
{
|
||||
name:'Product Management',
|
||||
code:'PRODUCT_LIST',
|
||||
route:'/home/productmanage',
|
||||
icon:'icon-shangpinguanli',
|
||||
key:'/home/productmanage',
|
||||
isShow:false,
|
||||
},
|
||||
{
|
||||
name:'Store Management',
|
||||
code:'STORE_LIST',
|
||||
route:'/home/storemanage',
|
||||
icon:'icon-mendianguanli',
|
||||
key:'/home/storemanage',
|
||||
isShow:false,
|
||||
},
|
||||
{
|
||||
name:'Label Management',
|
||||
code:'LABEL_LIST',
|
||||
route:'/home/labelmanage',
|
||||
icon:'icon-biaoqian',
|
||||
key:'/home/labelmanage',
|
||||
isShow:false,
|
||||
},
|
||||
{
|
||||
name:'Setting',
|
||||
code:'SYS_MANAGER',
|
||||
route:'',
|
||||
icon:'icon-xitongguanli',
|
||||
key:'sub2',
|
||||
expandIcon:'icon-xialajiantouxiao',
|
||||
isShow:false,
|
||||
children:[
|
||||
{
|
||||
code:'USER_MANAGER',
|
||||
name:'User Management',
|
||||
route:'/home/usermanage',
|
||||
icon:'',
|
||||
key:'/home/usermanage',
|
||||
isShow:false,
|
||||
},
|
||||
{
|
||||
code:'ROLE_MANAGER',
|
||||
name:'Access Permission',
|
||||
route:'/home/rolemanage',
|
||||
icon:'',
|
||||
key:'/home/rolemanage',
|
||||
isShow:false,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name:'Export Excil',
|
||||
code:'MI_TU_EXPORT',
|
||||
route:'/home/exportExcil',
|
||||
icon:'icon-xiazaiwenjian',
|
||||
key:'/home/exportExcil',
|
||||
isShow:false,
|
||||
},
|
||||
// {
|
||||
// name:'Export Excil',
|
||||
// code:'EXPORT_EXCIL',
|
||||
// route:'/home/exportExcil',
|
||||
// icon:'icon-xiazaiwenjian',
|
||||
// key:'sub3',
|
||||
// key:'/home/exportExcil',
|
||||
// expandIcon:'icon-xialajiantouxiao',
|
||||
// isShow:true,
|
||||
// children:[
|
||||
// {
|
||||
// code:'USER_MANAGER',
|
||||
// name:'User Management',
|
||||
// route:'/home/excil1',
|
||||
// icon:'',
|
||||
// key:'/home/excil1',
|
||||
// isShow:true,
|
||||
// },
|
||||
// {
|
||||
// code:'ROLE_MANAGER',
|
||||
// name:'Access Permission',
|
||||
// route:'/home/excil2',
|
||||
// icon:'',
|
||||
// key:'/home/excil2',
|
||||
// isShow:true,
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
])
|
||||
|
||||
let submitPerLogin = () =>{
|
||||
if (!username) {
|
||||
message.error("Please enter your account");
|
||||
return;
|
||||
}
|
||||
if(!password){
|
||||
message.error("Please enter your password");
|
||||
return;
|
||||
}
|
||||
let data = {
|
||||
userAccount: username.value,
|
||||
password: encode(password.value),
|
||||
};
|
||||
Https.axiosPost(Https.httpUrls.accountLogin, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let token = rv.token;
|
||||
setCookie("token", token);
|
||||
setCookie("userInfo", JSON.stringify(rv));
|
||||
rememberAccount()
|
||||
getMenuRole()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let rememberAccount = () =>{
|
||||
if(isRemember){
|
||||
let accountInfo = {
|
||||
account:username.value,
|
||||
password:password.value,
|
||||
}
|
||||
localStorage.setItem('accountInfo',JSON.stringify(accountInfo))
|
||||
}
|
||||
}
|
||||
|
||||
let getMenuRole = () =>{
|
||||
Https.axiosGet(Https.httpUrls.queryUsrPermission).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let rolePermission = rv.rolePermission.level1ResourceList
|
||||
let setMenuListShow = (role:any) =>{
|
||||
for(let menu of menuList){
|
||||
|
||||
if(role.code === menu.code){
|
||||
console.log(menu,role);
|
||||
menu.isShow = role.select == 1 ? true :false
|
||||
if(role.operationList){
|
||||
menu.operationList = role.operationList
|
||||
}
|
||||
break
|
||||
}
|
||||
if(menu.children){
|
||||
for(let child of menu.children){
|
||||
if(role.code == child.code){
|
||||
child.isShow = role.select == 1 ? true :false
|
||||
if(role.operationList){
|
||||
child.operationList = role.operationList
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for(let role of rolePermission){
|
||||
setMenuListShow(role)
|
||||
for(let level2 of role.level2ResourceList){
|
||||
setMenuListShow(level2)
|
||||
}
|
||||
}
|
||||
sessionStorage.setItem('menuList',JSON.stringify(menuList))
|
||||
let worktable = menuList.filter((v:any) => v.code === 'WORK_BENCH')[0]
|
||||
if(worktable.isShow){
|
||||
router.push("/home/worktable")
|
||||
}else{
|
||||
message.error('You do not have permission to access this website, please contact your manager.')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
if(localStorage.getItem('accountInfo')){
|
||||
let accountInfo:any = localStorage.getItem('accountInfo')
|
||||
accountInfo = JSON.parse(accountInfo)
|
||||
username.value = accountInfo.account
|
||||
password.value = accountInfo.password
|
||||
isRemember.value = true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
username,
|
||||
password,
|
||||
isRemember,
|
||||
submitPerLogin,
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.login_page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.login_content{
|
||||
width: 1440px;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.login_content_body{
|
||||
width: 100%;
|
||||
padding: 0 160px 0 80px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.login_logo{
|
||||
width: 457px;
|
||||
height: 370px;
|
||||
}
|
||||
|
||||
|
||||
.login_form_content{
|
||||
width: 600px;
|
||||
height: 540px;
|
||||
padding: 0 60px;
|
||||
box-sizing: border-box;
|
||||
background: #FFFFFF;
|
||||
box-shadow: -3px 20px 59px 0px rgba(200,200,200,0.3);
|
||||
border-radius: 10px;
|
||||
|
||||
.login_form_logo{
|
||||
width: 165px;
|
||||
margin: 78px auto 60px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login_form_input{
|
||||
width: 480px;
|
||||
height: 50px;
|
||||
border: 1px solid #DFDFDF;
|
||||
border-radius: 25px;
|
||||
margin-bottom: 29px;
|
||||
}
|
||||
|
||||
.form_checkbox{
|
||||
font-size: 16px;
|
||||
color: #151515;
|
||||
margin-bottom: 29px;
|
||||
|
||||
.ant-checkbox-inner{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 1px solid #DFDFDF;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.form_icon_content{
|
||||
font-size: 18px;
|
||||
color: #343579;
|
||||
}
|
||||
|
||||
.login_button{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background: #343579;
|
||||
border-radius: 25px;
|
||||
font-size: 18px;
|
||||
font-family: Roboto;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login_footer_content{
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #151515;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 68px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
<style lang="less">
|
||||
.login_page {
|
||||
.ant-input-prefix{
|
||||
margin: 0px 15px;
|
||||
}
|
||||
.ant-input-affix-wrapper:focus{
|
||||
border-color: #343579 !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
.ant-input-affix-wrapper-focused{
|
||||
border-color: #343579 !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
55
src/views/NoPermissionPage.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="no_permission_page">
|
||||
<div class="no_permission_page_content">
|
||||
<img class="no_permission_page_img" src="@/assets/images/no_permission.png">
|
||||
<div class="no_permission_tip">
|
||||
<span>对不起,您暂时无访问权限,</span>
|
||||
<router-link to="/home/worktable">点击返工作台</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent} from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name:'noPermission',
|
||||
setup(){
|
||||
|
||||
|
||||
|
||||
return {
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.no_permission_page{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #F6F6F6;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.no_permission_page_content{
|
||||
text-align: center;
|
||||
margin-top: -100px;
|
||||
|
||||
.no_permission_page_img{
|
||||
width: 593px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.no_permission_tip{
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 500;
|
||||
color: #151515;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
241
src/views/childView/exportExcil.vue
Normal file
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div class="export_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'Role Name or Id'"><a-input v-model:value="name" size="large" placeholder="Please input Role Name or Id" @keydown.enter="searchList()"/></filterComponent>
|
||||
<filterComponent :title="'Period'">
|
||||
<a-select
|
||||
v-model:value="type"
|
||||
size="large"
|
||||
style="width:280px"
|
||||
:options="labelTypeList"
|
||||
allowClear
|
||||
></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Export Time'"> <a-range-picker size="large" v-model:value="date" :placeholder="['Start Time', 'End Time']" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD"/></filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Seach</a-button>
|
||||
<a-button class="default_button" size="large" @click="resetList()">Reset</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="setExport(record,index)">Export</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef,onMounted,createVNode } from "vue";
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { Moment } from 'moment';
|
||||
import { Https } from "@/tool/https";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name:'roleManage',
|
||||
components:{filterComponent},
|
||||
setup(){
|
||||
let filter = reactive({
|
||||
name:'',
|
||||
type:'',
|
||||
date:ref<Moment[]>([])
|
||||
})
|
||||
let tableLoading = ref(false)
|
||||
let columns = reactive([
|
||||
{ title: 'Name', align:'center', ellipsis: true, dataIndex: 'exportName', key: 'exportName', width:250 },
|
||||
{ title: 'Create Time', align:'center', ellipsis: true, dataIndex: 'createTime', key: 'createTime'},
|
||||
{ title: 'Type', align:'center', ellipsis: true, dataIndex: 'span', key: 'span'},
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
// width: 250,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
])
|
||||
let collectionList = ref([])
|
||||
let labelTypeList:any = ref([
|
||||
{ value:'week',label:'Week'},
|
||||
{ value:'month',label:'Month'},
|
||||
{ value:'year',label:'Year'},
|
||||
])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(1)
|
||||
let changePage = (e:any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
getExportList()
|
||||
}
|
||||
|
||||
let getExportList = () =>{
|
||||
|
||||
let data = {
|
||||
page:currentPage.value,
|
||||
size:pageSize.value,
|
||||
fileName:filter.name,
|
||||
span:filter.type,
|
||||
endTime:filter.date[1]?filter.date[1]:'',
|
||||
startTime:filter.date[0]?filter.date[0]:'',
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.miTuExportPage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
if(currentPage.value > 1 && rv.content.length == 0){
|
||||
currentPage.value = 1
|
||||
getExportList()
|
||||
}else{
|
||||
collectionList.value = rv.content
|
||||
|
||||
total = rv.total
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
).catch((res:any)=>{
|
||||
tableLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getExportList()
|
||||
}
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.name = ''
|
||||
getExportList()
|
||||
}
|
||||
|
||||
let setExport = (data:any,index:any) =>{
|
||||
Https.axiosGet(Https.httpUrls.miTuExportExport+`/${data.id}`,{responseType: 'blob'}).then((rv)=>{
|
||||
let name = rv.name.split('=')[1];
|
||||
let url = window.URL.createObjectURL(new Blob([rv.data], { type: rv.type }));
|
||||
const link = document.createElement('a');
|
||||
link.download = name; //定义表格名称,后缀是文件格式
|
||||
link.style.display = 'none';
|
||||
link.href = url;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href)
|
||||
document.body.removeChild(link);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getExportList()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
tableLoading,
|
||||
columns,
|
||||
labelTypeList,
|
||||
collectionList,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
changePage,
|
||||
searchList,
|
||||
resetList,
|
||||
setExport,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.export_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.export_pic{
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
// justify-content: space-between;
|
||||
justify-content: center;
|
||||
padding: 0 50px;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form_check_all{
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.permission_table{
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
border-left:1px solid #DFDFDF;
|
||||
border-top:1px solid #DFDFDF;
|
||||
|
||||
td{
|
||||
border-bottom: 1px solid #DFDFDF;
|
||||
border-right: 1px solid #DFDFDF;
|
||||
|
||||
.table_td_block{
|
||||
padding: 16px 0 16px 20px;
|
||||
border-bottom: 1px solid #DFDFDF;
|
||||
|
||||
&:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.table_td_permission{
|
||||
display: flex;
|
||||
min-height: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
.first_td{
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.second_td{
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
533
src/views/childView/exportExcil/userManage.vue
Normal file
@@ -0,0 +1,533 @@
|
||||
<template>
|
||||
<div class="user_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'User Name'"><a-input v-model:value="userName" size="large" placeholder="Please input User Name" @keydown.enter="searchList()"/></filterComponent>
|
||||
<filterComponent :title="'User Phone'"><a-input v-model:value="userPhone" size="large" placeholder="Please input User Phone" @keydown.enter="searchList()" /></filterComponent>
|
||||
<filterComponent :title="'User Role'">
|
||||
<a-select v-model:value="roleId" size="large" style="width:280px" placeholder="Please select" :options="userRoleList" allowClear></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'User State'">
|
||||
<a-select v-model:value="state" size="large" style="width:280px" placeholder="Please select" :options="userStateList" allowClear></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Add Time'"><a-range-picker v-model:value="addTime" size="large" :placeholder="['Start Time', 'End Time']" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD"/></filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Seach</a-button>
|
||||
<a-button class="default_button" size="large" @click="resetList()">Reset</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35" type="primary" size="large" @click="addUser()">+Add User</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'stateName'">
|
||||
<div :class="['state_name_block', record.state == 1 ? 'enable_state' :'disable_state']">
|
||||
<div class="state_round"></div>
|
||||
<div>{{record.stateName}}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="editUser(record)">Edit</div>
|
||||
<div :class="['operate_item', record.state == 0 ? '' :'operate_disable_state']" @click="enableUser(record,index)">
|
||||
<div v-if="record.state == 0">Enable</div>
|
||||
<div v-else>Disable</div>
|
||||
</div>
|
||||
<div class="operate_item" @click="deleteUser(record,index)">Delete</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal class="edit_modal_component"
|
||||
:destroyOnClose="true"
|
||||
v-model:visible="editUserModal"
|
||||
:footer="null"
|
||||
:title="editUserTitle"
|
||||
width="680px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
@cancel="closeUser"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Name" name="userName">
|
||||
<a-input v-model:value="formState.userName" size="large" placeholder="Please input user name"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Account" name="userAccount">
|
||||
<a-input v-model:value="formState.userAccount" size="large" placeholder="Please input user account"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Code" name="userPassword">
|
||||
<a-input v-model:value="formState.userPassword" size="large" placeholder="Please input user code" type="password"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Phone" name="userPhone">
|
||||
<a-input v-model:value="formState.userPhone" size="large" placeholder="Please input user phone"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="App User" name="appUser">
|
||||
<a-select v-model:value="formState.appUser" size="large" :options="appUserList" placeholder="Please select"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Role" name="roleId">
|
||||
<a-select v-model:value="formState.roleId" size="large" :options="userRoleList" placeholder="Please select"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="Owner Store" name="storeIds">
|
||||
<a-select v-model:value="formState.storeIds" size="large" :options="ownerStoreList" mode="multiple" placeholder="Please select"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="24">
|
||||
<a-form-item label ="Remarks" name="remarks">
|
||||
<a-input v-model:value="formState.remarks" size="large" placeholder="Please input remarks"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeUser">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="confirmSubmit">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef,onMounted,createVNode } from "vue";
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { Moment } from 'moment';
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { Https } from "@/tool/https";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
import { encode, decode } from 'js-base64';
|
||||
import { useRoute } from 'vue-router'
|
||||
import { formatTime, startTime, endTime, exportExcil } from "@/tool/util"
|
||||
export default defineComponent({
|
||||
name:'userManage',
|
||||
components:{filterComponent},
|
||||
setup(){
|
||||
const route = useRoute()
|
||||
let filter:any = reactive({
|
||||
userName:'',
|
||||
userPhone:'',
|
||||
roleId:[],
|
||||
state:[],
|
||||
addTime:ref<Moment[]>([]),
|
||||
})
|
||||
let tableLoading = ref(false)
|
||||
let userRoleList:any = ref([])
|
||||
let userStateList:any = ref([
|
||||
{value:'1',label:'Activated'},
|
||||
{value:'0',label:'Deactivated'},
|
||||
])
|
||||
let appUserList:any = ref([
|
||||
{value:1,label:'Yes'},
|
||||
{value:0,label:'No'},
|
||||
])
|
||||
let ownerStoreList:any = ref([])
|
||||
let columns = reactive([
|
||||
{ title: 'User Name', align:'center', ellipsis: true, dataIndex: 'userName', key: 'userName' },
|
||||
{ title: 'User Account', align:'center', ellipsis: true, dataIndex: 'userAccount', key: 'userAccount'},
|
||||
{ title: 'Add Role', align:'center', ellipsis: true, dataIndex: 'roleName', key: 'roleName' },
|
||||
{ title: 'User Phone', align:'center', ellipsis: true, dataIndex: 'userPhone', key: 'userPhone' },
|
||||
{ title: 'Owned Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',customRender:(record:any)=>{
|
||||
let storeNameNew = record.text.join(',')
|
||||
return storeNameNew
|
||||
}
|
||||
},
|
||||
{ title: 'Add Time', align:'center', ellipsis: true, dataIndex: 'createDate', key: 'createDate' ,customRender:(record:any)=>{
|
||||
let time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
||||
return time
|
||||
},
|
||||
},
|
||||
{ title: 'Activated', align:'center', ellipsis: true, dataIndex: 'appUserName', key: 'appUserName' },
|
||||
{ title: 'User State', align:'center', ellipsis: true, dataIndex: 'stateName', key: 'stateName' },
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
])
|
||||
let collectionList:any = ref([])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(1)
|
||||
let editUserModal = ref(false)
|
||||
let editUserTitle = ref('Add user')
|
||||
let formRef = ref();
|
||||
let formState:any = ref({
|
||||
id: '',
|
||||
userName: '',
|
||||
userAccount: '',
|
||||
userPassword:'',
|
||||
userPhone:'',
|
||||
appUser:null,
|
||||
roleId:null,
|
||||
storeIds:[],
|
||||
remarks:'',
|
||||
});
|
||||
let rules = {
|
||||
userName: [
|
||||
{ required: true, message: 'Please input user name', trigger: 'blur' },
|
||||
],
|
||||
userAccount: [
|
||||
{ required: true, message: 'Please input user account', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: 'Length should be 6 to 20', trigger: 'blur' },
|
||||
],
|
||||
userPassword: [
|
||||
{ required: true, message: 'Please input user code', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: 'Length should be 6 to 20', trigger: 'blur' },
|
||||
],
|
||||
userPhone: [
|
||||
{ required: true, message: 'Please input user phone', trigger: 'blur' },
|
||||
{ pattern: /^\d*$/, message: 'The format of the mobile phone number is incorrect', trigger: 'blur' },
|
||||
],
|
||||
appUser: [
|
||||
{ required: true, message: 'Please select app user', trigger: 'blur' },
|
||||
],
|
||||
userRole: [
|
||||
{ required: true, message: 'Please select user role', trigger: 'blur' },
|
||||
],
|
||||
ownerStore: [
|
||||
{ required: true, message: 'Please select owner store', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
let changePage = (e:any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
getUserlist()
|
||||
}
|
||||
|
||||
let closeUser = () =>{
|
||||
formState.value = {
|
||||
id: '',
|
||||
userName: '',
|
||||
userAccount: '',
|
||||
userPassword:'',
|
||||
userPhone:'',
|
||||
appUser:null,
|
||||
roleId:null,
|
||||
storeIds:[],
|
||||
remarks:'',
|
||||
}
|
||||
editUserModal.value = false
|
||||
}
|
||||
|
||||
let addUser = () =>{
|
||||
editUserTitle.value = 'Add user'
|
||||
editUserModal.value = true
|
||||
|
||||
// const data = [
|
||||
// [
|
||||
// ['12312312312'],
|
||||
// ['姓名', '年龄', '性别'],
|
||||
// ['张三', 20, '男'],
|
||||
// ['李四', 25, '女']
|
||||
// ],
|
||||
// [
|
||||
// ['城市', '人口'],
|
||||
// ['北京', 21540000],
|
||||
// ['上海', 24240000]
|
||||
// ]
|
||||
// ];
|
||||
// const sheetNames = ['Sheet1', 'Sheet2'];
|
||||
// const fileName = 'output.xlsx';
|
||||
// const configuration = {
|
||||
// width:15,
|
||||
// titleCell:2,
|
||||
// }
|
||||
// exportExcil(data,sheetNames,fileName,configuration)
|
||||
}
|
||||
|
||||
let editUser = (data:any,) =>{
|
||||
editUserModal.value = true
|
||||
editUserTitle.value = 'Eidt user'
|
||||
formState.value = {
|
||||
id: data.id,
|
||||
userName: data.userName,
|
||||
userAccount:data.userAccount,
|
||||
userPassword:data.userPassword ? decode(data.userPassword) : '',
|
||||
userPhone:data.userPhone,
|
||||
appUser:data.appUser,
|
||||
roleId:String(data.roleId),
|
||||
storeIds:data.storeIds,
|
||||
remarks:data.remarks,
|
||||
}
|
||||
}
|
||||
|
||||
let getUserlist = () =>{
|
||||
let data = {
|
||||
userName : filter.userName,
|
||||
userPhone:filter.userPhone,
|
||||
state:filter.state && filter.state.length ? filter.state : '',
|
||||
roleId:filter.roleId && filter.roleId.length ? filter.roleId : '',
|
||||
createDateStart:filter.addTime && filter.addTime.length ? startTime(filter.addTime[0]) :'',
|
||||
createDateEnd:filter.addTime && filter.addTime.length ? endTime(filter.addTime[1]) :'',
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.accountQueryUserPage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
collectionList.value = rv.content
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getRolelist = () =>{
|
||||
Https.axiosPost(Https.httpUrls.roleQueryAll, {}).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
userRoleList.value = rv.map((v:any)=>{
|
||||
let data = {
|
||||
...v,
|
||||
value:v.id,
|
||||
label:v.name
|
||||
}
|
||||
return data
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getUserlist()
|
||||
}
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.userName = ''
|
||||
filter.userPhone = ''
|
||||
filter.roleId = []
|
||||
filter.state = []
|
||||
filter.addTime = ref<Moment[]>([])
|
||||
getUserlist()
|
||||
}
|
||||
|
||||
let confirmSubmit = () =>{
|
||||
let data = {
|
||||
...formState.value,
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
data.userPassword = encode(data.userPassword)
|
||||
let submit = () =>{
|
||||
Https.axiosPost(Https.httpUrls.accountSaveOrEdit, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = !formState.value.id ? 'User added successfully' :'Edit user successfully'
|
||||
message.success(tip)
|
||||
resetList()
|
||||
closeUser()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
formRef.value.validate().then(()=>{
|
||||
submit()
|
||||
})
|
||||
}
|
||||
|
||||
let enableUser = (record:any,index:any) =>{
|
||||
let data ={
|
||||
id:record.id,
|
||||
enable:record.state == 1 ? 0 : 1
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.accountEnable, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = record.state == 0? 'Enable successfully' :'Disable successfully'
|
||||
message.success(tip)
|
||||
collectionList.value[index].state = data.enable
|
||||
collectionList.value[index].stateName = data.enable == 1 ? 'Activated' :'Deactivated'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let deleteUser = (data:any,index:any) =>{
|
||||
let confirmDelete = (data:any,index:any) =>{
|
||||
let newData = {
|
||||
id:data.id
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.accountDelete, newData).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
message.success('Delete success')
|
||||
collectionList.value.splice(index,1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "Are you sure delete the user?",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
confirmDelete(data,index)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let storeQueryAll = () =>{
|
||||
Https.axiosPost(Https.httpUrls.storeQueryAll,{}).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
ownerStoreList.value = rv.map((v:any)=>{
|
||||
let data = {
|
||||
...v,
|
||||
value:v.id,
|
||||
label:v.name
|
||||
}
|
||||
return data
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(route.query && route.query.type === 'add'){
|
||||
addUser()
|
||||
}
|
||||
getUserlist()
|
||||
storeQueryAll()
|
||||
getRolelist()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
tableLoading,
|
||||
userRoleList,
|
||||
userStateList,
|
||||
appUserList,
|
||||
ownerStoreList,
|
||||
columns,
|
||||
collectionList,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
editUserModal,
|
||||
editUserTitle,
|
||||
formRef,
|
||||
formState,
|
||||
rules,
|
||||
changePage,
|
||||
closeUser,
|
||||
addUser,
|
||||
editUser,
|
||||
searchList,
|
||||
resetList,
|
||||
confirmSubmit,
|
||||
enableUser,
|
||||
deleteUser
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.user_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.user_pic{
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.state_name_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
|
||||
&.enable_state{
|
||||
color: #47E417;
|
||||
|
||||
.state_round{
|
||||
background: #47E417;
|
||||
}
|
||||
}
|
||||
|
||||
&.disable_state{
|
||||
color: #E41335;
|
||||
|
||||
.state_round{
|
||||
background: #E41335;
|
||||
}
|
||||
}
|
||||
|
||||
.state_round{
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
|
||||
&.operate_disable_state{
|
||||
color: #E41335;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
321
src/views/childView/labelManage.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<div class="label_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'Label Name'"><a-input v-model:value="name" size="large" placeholder="Please input Label Name or Id" @keydown.enter="searchList"/></filterComponent>
|
||||
<filterComponent :title="'Label attributes'">
|
||||
<a-select
|
||||
v-model:value="type"
|
||||
size="large"
|
||||
style="width:280px"
|
||||
:options="labelTypeList"
|
||||
placeholder="Please select"
|
||||
allowClear
|
||||
></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Add Time'"><a-range-picker v-model:value="addTime" size="large" :placeholder="['Start Time', 'End Time']" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD"/></filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Seach</a-button>
|
||||
<a-button class="default_button" size="large" @click="resetList()">Reset</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35" type="primary" size="large" @click="addLabel()">+Add Label</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="editLabel(record)">Edit</div>
|
||||
<div class="operate_item" @click="deleteLabel(record,index)">Delete</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal class="edit_modal_component"
|
||||
:destroyOnClose="true"
|
||||
v-model:visible="editLabelModal"
|
||||
:footer="null"
|
||||
:title="editLabelTitle"
|
||||
width="560px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
@cancel="closeLabel"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-form-item label="Label Name" name="name">
|
||||
<a-input v-model:value="formState.name" size="large" placeholder="Please input label name" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Label attributes" name="type">
|
||||
<a-select v-model:value="formState.type" size="large" :options="labelTypeList" placeholder="Please select label attributes"></a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Remarks" name="remarks">
|
||||
<a-input v-model:value="formState.remarks" size="large" placeholder="Please input remarks"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeLabel">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="confirmSubmit">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef,onMounted,createVNode } from "vue";
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { Moment } from 'moment';
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
import { Https } from "@/tool/https";
|
||||
import { formatTime, startTime, endTime } from "@/tool/util"
|
||||
|
||||
export default defineComponent({
|
||||
name:'labelManage',
|
||||
components:{filterComponent},
|
||||
setup(){
|
||||
let filter:any = reactive({
|
||||
name:'',
|
||||
type:'',
|
||||
addTime:ref<Moment[]>([])
|
||||
})
|
||||
let tableLoading = ref(false)
|
||||
let labelTypeList:any = ref([
|
||||
{ value:'CUSTOM',label:'Custom tags'},
|
||||
{ value:'NEW_PRODUCT',label:'New in'},
|
||||
{ value:'SALE',label:'Discount'},
|
||||
])
|
||||
let labelTypeData:any = {
|
||||
'CUSTOM':'Custom tags',
|
||||
'NEW_PRODUCT':'New in',
|
||||
'SALE':'Discount'
|
||||
}
|
||||
let columns = reactive([
|
||||
{ title: 'Label Name', align:'center', ellipsis: true, dataIndex: 'name', key: 'name' },
|
||||
{ title: 'Label attributes', align:'center', ellipsis: true, dataIndex: 'type', key: 'type', customRender:(record:any)=>{
|
||||
let labelType = labelTypeData[record.text]
|
||||
return labelType },
|
||||
},
|
||||
{ title: 'Add User', align:'center', ellipsis: true, dataIndex: 'createUserName', key: 'createUserName' },
|
||||
{ title: 'Label Product Counts', align:'center', ellipsis: true, dataIndex: 'counts', key: 'counts' },
|
||||
{ title: 'Remarks', align:'center', ellipsis: true, dataIndex: 'remarks', key: 'remarks' },
|
||||
{ title: 'Add Time', align:'center', ellipsis: true, dataIndex: 'createDate', key: 'createDate',customRender:(record:any)=>{
|
||||
let time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
||||
return time },
|
||||
},
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
])
|
||||
let collectionList = ref([])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(1)
|
||||
let editLabelModal = ref(false)
|
||||
let editLabelTitle = ref('Add Label')
|
||||
let formRef = ref();
|
||||
let formState = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
type:[],
|
||||
remarks:'',
|
||||
});
|
||||
let rules = reactive({
|
||||
name: [
|
||||
{ required: true, message: 'Please input label name', trigger: 'blur' },
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: 'Please select label attributes', trigger: 'blur' },
|
||||
],
|
||||
})
|
||||
|
||||
let changePage = (e:any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
getLabelist()
|
||||
}
|
||||
|
||||
let closeLabel = () =>{
|
||||
formState.value = {
|
||||
id: '',
|
||||
name: '',
|
||||
type:[],
|
||||
remarks:''
|
||||
}
|
||||
editLabelModal.value = false
|
||||
}
|
||||
|
||||
let addLabel = () =>{
|
||||
editLabelTitle.value = 'Add Label'
|
||||
editLabelModal.value = true
|
||||
}
|
||||
|
||||
let editLabel = (data:any,) =>{
|
||||
editLabelModal.value = true
|
||||
editLabelTitle.value = 'Eidt Label'
|
||||
formState.value = {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
type:data.type,
|
||||
remarks:data.remarks,
|
||||
}
|
||||
}
|
||||
|
||||
let getLabelist = () =>{
|
||||
let data = {
|
||||
name : filter.name,
|
||||
type:filter.type,
|
||||
createDateStart:filter.addTime && filter.addTime.length ? startTime(filter.addTime[0]) : '',
|
||||
createDateEnd:filter.addTime && filter.addTime.length ? endTime(filter.addTime[1]) : '',
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.labelQueryStorePage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
collectionList.value = rv.content
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let confirmSubmit = () =>{
|
||||
let data = {
|
||||
...formState.value,
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
formRef.value.validate().then(()=>{
|
||||
submit()
|
||||
})
|
||||
let submit = () => {
|
||||
Https.axiosPost(Https.httpUrls.labelSaveOrEdit, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = !formState.value.id ? 'Label added successfully' :'Edit label successfully'
|
||||
message.success(tip)
|
||||
resetList()
|
||||
closeLabel()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getLabelist()
|
||||
}
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.name = ''
|
||||
filter.type = ''
|
||||
filter.addTime = ref<Moment[]>([])
|
||||
getLabelist()
|
||||
}
|
||||
|
||||
let deleteLabel = (data:any,index:any) =>{
|
||||
let confirmDelete = (data:any,index:any) =>{
|
||||
let newData = {
|
||||
id:data.id
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.labelDelete, newData).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
message.success('Delete success')
|
||||
collectionList.value.splice(index,1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "Are you sure about the label? The label associated with the product will become invalid after deletion!",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
confirmDelete(data,index)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLabelist()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
tableLoading,
|
||||
labelTypeList,
|
||||
columns,
|
||||
collectionList,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
editLabelModal,
|
||||
editLabelTitle,
|
||||
formRef,
|
||||
formState,
|
||||
rules,
|
||||
changePage,
|
||||
closeLabel,
|
||||
addLabel,
|
||||
editLabel,
|
||||
confirmSubmit,
|
||||
searchList,
|
||||
resetList,
|
||||
deleteLabel
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.label_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
485
src/views/childView/productDetail.vue
Normal file
@@ -0,0 +1,485 @@
|
||||
<template>
|
||||
<div class="productDetail_page">
|
||||
<div class="productDetail_page_cotent">
|
||||
<div class="productDetail_page_cotent_center">
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Product image</div>
|
||||
</div>
|
||||
<div class="product_master_diagram">
|
||||
<img class="product_master_img" :src="formState.pictureUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Product label(s)</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="productCategory_block">
|
||||
<div class="produce_label_item" :style="{background:colorMap[label.type]}" v-for="label in productLabelList" :key="label.id">{{label.name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Pirce</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="productCategory_block">${{formState.price}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Inventory</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<a-table class="form_width_100" :columns="columns" :data-source="formState.storeList" bordered
|
||||
:pagination="false" :scroll="{y: 400 }">
|
||||
</a-table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Label attributes</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="productCategory_block">{{formState.productCategory}}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="detail_item_sec_title sec_title_margin">Attribute(s)</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="attribute_block" v-if="formState.productAttribute?.length">
|
||||
<div class="attribute_block_item" v-for="attr in formState.productAttribute" :key="attr.value">
|
||||
<div class="attribute_block_item_content">
|
||||
<div class="attribute_item_title">{{attr.labelType}}</div>
|
||||
<div class="attribute_item_value" :title="attr.attributeValueList.join(',')">{{attr.attributeValueList.join(',')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="null_data_block">
|
||||
<img class="null_data_img" src="@/assets/images/null_img.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail_item_block">
|
||||
<div class="detail_item_title_block">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng detail_icon"></span>
|
||||
<div>Mix and Match(es)</div>
|
||||
</div>
|
||||
<div class="detail_item_content">
|
||||
<div class="product_match_block">
|
||||
<div class="product_match_item" v-for="(match,index) in formState.assortmentList" :key="match.title" v-show="index<5 || assortmentShowMore">
|
||||
<div class="product_match_item_header">
|
||||
<div>Look{{index+1}}</div>
|
||||
</div>
|
||||
<div class="product_match_item_content">
|
||||
<div class="match_item_img_content" v-for="img in match" :key="img">
|
||||
<div class="match_item_img_block" @click="showMatchDetail(img.productId)">
|
||||
<img :src="img.generatePictureUrl">
|
||||
</div>
|
||||
<div class="product_price">${{img.price}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-button class="primary_button show_more_button" type="primary" @click="assortmentListShowMore()" v-show="formState.assortmentList.length>5">{{assortmentShowMore?'Hide':'See More'}}</a-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<ProductDetailModal ref="productDetailModalDom"></ProductDetailModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,onMounted,reactive } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { useRouter,useRoute } from 'vue-router'
|
||||
import { Https } from "@/tool/https";
|
||||
import ProductDetailModal from '@/component/productComponent/productDetailModal.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name:'productDetail',
|
||||
components:{ProductDetailModal},
|
||||
setup(){
|
||||
const route = useRoute()
|
||||
let productDetail:any = ref([])
|
||||
let productDetailModalDom:any = ref()
|
||||
let formState:any = ref({
|
||||
productPic:'',
|
||||
productLabel:[],
|
||||
inventoryData:[],
|
||||
storeList:[],
|
||||
storeNameList:[],
|
||||
productCategory:'',
|
||||
productAttribute:[],
|
||||
assortmentList:[]
|
||||
});
|
||||
let assortmentShowMore = ref(false)
|
||||
let productCategoryList:any = ref([])
|
||||
let productAttributeList = ref([])
|
||||
let columns = reactive([
|
||||
{ title: 'Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',width:"200px" },
|
||||
{
|
||||
title: 'Inventory',
|
||||
align:'center',
|
||||
ellipsis: true,
|
||||
dataIndex: 'inventory',
|
||||
key: 'inventory',
|
||||
children: [
|
||||
{title: 'XS',dataIndex: 'XS',key: 'XS', align:'center', },
|
||||
{title: 'S',dataIndex: 'S',key: 'S', align:'center',},
|
||||
{title: 'M',dataIndex: 'M',key: 'M', align:'center',},
|
||||
{title: 'L',dataIndex: 'L',key: 'L', align:'center',},
|
||||
{title: 'XL',dataIndex: 'XL',key: 'XL', align:'center',},
|
||||
{title: 'XXL',dataIndex: 'XXL',key: 'XXL', align:'center',},
|
||||
],
|
||||
},
|
||||
])
|
||||
let colorMap = ref({
|
||||
CUSTOM:'#798BD9',
|
||||
NEW_PRODUCT:'#FFC384',
|
||||
SALE:'#79D97C',
|
||||
})
|
||||
|
||||
|
||||
let productLabelList:any = ref([])
|
||||
|
||||
let getProductLabelList = () =>{
|
||||
Https.axiosPost(Https.httpUrls.queryProductLabel).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
productLabelList.value = rv.filter((v:any) => formState.value.productLabel.indexOf(v.id) > -1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getStoreInfo = (storeInfo:any) =>{
|
||||
let storeList = []
|
||||
let storeNameList:any = []
|
||||
for(let item of storeInfo){
|
||||
let sizeData:any = {}
|
||||
for(let size of item.stock){
|
||||
sizeData[size.size] = size.num
|
||||
}
|
||||
let data = {
|
||||
storeName:item.storeName,
|
||||
storeId:item.storeId,
|
||||
...sizeData,
|
||||
}
|
||||
storeList.push(data)
|
||||
}
|
||||
storeInfo.forEach((item:any) => {
|
||||
item.stock.forEach((stockItem:any) => {
|
||||
storeNameList.push(stockItem.size);
|
||||
});
|
||||
});
|
||||
|
||||
// 排序并去重
|
||||
storeNameList = storeNameList.sort().filter((value:any, index:any, array:any) => {
|
||||
return array.indexOf(value) === index;
|
||||
});
|
||||
let children:any = []
|
||||
storeNameList.forEach((item:any) => {
|
||||
children.push({
|
||||
title:item,
|
||||
dataIndex:item,
|
||||
key:item,
|
||||
align:'center'
|
||||
})
|
||||
});
|
||||
columns[1].children = children
|
||||
return {storeList,storeNameList}
|
||||
}
|
||||
|
||||
let getProductDetail = (id:any) =>{
|
||||
return new Promise((resolve:any,rj:any)=>{
|
||||
Https.axiosGet(Https.httpUrls.productDetail+'?id='+id).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let store = getStoreInfo(rv.storeInfoList)
|
||||
formState.value = {
|
||||
pictureUrl:rv.pictureUrl,
|
||||
productLabel:rv.productLabelInfo.map((v:any)=>v.id),
|
||||
price:rv.price,
|
||||
productCategory:rv.attributeItemInfo.labelItem,
|
||||
productAttribute:rv.attributeItemInfo.attributeTypeList,
|
||||
storeList:store.storeList,
|
||||
storeNameList:store.storeNameList,
|
||||
assortmentList:rv.assortmentList,
|
||||
}
|
||||
productDetail.value = rv
|
||||
resolve(rv)
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
let showMatchDetail = (productId:any) =>{
|
||||
productDetailModalDom.value.openProductDetailModal(productId)
|
||||
}
|
||||
|
||||
let assortmentListShowMore = () =>{
|
||||
assortmentShowMore.value = !assortmentShowMore.value
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getProductDetail(route.query.id).then(rv=>{
|
||||
getProductLabelList()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
productDetailModalDom,
|
||||
columns,
|
||||
formState,
|
||||
assortmentShowMore,
|
||||
productCategoryList,
|
||||
productAttributeList,
|
||||
colorMap,
|
||||
productLabelList,
|
||||
showMatchDetail,
|
||||
assortmentListShowMore,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.productDetail_page{
|
||||
padding-left: 28px;
|
||||
height: 100%;
|
||||
|
||||
.productDetail_page_cotent{
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
background: #fff;
|
||||
|
||||
.productDetail_page_cotent_center{
|
||||
width: 1150px;
|
||||
padding: 20px 70px 30px 30px;
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.detail_item_block{
|
||||
margin-bottom: 15px;
|
||||
|
||||
.detail_icon{
|
||||
color: #80B8F8;
|
||||
font-size: 24px;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.detail_item_title_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-family: Adobe Heiti Std;
|
||||
font-weight: normal;
|
||||
color: #030303;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.detail_item_content{
|
||||
padding-left: 10px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.produce_label_item{
|
||||
padding: 0 11px;
|
||||
margin: 9px 27px 9px 0;
|
||||
height: 27px;
|
||||
line-height: 27px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.productCategory_block{
|
||||
width: 100%;
|
||||
line-height: 46px;
|
||||
padding-left: 20px;
|
||||
font-size: 18px;
|
||||
color: #030303;
|
||||
height: 46px;
|
||||
border: 1px solid #DFDFDF;
|
||||
}
|
||||
|
||||
.detail_item_sec_title{
|
||||
padding-left: 38px;
|
||||
font-size: 16px;
|
||||
font-family: Adobe Heiti Std;
|
||||
font-weight: normal;
|
||||
color: #030303;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sec_title_margin{
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.attribute_block{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding-top: 30px;
|
||||
|
||||
.attribute_block_item{
|
||||
padding: 0 22px;
|
||||
display: inline-block;
|
||||
margin-bottom: 30px;
|
||||
width: 50%;
|
||||
|
||||
.attribute_block_item_content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.attribute_item_title{
|
||||
width: 120px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.attribute_item_value{
|
||||
width: 200px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border: 1px solid #DFDFDF;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
padding-left: 18px;
|
||||
color: #030303;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.null_data_block{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
|
||||
.null_data_img{
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.product_match_block{
|
||||
|
||||
.product_match_item{
|
||||
background: #F7F8FC;
|
||||
border: 1px solid #DFDFDF;
|
||||
padding: 0 16px 26px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.product_match_item_header{
|
||||
height: 83px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
color: #030303;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.product_match_item_content{
|
||||
display: flex;
|
||||
|
||||
.match_item_img_content{
|
||||
margin-right: 20px;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.match_item_img_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 160px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
border: 1px solid #DFDFDF;
|
||||
cursor: pointer;
|
||||
|
||||
img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
|
||||
&:last-child{
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.product_price{
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
color: #030303;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.show_more_button{
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.form_width_100{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.product_master_diagram{
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
margin: 30px auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.product_master_img{
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
580
src/views/childView/productManage.vue
Normal file
@@ -0,0 +1,580 @@
|
||||
<template>
|
||||
<div class="product_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'Product Label'">
|
||||
<a-select v-model:value="productLabelIds" size="large" style="width:280px" optionFilterProp="label" :options="productLabelList" placeholder="Please select" allowClear show-search></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Category'">
|
||||
<a-select v-model:value="labelItem" size="large" style="width:280px" :options="labelItemList" placeholder="Please select" allowClear @change="labelItemChange" optionFilterProp="label" show-search></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'On Sale State'">
|
||||
<a-select v-model:value="onSaleState" size="large" style="width:280px" :options="onSaleStateList" placeholder="Please select" allowClear optionFilterProp="label" show-search></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Label attributes'">
|
||||
<a-select v-model:value="labelTypeMap" size="large" style="width:280px" :options="labelTypeList" placeholder="Please select" allowClear @change="labelTypeChange" optionFilterProp="label" show-search></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Add Time'"><a-range-picker v-model:value="addTime" size="large" :placeholder="['Start Time', 'End Time']" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD"/></filterComponent>
|
||||
<filterComponent :title="'Store'">
|
||||
<a-select v-model:value="storeIds" size="large" style="width:280px" :options="shopList" placeholder="Please select" allowClear optionFilterProp="label" show-search></a-select>
|
||||
</filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content" :style="{width: '255px'}">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Seach</a-button>
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="resetList()">Reset</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35 btn-margin-r-20" type="primary" size="large" @click="addProduct()" v-if="operatePermission.ADD">+Add product</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35" type="primary" size="large" @click="exportProduct()" :loading="exportLoading">Export</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'pictureUrl'">
|
||||
<img class="product_pic" :src="record.pictureUrl">
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'productLabel'">
|
||||
<a-popover placement="top" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)" arrowPointAtCenter>
|
||||
<template #content>
|
||||
<div class="record_pop_block">{{record.productLabel}}</div>
|
||||
</template>
|
||||
<div class="record_pop_content">{{record.productLabel}}</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'labelType'">
|
||||
<a-popover placement="top" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)">
|
||||
<template #content>
|
||||
<div class="record_pop_block">{{record.labelType}}</div>
|
||||
</template>
|
||||
<div class="record_pop_content">{{record.labelType}}</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'shop'" >
|
||||
<a-popover placement="top" :getPopupContainer="(triggerNode) => getPopupContainer(triggerNode)">
|
||||
<template #content>
|
||||
<div class="record_pop_block">{{record.shop}}</div>
|
||||
</template>
|
||||
<div class="record_pop_content">{{record.shop}}</div>
|
||||
</a-popover>
|
||||
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'onSaleState'">
|
||||
<a-switch checked-children="open" un-checked-children="close" v-model:checked="record.onSaleState" @change="change_on_sale(record)"/>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="editProduct(record)" v-if="operatePermission.EDIT">Edit</div>
|
||||
<div class="operate_item" @click="toProductDteail(record)" v-if="operatePermission.DETAIL">Detail</div>
|
||||
<div class="operate_item" @click="deleteProduct(record,index)" v-if="operatePermission.DELETE">Delete</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddProduct ref="addproductComponent" :productLabelList="productLabelList" @confirmSubmitAdd="confirmSubmitAdd"></AddProduct>
|
||||
<EditProduct ref="editProductComponent" :productLabelList="productLabelList" @refreshList="getProductlist"></EditProduct>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef,onMounted,createVNode,computed } from "vue";
|
||||
import { useRouter,useRoute } from 'vue-router'
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import AddProduct from '@/component/productComponent/addProduct.vue'
|
||||
import EditProduct from '@/component/productComponent/editProduct.vue'
|
||||
import { Moment } from 'moment';
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
import { Https } from "@/tool/https";
|
||||
import { formatTime, startTime, endTime } from "@/tool/util"
|
||||
|
||||
type TableDataType = {
|
||||
key: string;
|
||||
name: string;
|
||||
age: number;
|
||||
createDate: string;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name:'productManage',
|
||||
components:{filterComponent,AddProduct,EditProduct},
|
||||
setup(){
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
let filter:any = reactive({
|
||||
productLabelIds:[],
|
||||
labelItem:[],
|
||||
onSaleState:[],
|
||||
labelTypeMap:[],
|
||||
addTime:ref<Moment[]>([]),
|
||||
storeIds:[],
|
||||
})
|
||||
let filterLabelTypeMap :any = ref({})
|
||||
let tableLoading = ref(false)
|
||||
let exportLoading = ref(false)
|
||||
let addproductComponent:any = ref(null)
|
||||
let editProductComponent:any = ref(null)
|
||||
let productLabelList:any = ref([])
|
||||
let labelItemList:any = ref([])
|
||||
let labelTypeList:any = ref([])
|
||||
let onSaleStateList:any = ref([{
|
||||
value:'1',label:'Open'
|
||||
},{
|
||||
value:'0',label:'Close'
|
||||
}])
|
||||
let shopList:any = ref([])
|
||||
const columns = computed(() => {
|
||||
const sorted = sortedInfo.value || {};
|
||||
return [
|
||||
{ title: 'Product Pic', align:'center', ellipsis: true, dataIndex: 'pictureUrl', key: 'pictureUrl' },
|
||||
{ title: 'Product Label', align:'center', dataIndex: 'productLabel', key: 'productLabel'},
|
||||
{ title: 'Category', align:'center', ellipsis: true, dataIndex: 'labelItem', key: 'labelItem'},
|
||||
{ title: 'Label attributes', align:'center', dataIndex: 'labelType', key: 'labelType'},
|
||||
{ title: 'Shop', align:'center', dataIndex: 'shop', key: 'shop' },
|
||||
{ title: 'Inventory', align:'center', ellipsis: true, dataIndex: 'total', key: 'total', sorter:()=>{},
|
||||
sortOrder: sorted.columnKey === 'total' && sorted.order,
|
||||
},
|
||||
{ title: 'price', align:'center', ellipsis: true, dataIndex: 'price', key: 'price',customRender:(record:any)=>{
|
||||
let newPrrice = '$' + record.text
|
||||
return newPrrice },
|
||||
sorter:()=>{},
|
||||
sortOrder: sorted.columnKey === 'price' && sorted.order,
|
||||
},
|
||||
{ title: 'Add Time', align:'center', ellipsis: true, dataIndex: 'createDate', key: 'createDate',customRender:(record:any)=>{
|
||||
let time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
||||
return time },
|
||||
sorter:()=>{},
|
||||
sortOrder: sorted.columnKey === 'createDate' && sorted.order,
|
||||
},
|
||||
{ title: 'On Sale State', align:'center', ellipsis: true, dataIndex: 'onSaleState', key: 'onSaleState' },
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
]
|
||||
})
|
||||
let collectionList = ref([])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(1)
|
||||
let editProductTitle = ref('Add Product')
|
||||
let formState = ref({
|
||||
productId: '',
|
||||
productName: '',
|
||||
productType:'',
|
||||
remarks:'',
|
||||
});
|
||||
let columnSortListData:any = []; //排序参数
|
||||
let sortedInfo:any = ref()
|
||||
let typeMap:any = {
|
||||
dress:'Dress_Type',
|
||||
top:'Top_Type',
|
||||
bottom:'BTM_Type',
|
||||
outer:'Outer_Type',
|
||||
jumpsuit:'Jumpsuit_subtype'
|
||||
|
||||
}
|
||||
let rules = {
|
||||
productName: [
|
||||
{ required: true, message: 'Please input product name', trigger: 'blur' },
|
||||
],
|
||||
productType: [
|
||||
{ required: true, message: 'Please select product type', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
let operatePermission:any = reactive({
|
||||
DETAIL:false,
|
||||
ADD:false,
|
||||
EDIT:false,
|
||||
DELETE:false,
|
||||
})
|
||||
|
||||
let getPopupContainer = (triggerNode:any) =>{
|
||||
return triggerNode.parentNode || document.body
|
||||
}
|
||||
|
||||
let changePage = (e:any, filters: any, sorter: any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
if(sorter.order){
|
||||
columnSortListData = [{
|
||||
column:getSortField(sorter.field),
|
||||
style:sorter.order == 'ascend' ? 'asc' : 'desc'
|
||||
}]
|
||||
sortedInfo.value = sorter
|
||||
}else{
|
||||
columnSortListData = []
|
||||
sortedInfo.value = sorter
|
||||
}
|
||||
getProductlist()
|
||||
}
|
||||
|
||||
let getSortField = (field:string) =>{
|
||||
let fieldData:any = {
|
||||
createDate: 'create_date',
|
||||
price:'price',
|
||||
total:'total',
|
||||
}
|
||||
return fieldData[field]
|
||||
}
|
||||
|
||||
let closeProduct = () =>{
|
||||
formState.value = {
|
||||
productId: '',
|
||||
productName: '',
|
||||
productType:'',
|
||||
remarks:'',
|
||||
}
|
||||
}
|
||||
|
||||
let addProduct = () =>{
|
||||
addproductComponent.value.showModal()
|
||||
}
|
||||
|
||||
let change_on_sale = (record:any) =>{
|
||||
let data = {
|
||||
id:record.id,
|
||||
onSaleState:record.onSaleState ? 1 : 0
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.doOnSale, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let editProduct = (record:any) =>{
|
||||
editProductComponent.value.showModal({record:record,labelItemList:labelItemList.value})
|
||||
}
|
||||
|
||||
let toProductDteail = (record:any) =>{
|
||||
router.push({path:'/home/productDetail',query:{id:record.id}})
|
||||
}
|
||||
|
||||
let getProductlist = () =>{
|
||||
let data = {
|
||||
columnSortList:columnSortListData,
|
||||
productLabelIds:filter.productLabelIds && filter.productLabelIds.length ? [filter.productLabelIds] : [],
|
||||
labelItem:filter.labelItem && filter.labelItem.length ? filter.labelItem : '',
|
||||
onSaleState:filter.onSaleState && filter.onSaleState.length ? filter.onSaleState : '',
|
||||
labelTypeMap:filterLabelTypeMap.value,
|
||||
storeIds:filter.storeIds && filter.storeIds.length ? [filter.storeIds] : [],
|
||||
createDateStart:filter.addTime && filter.addTime.length ? startTime(filter.addTime[0]) : '',
|
||||
createDateEnd:filter.addTime && filter.addTime.length ? endTime(filter.addTime[1]) : '',
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.queryProductPage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
collectionList.value = rv.content.map((v:any)=>{
|
||||
return {
|
||||
...v,
|
||||
onSaleState:v.onSaleState == 1? true :false,
|
||||
productLabel:getProductLabel(v.productLabelInfo),
|
||||
labelItem:v.attributeItemInfo.labelItem,
|
||||
labelType:getLabelType(v.attributeItemInfo || []),
|
||||
shop:getStoreName(v.storeInfoList)
|
||||
}
|
||||
})
|
||||
getProductPromission()
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
).catch((err:any)=>{
|
||||
tableLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
let getProductLabel = (productLabelInfo:any) =>{
|
||||
let productLabel = productLabelInfo.map((v:any) => v.name).join(',')
|
||||
return productLabel
|
||||
}
|
||||
|
||||
let getLabelType = (attributeItemInfo:any) => {
|
||||
// let labelType = attributeItemInfo.attributeTypeList ? attributeItemInfo.attributeTypeList.filter((v:any) => v.labelType === typeMap[attributeItemInfo.labelItem]) :[]
|
||||
// labelType = labelType.map(((v:any)=>v.attributeValueList)).join(',')
|
||||
let labelType = attributeItemInfo.labelTypeValueList && attributeItemInfo.labelTypeValueList.length ? attributeItemInfo.labelTypeValueList.join(',') : ''
|
||||
return labelType
|
||||
}
|
||||
|
||||
let getStoreName = (storeInfoList:any) =>{
|
||||
let storeName = storeInfoList.map((v:any) => v.storeName).join(',')
|
||||
return storeName
|
||||
}
|
||||
|
||||
|
||||
let getProductLabelList = () =>{
|
||||
Https.axiosPost(Https.httpUrls.queryProductLabel).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
productLabelList.value = rv.map((v:any)=>{
|
||||
return {
|
||||
...v,
|
||||
value:v.id,
|
||||
label:v.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getStoreList = () =>{
|
||||
Https.axiosPost(Https.httpUrls.queryProductStore).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
shopList.value = rv.map((v:any)=>{
|
||||
return {
|
||||
...v,
|
||||
value:v.id,
|
||||
label:v.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getAttributeList = () =>{
|
||||
Https.axiosGet(Https.httpUrls.attributeQueryAll).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
labelItemList.value = rv.map((v:any)=>{
|
||||
return {
|
||||
...v,
|
||||
value:v.labelItem,
|
||||
label:v.labelItem
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let labelItemChange = (event:any,selectedOptions:any) =>{
|
||||
filter.labelTypeMap =[]
|
||||
filterLabelTypeMap.value = {}
|
||||
if(selectedOptions){
|
||||
labelTypeList.value = selectedOptions.labelTypeValueList.map((v:any)=>{
|
||||
return {
|
||||
value:v,
|
||||
label:v
|
||||
}
|
||||
})
|
||||
}else{
|
||||
labelTypeList.value = []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let labelTypeChange = (event:any,selectedOptions:any) => {
|
||||
let key = typeMap[filter.labelItem]
|
||||
if(selectedOptions){
|
||||
filterLabelTypeMap.value[key] = [event]
|
||||
}else{
|
||||
filterLabelTypeMap.value = {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getProductlist()
|
||||
}
|
||||
|
||||
let exportProduct = () =>{
|
||||
let data = {
|
||||
columnSortList:columnSortListData,
|
||||
productLabelIds:filter.productLabelIds && filter.productLabelIds.length ? [filter.productLabelIds] : [],
|
||||
labelItem:filter.labelItem && filter.labelItem.length ? filter.labelItem : '',
|
||||
onSaleState:filter.onSaleState && filter.onSaleState.length ? filter.onSaleState : '',
|
||||
labelTypeMap:filterLabelTypeMap.value,
|
||||
storeIds:filter.storeIds && filter.storeIds.length ? [filter.storeIds] : [],
|
||||
createDateStart:filter.addTime && filter.addTime.length ? startTime(filter.addTime[0]) : '',
|
||||
createDateEnd:filter.addTime && filter.addTime.length ? endTime(filter.addTime[1]) : '',
|
||||
}
|
||||
exportLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.exportProduct, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
exportLoading.value = false
|
||||
window.location.href= rv
|
||||
}
|
||||
}
|
||||
).catch((err:any)=>{
|
||||
exportLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.productLabelIds = []
|
||||
filter.labelItem = []
|
||||
filter.onSaleState = []
|
||||
filter.labelTypeMap = []
|
||||
filter.addTime = ref<Moment[]>([])
|
||||
filter.storeIds = []
|
||||
sortedInfo.value = null
|
||||
columnSortListData = []
|
||||
getProductlist()
|
||||
}
|
||||
|
||||
let deleteProduct = (data:any,index:any) =>{
|
||||
let confirmDelete = (data:any,index:any) =>{
|
||||
let newData = {
|
||||
id:data.id
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.productDelete, newData).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
message.success('Delete success')
|
||||
collectionList.value.splice(index,1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "Are you sure about the product? The product associated with the product will become invalid after deletion!",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
confirmDelete(data,index)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let getProductPromission = () =>{
|
||||
let menuList:any = sessionStorage.getItem('menuList')
|
||||
menuList= JSON.parse(menuList)
|
||||
for(let item of menuList){
|
||||
if(item.name === 'Product Management'){
|
||||
for(let operate of item.operationList){
|
||||
operatePermission[operate.code] = operate.select == 1 ? true :false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let confirmSubmitAdd = () =>{
|
||||
resetList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(route.query && route.query.type === 'add'){
|
||||
addProduct()
|
||||
}
|
||||
getProductlist()
|
||||
getProductLabelList()
|
||||
getStoreList()
|
||||
getAttributeList()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
addproductComponent,
|
||||
editProductComponent,
|
||||
tableLoading,
|
||||
exportLoading,
|
||||
productLabelList,
|
||||
labelItemList,
|
||||
labelTypeList,
|
||||
onSaleStateList,
|
||||
shopList,
|
||||
columns,
|
||||
collectionList,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
editProductTitle,
|
||||
formState,
|
||||
rules,
|
||||
operatePermission,
|
||||
getPopupContainer,
|
||||
changePage,
|
||||
closeProduct,
|
||||
addProduct,
|
||||
change_on_sale,
|
||||
editProduct,
|
||||
toProductDteail,
|
||||
labelItemChange,
|
||||
labelTypeChange,
|
||||
searchList,
|
||||
exportProduct,
|
||||
resetList,
|
||||
deleteProduct,
|
||||
confirmSubmitAdd,
|
||||
getProductlist
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.product_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.product_pic{
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.record_pop_block{
|
||||
max-width: 300px;
|
||||
// word-break: break-all;
|
||||
}
|
||||
|
||||
.record_pop_content{
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
292
src/views/childView/storeManage.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<div class="store_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'Store Name'"><a-input v-model:value="name" size="large" placeholder="Please input Store Name or Id" @keydown.enter="searchList"/></filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList">Seach</a-button>
|
||||
<a-button class="default_button" size="large" @click="resetList">Reset</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35" type="primary" size="large" @click="addStore()">+Add Store</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="editStore(record)">Edit</div>
|
||||
<div class="operate_item" @click="deleteStore(record,index)">Delete</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal class="edit_modal_component"
|
||||
:destroyOnClose="true"
|
||||
v-model:visible="editStoreModal"
|
||||
:footer="null"
|
||||
:title="editStoreTitle"
|
||||
width="560px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
@cancel="closeStore"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-form-item label="Store Name" name="name">
|
||||
<a-input v-model:value="formState.name" size="large" placeholder="Please input store name"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Store Adress" name="address">
|
||||
<a-input v-model:value="formState.address" size="large" placeholder="Please select"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="Remarks" name="remarks">
|
||||
<a-input v-model:value="formState.remarks" size="large" placeholder="Please input remarks"/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeStore">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="confirmSubmit">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef, onMounted,createVNode } from "vue";
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { formatTime } from "@/tool/util"
|
||||
import { Https } from "@/tool/https";
|
||||
import { useRoute } from 'vue-router'
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name:'storeManage',
|
||||
components:{filterComponent},
|
||||
setup(){
|
||||
const route = useRoute()
|
||||
let filter = reactive({
|
||||
name:''
|
||||
})
|
||||
let columns = reactive([
|
||||
{ title: 'Store Id', align:'center', ellipsis: true, dataIndex: 'id', key: 'id' },
|
||||
{ title: 'Store Name', align:'center', ellipsis: true, dataIndex: 'name', key: 'name',
|
||||
},
|
||||
{ title: 'Store Adress', align:'center', ellipsis: true, dataIndex: 'address', key: 'address' },
|
||||
{ title: 'Remarks', align:'center', ellipsis: true, dataIndex: 'remarks', key: 'remarks' },
|
||||
{ title: 'Add Time', align:'center', ellipsis: true, dataIndex: 'createDate', key: 'createDate',customRender:(record:any)=>{
|
||||
let time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
||||
return time },
|
||||
},
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
])
|
||||
let tableLoading = ref(false)
|
||||
let collectionList = ref([])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(0)
|
||||
let editStoreModal = ref(false)
|
||||
let editStoreTitle = ref('Add Store')
|
||||
let formRef = ref()
|
||||
let formState = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
address:'',
|
||||
remarks:'',
|
||||
});
|
||||
let rules = {
|
||||
name: [
|
||||
{ required: true, message: 'Please input store name', trigger: 'blur' },
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: 'Please input store address', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
let changePage = (e:any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
getStoreList()
|
||||
}
|
||||
|
||||
let closeStore = () =>{
|
||||
formState.value = {
|
||||
id: '',
|
||||
name: '',
|
||||
address:'',
|
||||
remarks:'',
|
||||
}
|
||||
editStoreModal.value = false
|
||||
}
|
||||
|
||||
let addStore = () =>{
|
||||
editStoreTitle.value = 'Add Store'
|
||||
editStoreModal.value = true
|
||||
}
|
||||
|
||||
let editStore = (data:any,) =>{
|
||||
editStoreModal.value = true
|
||||
editStoreTitle.value = 'Eidt Store'
|
||||
formState.value = {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
address:data.address,
|
||||
remarks:data.remarks,
|
||||
}
|
||||
}
|
||||
|
||||
let getStoreList = () =>{
|
||||
let data = {
|
||||
name : filter.name,
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.queryStorePage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
collectionList.value = rv.content
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getStoreList()
|
||||
}
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.name = ''
|
||||
getStoreList()
|
||||
}
|
||||
|
||||
let confirmSubmit = () =>{
|
||||
let data = {
|
||||
...formState.value,
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
formRef.value.validate().then(()=>{
|
||||
submit()
|
||||
})
|
||||
let submit = () =>{
|
||||
Https.axiosPost(Https.httpUrls.sotreSaveOrEdit, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = !formState.value.id ? 'Store added successfully' :'Edit store successfully'
|
||||
message.success(tip)
|
||||
resetList()
|
||||
closeStore()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let deleteStore = (data:any,index:any) => {
|
||||
let confirmDelete = (data:any,index:any) =>{
|
||||
let newData = {
|
||||
id:data.id
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.storeDelete, newData).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
message.success('Delete success')
|
||||
collectionList.value.splice(index,1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: 'Are you sure to delete the store?',
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Yes',
|
||||
cancelText: 'No',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
confirmDelete(data,index)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(route.query && route.query.type === 'add'){
|
||||
addStore()
|
||||
}
|
||||
getStoreList()
|
||||
})
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
columns,
|
||||
collectionList,
|
||||
currentPage,
|
||||
tableLoading,
|
||||
pageSize,
|
||||
total,
|
||||
editStoreModal,
|
||||
editStoreTitle,
|
||||
formRef,
|
||||
formState,
|
||||
rules,
|
||||
changePage,
|
||||
closeStore,
|
||||
addStore,
|
||||
editStore,
|
||||
searchList,
|
||||
resetList,
|
||||
confirmSubmit,
|
||||
deleteStore
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.store_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
469
src/views/childView/systemSetting/roleManage.vue
Normal file
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="role_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'Role Name or Id'"><a-input v-model:value="name" size="large" placeholder="Please input Role Name or Id" @keydown.enter="searchList()"/></filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Seach</a-button>
|
||||
<a-button class="default_button" size="large" @click="resetList()">Reset</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35" type="primary" size="large" @click="addRole()">+Add Role</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="editRole(record)">Edit</div>
|
||||
<div class="operate_item" @click="deleteRole(record,index)">Delete</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal class="edit_modal_component"
|
||||
v-model:visible="editRoleModal"
|
||||
:footer="null"
|
||||
:title="editRoleTitle"
|
||||
width="680px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
@cancel="closeRole"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-form-item label="Role Name" name="name">
|
||||
<a-input v-model:value="formState.name" size="large" placeholder="Please input role name"/>
|
||||
</a-form-item>
|
||||
<div>
|
||||
<div class="form_item_title required">Role Permission</div>
|
||||
<a-checkbox class="form_check_all" size="large" v-model:checked="permissionCheckAll" @change="perCheckAll">All</a-checkbox>
|
||||
<table class="permission_table">
|
||||
<tr v-for="(role) in rolePermission" :key="role.code">
|
||||
<td class="first_td">
|
||||
<div class="table_td_block"><a-checkbox size="large" v-model:checked="role.select" @change="checkLevelFirst(role)">{{role.resource}}</a-checkbox></div>
|
||||
</td>
|
||||
<td class="second_td">
|
||||
<div class="table_td_block" v-for="(menu) in role.level2ResourceList" :key="menu.code"><a-checkbox size="large" v-model:checked="menu.select" @change="checkLevelSec(role,menu)">{{menu.resource}}</a-checkbox></div>
|
||||
</td>
|
||||
<td class="third_td">
|
||||
<div class="table_td_block" v-for="(menu) in role.level2ResourceList" :key="menu.code">
|
||||
<div class="table_td_permission">
|
||||
<div v-for="(per) in menu.operationList" :key="per.code"><a-checkbox :disabled="!menu.select" size="large" v-model:checked="per.select">{{per.resource}}</a-checkbox></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeRole()">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="confirmSubmit()">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef,onMounted,createVNode } from "vue";
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { Https } from "@/tool/https";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name:'roleManage',
|
||||
components:{filterComponent},
|
||||
setup(){
|
||||
let filter = reactive({
|
||||
name:'',
|
||||
})
|
||||
let tableLoading = ref(false)
|
||||
let columns = reactive([
|
||||
{ title: 'Role Name', align:'center', ellipsis: true, dataIndex: 'name', key: 'name', width:250 },
|
||||
{ title: 'Role Permission', align:'center', ellipsis: true, dataIndex: 'permission', key: 'permission'},
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
width: 250,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
])
|
||||
let collectionList = ref([])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(1)
|
||||
let editRoleModal = ref(false)
|
||||
let editRoleTitle = ref('Add Role')
|
||||
let permissionCheckAll=ref(false)
|
||||
let rolePermission:any = ref([])
|
||||
let backupRolePermission:any = ref([])
|
||||
let formRef = ref();
|
||||
let formState = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
});
|
||||
let rules = {
|
||||
name: [
|
||||
{ required: true, message: 'Please input role name', trigger: 'blur' },
|
||||
],
|
||||
roleType: [
|
||||
{ required: true, message: '', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
let changePage = (e:any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
getRoleList()
|
||||
}
|
||||
|
||||
let closeRole = () =>{
|
||||
formState.value = {
|
||||
id: '',
|
||||
name: '',
|
||||
}
|
||||
permissionCheckAll.value = false
|
||||
rolePermission.value = JSON.parse(JSON.stringify(backupRolePermission.value))
|
||||
editRoleModal.value = false
|
||||
}
|
||||
|
||||
let addRole = () =>{
|
||||
editRoleTitle.value = 'Add Role'
|
||||
editRoleModal.value = true
|
||||
}
|
||||
|
||||
let editRole = (data:any,) =>{
|
||||
editRoleModal.value = true
|
||||
editRoleTitle.value = 'Eidt Role'
|
||||
formState.value = {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
}
|
||||
rolePermission.value = changeSelectBoolean(data.rolePermission.level1ResourceList)
|
||||
permissionCheckAll.value = data.rolePermission.allSelect
|
||||
}
|
||||
|
||||
let getRoleList = () =>{
|
||||
let data = {
|
||||
name : filter.name,
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.roleQueryRolePage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
collectionList.value = rv.content.map((v:any) =>{
|
||||
let data = {
|
||||
...v,
|
||||
permission:v.rolePermission.level1ResourceList.filter((v:any)=>v.select).map((per:any) => per.resource).join('、')
|
||||
}
|
||||
return data
|
||||
})
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getMenuListPer = () =>{
|
||||
Https.axiosGet(Https.httpUrls.roleQueryPermissionList).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
rolePermission.value = changeSelectBoolean(rv)
|
||||
backupRolePermission.value = changeSelectBoolean(rv)
|
||||
console.log(rolePermission.value);
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let changeSelectBoolean = (data:any) =>{
|
||||
let newData = JSON.parse(JSON.stringify(data))
|
||||
for(let first of newData){
|
||||
first.select = changeBooleanNumber(first.select)
|
||||
for(let sec of first.level2ResourceList){
|
||||
sec.select = changeBooleanNumber(sec.select)
|
||||
for(let third of sec.operationList){
|
||||
third.select = changeBooleanNumber(third.select)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newData
|
||||
}
|
||||
|
||||
//判断是布尔值则转为数字,数字则转布尔值
|
||||
let changeBooleanNumber = (data:any) => {
|
||||
let newData = data
|
||||
if(newData.constructor === Boolean){
|
||||
newData = newData ? 1 :0
|
||||
}else{
|
||||
newData = newData ? true :false
|
||||
}
|
||||
return newData
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getRoleList()
|
||||
}
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.name = ''
|
||||
getRoleList()
|
||||
}
|
||||
|
||||
|
||||
let deleteRole = (data:any,index:any) =>{
|
||||
let confirmDelete = (data:any,index:any) =>{
|
||||
let newData = {
|
||||
id:data.id
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.roleDelete, newData).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
message.success('Delete success')
|
||||
collectionList.value.splice(index,1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "Are you sure delete the role?",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
confirmDelete(data,index)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let perCheckAll = () =>{
|
||||
for(let first of rolePermission.value){
|
||||
first.select = permissionCheckAll.value
|
||||
for(let sec of first.level2ResourceList){
|
||||
sec.select = permissionCheckAll.value
|
||||
for(let third of sec.operationList){
|
||||
third.select = permissionCheckAll.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let checkLevelFirst = (firstDdata:any) =>{
|
||||
for(let sec of firstDdata.level2ResourceList){
|
||||
sec.select = firstDdata.select
|
||||
for(let third of sec.operationList){
|
||||
third.select = firstDdata.select
|
||||
}
|
||||
}
|
||||
checkIsAllPer()
|
||||
}
|
||||
|
||||
let checkLevelSec = (firstData:any, secondData:any) =>{
|
||||
for(let third of secondData.operationList){
|
||||
third.select = secondData.select
|
||||
}
|
||||
let status = false
|
||||
for(let item of firstData.level2ResourceList){
|
||||
if(item.select){
|
||||
status = true
|
||||
break
|
||||
}
|
||||
}
|
||||
firstData.select = status
|
||||
checkIsAllPer()
|
||||
}
|
||||
|
||||
//判断是否已经全部勾选
|
||||
let checkIsAllPer = () =>{
|
||||
let status = true
|
||||
for(let first of rolePermission.value){
|
||||
if(!first.select){
|
||||
status = false
|
||||
break
|
||||
}
|
||||
for(let sec of first.level2ResourceList){
|
||||
if(!sec.select){
|
||||
status = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if(!status){break}
|
||||
}
|
||||
permissionCheckAll.value = status
|
||||
}
|
||||
|
||||
let confirmSubmit = () =>{
|
||||
let data = {
|
||||
...formState.value,
|
||||
rolePermission:{
|
||||
allSelect:permissionCheckAll.value ? 1 : 0,
|
||||
level1ResourceList:changeSelectBoolean(rolePermission.value)
|
||||
},
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
formRef.value.validate().then(()=>{
|
||||
let status = false
|
||||
for(let first of rolePermission.value){
|
||||
if(first.select){
|
||||
status = true
|
||||
break
|
||||
}
|
||||
for(let sec of first.level2ResourceList){
|
||||
if(sec.select){
|
||||
status = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if(status){break}
|
||||
}
|
||||
if(!status){
|
||||
message.error('Please check role permission')
|
||||
return
|
||||
}
|
||||
submit()
|
||||
})
|
||||
let submit = () => {
|
||||
Https.axiosPost(Https.httpUrls.roleSaveOrEdit, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = !formState.value.id ? 'Role added successfully' :'Edit role successfully'
|
||||
message.success(tip)
|
||||
resetList()
|
||||
closeRole()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getRoleList()
|
||||
getMenuListPer()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
tableLoading,
|
||||
columns,
|
||||
collectionList,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
permissionCheckAll,
|
||||
rolePermission,
|
||||
editRoleModal,
|
||||
editRoleTitle,
|
||||
formRef,
|
||||
formState,
|
||||
rules,
|
||||
changePage,
|
||||
closeRole,
|
||||
addRole,
|
||||
editRole,
|
||||
searchList,
|
||||
resetList,
|
||||
deleteRole,
|
||||
perCheckAll,
|
||||
checkLevelFirst,
|
||||
checkLevelSec,
|
||||
confirmSubmit,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.role_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.role_pic{
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 50px;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form_check_all{
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.permission_table{
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
border-left:1px solid #DFDFDF;
|
||||
border-top:1px solid #DFDFDF;
|
||||
|
||||
td{
|
||||
border-bottom: 1px solid #DFDFDF;
|
||||
border-right: 1px solid #DFDFDF;
|
||||
|
||||
.table_td_block{
|
||||
padding: 16px 0 16px 20px;
|
||||
border-bottom: 1px solid #DFDFDF;
|
||||
|
||||
&:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.table_td_permission{
|
||||
display: flex;
|
||||
min-height: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
.first_td{
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.second_td{
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
513
src/views/childView/systemSetting/userManage.vue
Normal file
@@ -0,0 +1,513 @@
|
||||
<template>
|
||||
<div class="user_manage_page list_page">
|
||||
<div class="list_page_content">
|
||||
<div class="list_top_content">
|
||||
<div class="list_top_search_content">
|
||||
<div class="search_content_left">
|
||||
<filterComponent :title="'User Name'"><a-input v-model:value="userName" size="large" placeholder="Please input User Name" @keydown.enter="searchList()"/></filterComponent>
|
||||
<filterComponent :title="'User Phone'"><a-input v-model:value="userPhone" size="large" placeholder="Please input User Phone" @keydown.enter="searchList()" /></filterComponent>
|
||||
<filterComponent :title="'User Role'">
|
||||
<a-select v-model:value="roleId" size="large" style="width:280px" placeholder="Please select" :options="userRoleList" allowClear></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'User State'">
|
||||
<a-select v-model:value="state" size="large" style="width:280px" placeholder="Please select" :options="userStateList" allowClear></a-select>
|
||||
</filterComponent>
|
||||
<filterComponent :title="'Add Time'"><a-range-picker v-model:value="addTime" size="large" :placeholder="['Start Time', 'End Time']" format="YYYY-MM-DD" valueFormat="YYYY-MM-DD"/></filterComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_top_button_content">
|
||||
<a-button class="primary_button btn-margin-r-20" type="primary" size="large" @click="searchList()">Seach</a-button>
|
||||
<a-button class="default_button" size="large" @click="resetList()">Reset</a-button>
|
||||
<a-button class="primary_button btn-margin-t-35" type="primary" size="large" @click="addUser()">+Add User</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list_table_content">
|
||||
<a-table :columns="columns" :data-source="collectionList" @change="changePage" :loading="tableLoading"
|
||||
:pagination="{
|
||||
showSizeChanger:true,
|
||||
current: currentPage,
|
||||
pageSize:pageSize,
|
||||
total: total,
|
||||
showQuickJumper:true,
|
||||
bordered:false,
|
||||
pageSizeOptions:['10','20','50'],
|
||||
}">
|
||||
<template v-slot:bodyCell="{column,record, index}" >
|
||||
<template v-if="column.dataIndex === 'stateName'">
|
||||
<div :class="['state_name_block', record.state == 1 ? 'enable_state' :'disable_state']">
|
||||
<div class="state_round"></div>
|
||||
<div>{{record.stateName}}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'operation'">
|
||||
<div class="operate_list">
|
||||
<div class="operate_item" @click="editUser(record)">Edit</div>
|
||||
<div :class="['operate_item', record.state == 0 ? '' :'operate_disable_state']" @click="enableUser(record,index)">
|
||||
<div v-if="record.state == 0">Enable</div>
|
||||
<div v-else>Disable</div>
|
||||
</div>
|
||||
<div class="operate_item" @click="deleteUser(record,index)">Delete</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal class="edit_modal_component"
|
||||
:destroyOnClose="true"
|
||||
v-model:visible="editUserModal"
|
||||
:footer="null"
|
||||
:title="editUserTitle"
|
||||
width="680px"
|
||||
:maskClosable="false"
|
||||
:centered="true"
|
||||
@cancel="closeUser"
|
||||
>
|
||||
<a-form ref="formRef" :model="formState" :rules="rules" :layout="'vertical'" >
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Name" name="userName">
|
||||
<a-input v-model:value="formState.userName" size="large" placeholder="Please input user name"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Account" name="userAccount">
|
||||
<a-input v-model:value="formState.userAccount" size="large" placeholder="Please input user account"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Code" name="userPassword">
|
||||
<a-input v-model:value="formState.userPassword" size="large" placeholder="Please input user code" type="password"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Phone" name="userPhone">
|
||||
<a-input v-model:value="formState.userPhone" size="large" placeholder="Please input user phone"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="App User" name="appUser">
|
||||
<a-select v-model:value="formState.appUser" size="large" :options="appUserList" placeholder="Please select"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="User Role" name="roleId">
|
||||
<a-select v-model:value="formState.roleId" size="large" :options="userRoleList" placeholder="Please select"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="Owner Store" name="storeIds">
|
||||
<a-select v-model:value="formState.storeIds" size="large" :options="ownerStoreList" mode="multiple" placeholder="Please select"></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="[16,16]">
|
||||
<a-col :span="24">
|
||||
<a-form-item label ="Remarks" name="remarks">
|
||||
<a-input v-model:value="formState.remarks" size="large" placeholder="Please input remarks"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<div class="modal_button_list">
|
||||
<a-button class="default_button btn-margin-r-20" size="large" @click="closeUser">Cancel</a-button>
|
||||
<a-button class="primary_button" type="primary" size="large" @click="confirmSubmit">Submit</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,ref,reactive,toRefs,UnwrapRef,onMounted,createVNode } from "vue";
|
||||
import filterComponent from '@/component/filterComponent.vue'
|
||||
import { Moment } from 'moment';
|
||||
import { message,Modal } from "ant-design-vue";
|
||||
import { Https } from "@/tool/https";
|
||||
import { WarningOutlined } from '@ant-design/icons-vue';
|
||||
import { encode, decode } from 'js-base64';
|
||||
import { useRoute } from 'vue-router'
|
||||
import { formatTime, startTime, endTime } from "@/tool/util"
|
||||
|
||||
export default defineComponent({
|
||||
name:'userManage',
|
||||
components:{filterComponent},
|
||||
setup(){
|
||||
const route = useRoute()
|
||||
let filter:any = reactive({
|
||||
userName:'',
|
||||
userPhone:'',
|
||||
roleId:[],
|
||||
state:[],
|
||||
addTime:ref<Moment[]>([]),
|
||||
})
|
||||
let tableLoading = ref(false)
|
||||
let userRoleList:any = ref([])
|
||||
let userStateList:any = ref([
|
||||
{value:'1',label:'Activated'},
|
||||
{value:'0',label:'Deactivated'},
|
||||
])
|
||||
let appUserList:any = ref([
|
||||
{value:1,label:'Yes'},
|
||||
{value:0,label:'No'},
|
||||
])
|
||||
let ownerStoreList:any = ref([])
|
||||
let columns = reactive([
|
||||
{ title: 'User Name', align:'center', ellipsis: true, dataIndex: 'userName', key: 'userName' },
|
||||
{ title: 'User Account', align:'center', ellipsis: true, dataIndex: 'userAccount', key: 'userAccount'},
|
||||
{ title: 'Add Role', align:'center', ellipsis: true, dataIndex: 'roleName', key: 'roleName' },
|
||||
{ title: 'User Phone', align:'center', ellipsis: true, dataIndex: 'userPhone', key: 'userPhone' },
|
||||
{ title: 'Owned Store', align:'center', ellipsis: true, dataIndex: 'storeName', key: 'storeName',customRender:(record:any)=>{
|
||||
let storeNameNew = record.text.join(',')
|
||||
return storeNameNew
|
||||
}
|
||||
},
|
||||
{ title: 'Add Time', align:'center', ellipsis: true, dataIndex: 'createDate', key: 'createDate' ,customRender:(record:any)=>{
|
||||
let time = formatTime(record.text / 1000, 'YYYY-MM-DD hh:mm:ss')
|
||||
return time
|
||||
},
|
||||
},
|
||||
{ title: 'Activated', align:'center', ellipsis: true, dataIndex: 'appUserName', key: 'appUserName' },
|
||||
{ title: 'User State', align:'center', ellipsis: true, dataIndex: 'stateName', key: 'stateName' },
|
||||
{
|
||||
title: 'Operations',
|
||||
key: 'operation',
|
||||
align:'center',
|
||||
fixed: 'right',
|
||||
width: 150,
|
||||
dataIndex:'operation',
|
||||
|
||||
},
|
||||
])
|
||||
let collectionList:any = ref([])
|
||||
let currentPage = ref(1)
|
||||
let pageSize = ref(10)
|
||||
let total = ref(1)
|
||||
let editUserModal = ref(false)
|
||||
let editUserTitle = ref('Add user')
|
||||
let formRef = ref();
|
||||
let formState:any = ref({
|
||||
id: '',
|
||||
userName: '',
|
||||
userAccount: '',
|
||||
userPassword:'',
|
||||
userPhone:'',
|
||||
appUser:null,
|
||||
roleId:null,
|
||||
storeIds:[],
|
||||
remarks:'',
|
||||
});
|
||||
let rules = {
|
||||
userName: [
|
||||
{ required: true, message: 'Please input user name', trigger: 'blur' },
|
||||
],
|
||||
userAccount: [
|
||||
{ required: true, message: 'Please input user account', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: 'Length should be 6 to 20', trigger: 'blur' },
|
||||
],
|
||||
userPassword: [
|
||||
{ required: true, message: 'Please input user code', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: 'Length should be 6 to 20', trigger: 'blur' },
|
||||
],
|
||||
userPhone: [
|
||||
{ required: true, message: 'Please input user phone', trigger: 'blur' },
|
||||
{ pattern: /^\d*$/, message: 'The format of the mobile phone number is incorrect', trigger: 'blur' },
|
||||
],
|
||||
appUser: [
|
||||
{ required: true, message: 'Please select app user', trigger: 'blur' },
|
||||
],
|
||||
userRole: [
|
||||
{ required: true, message: 'Please select user role', trigger: 'blur' },
|
||||
],
|
||||
ownerStore: [
|
||||
{ required: true, message: 'Please select owner store', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
let changePage = (e:any) =>{
|
||||
currentPage.value = e.current
|
||||
pageSize.value = e.pageSize
|
||||
getUserlist()
|
||||
}
|
||||
|
||||
let closeUser = () =>{
|
||||
formState.value = {
|
||||
id: '',
|
||||
userName: '',
|
||||
userAccount: '',
|
||||
userPassword:'',
|
||||
userPhone:'',
|
||||
appUser:null,
|
||||
roleId:null,
|
||||
storeIds:[],
|
||||
remarks:'',
|
||||
}
|
||||
editUserModal.value = false
|
||||
}
|
||||
|
||||
let addUser = () =>{
|
||||
editUserTitle.value = 'Add user'
|
||||
editUserModal.value = true
|
||||
}
|
||||
|
||||
let editUser = (data:any,) =>{
|
||||
editUserModal.value = true
|
||||
editUserTitle.value = 'Eidt user'
|
||||
formState.value = {
|
||||
id: data.id,
|
||||
userName: data.userName,
|
||||
userAccount:data.userAccount,
|
||||
userPassword:data.userPassword ? decode(data.userPassword) : '',
|
||||
userPhone:data.userPhone,
|
||||
appUser:data.appUser,
|
||||
roleId:String(data.roleId),
|
||||
storeIds:data.storeIds,
|
||||
remarks:data.remarks,
|
||||
}
|
||||
}
|
||||
|
||||
let getUserlist = () =>{
|
||||
let data = {
|
||||
userName : filter.userName,
|
||||
userPhone:filter.userPhone,
|
||||
state:filter.state && filter.state.length ? filter.state : '',
|
||||
roleId:filter.roleId && filter.roleId.length ? filter.roleId : '',
|
||||
createDateStart:filter.addTime && filter.addTime.length ? startTime(filter.addTime[0]) :'',
|
||||
createDateEnd:filter.addTime && filter.addTime.length ? endTime(filter.addTime[1]) :'',
|
||||
page:currentPage.value,
|
||||
size:pageSize.value
|
||||
}
|
||||
tableLoading.value = true
|
||||
Https.axiosPost(Https.httpUrls.accountQueryUserPage, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
tableLoading.value = false
|
||||
collectionList.value = rv.content
|
||||
total.value = rv.total
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let getRolelist = () =>{
|
||||
Https.axiosPost(Https.httpUrls.roleQueryAll, {}).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
userRoleList.value = rv.map((v:any)=>{
|
||||
let data = {
|
||||
...v,
|
||||
value:v.id,
|
||||
label:v.name
|
||||
}
|
||||
return data
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let searchList = () =>{
|
||||
currentPage.value = 1
|
||||
getUserlist()
|
||||
}
|
||||
|
||||
let resetList = () =>{
|
||||
currentPage.value = 1
|
||||
filter.userName = ''
|
||||
filter.userPhone = ''
|
||||
filter.roleId = []
|
||||
filter.state = []
|
||||
filter.addTime = ref<Moment[]>([])
|
||||
getUserlist()
|
||||
}
|
||||
|
||||
let confirmSubmit = () =>{
|
||||
let data = {
|
||||
...formState.value,
|
||||
timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
}
|
||||
data.userPassword = encode(data.userPassword)
|
||||
let submit = () =>{
|
||||
Https.axiosPost(Https.httpUrls.accountSaveOrEdit, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = !formState.value.id ? 'User added successfully' :'Edit user successfully'
|
||||
message.success(tip)
|
||||
resetList()
|
||||
closeUser()
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
formRef.value.validate().then(()=>{
|
||||
submit()
|
||||
})
|
||||
}
|
||||
|
||||
let enableUser = (record:any,index:any) =>{
|
||||
let data ={
|
||||
id:record.id,
|
||||
enable:record.state == 1 ? 0 : 1
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.accountEnable, data).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
let tip = record.state == 0? 'Enable successfully' :'Disable successfully'
|
||||
message.success(tip)
|
||||
collectionList.value[index].state = data.enable
|
||||
collectionList.value[index].stateName = data.enable == 1 ? 'Activated' :'Deactivated'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let deleteUser = (data:any,index:any) =>{
|
||||
let confirmDelete = (data:any,index:any) =>{
|
||||
let newData = {
|
||||
id:data.id
|
||||
}
|
||||
Https.axiosPost(Https.httpUrls.accountDelete, newData).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
message.success('Delete success')
|
||||
collectionList.value.splice(index,1)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: "Are you sure delete the user?",
|
||||
icon: createVNode(WarningOutlined),
|
||||
class:'confirm_style',
|
||||
okText: 'Ok',
|
||||
cancelText: 'Cancel',
|
||||
// centered:true,
|
||||
onOk() {
|
||||
confirmDelete(data,index)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let storeQueryAll = () =>{
|
||||
Https.axiosPost(Https.httpUrls.storeQueryAll,{}).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
ownerStoreList.value = rv.map((v:any)=>{
|
||||
let data = {
|
||||
...v,
|
||||
value:v.id,
|
||||
label:v.name
|
||||
}
|
||||
return data
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(route.query && route.query.type === 'add'){
|
||||
addUser()
|
||||
}
|
||||
getUserlist()
|
||||
storeQueryAll()
|
||||
getRolelist()
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
...toRefs(filter),
|
||||
tableLoading,
|
||||
userRoleList,
|
||||
userStateList,
|
||||
appUserList,
|
||||
ownerStoreList,
|
||||
columns,
|
||||
collectionList,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
editUserModal,
|
||||
editUserTitle,
|
||||
formRef,
|
||||
formState,
|
||||
rules,
|
||||
changePage,
|
||||
closeUser,
|
||||
addUser,
|
||||
editUser,
|
||||
searchList,
|
||||
resetList,
|
||||
confirmSubmit,
|
||||
enableUser,
|
||||
deleteUser
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.user_manage_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.user_pic{
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.state_name_block{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
|
||||
&.enable_state{
|
||||
color: #47E417;
|
||||
|
||||
.state_round{
|
||||
background: #47E417;
|
||||
}
|
||||
}
|
||||
|
||||
&.disable_state{
|
||||
color: #E41335;
|
||||
|
||||
.state_round{
|
||||
background: #E41335;
|
||||
}
|
||||
}
|
||||
|
||||
.state_round{
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.operate_list{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.operate_item{
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
font-weight: 400;
|
||||
color: #343579;
|
||||
cursor: pointer;
|
||||
|
||||
&.operate_disable_state{
|
||||
color: #E41335;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
265
src/views/childView/worktable.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<div class="worktable_page">
|
||||
<div class="worktable_top_info">
|
||||
<img class="worktable_logo" src="@/assets/images/worktable_logo.png">
|
||||
<div class="worktable_top_left">
|
||||
<div class="top_left_header">{{nowHours > 12 ? 'Afternoon':'Morning'}},{{userName}}.Have a nice day!</div>
|
||||
<div class="work_user_info"><span class="user_info_margin">{{roleName}}</span><span class="user_info_margin">|</span><span>{{storeAddress}}{{storeName}}</span></div>
|
||||
</div>
|
||||
<div class="work_statistical_data_list">
|
||||
<div class="work_statistical_data_item">
|
||||
<div class="work_statistical_data_title">Store(s)</div>
|
||||
<div class="work_statistical_data_num color_FF6D60">{{storeCount}}</div>
|
||||
</div>
|
||||
<div class="work_statistical_data_item">
|
||||
<div class="work_statistical_data_title">Product(s)</div>
|
||||
<div class="work_statistical_data_num color_0CB4B3">{{onSaleProductCount}}</div>
|
||||
</div>
|
||||
<div class="work_statistical_data_item">
|
||||
<div class="work_statistical_data_title">Order(s)</div>
|
||||
<div class="work_statistical_data_num color_FEAD75">{{orderSuccessCount}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="worktable_content">
|
||||
<div class="worktable_content_header">
|
||||
<span class="icon iconfont icon-a-gengduocaidangongneng"></span>
|
||||
<div>Quick access</div>
|
||||
</div>
|
||||
<div class="worktable_content_entry_list">
|
||||
<div class="worktable_content_entry_item" @click="turnToPage({path:'/home/productmanage',type:''})" v-if="buttonShow.PRODUCT_LIST">
|
||||
<img class="entry_bg" src="@/assets/images/green_bg.png">
|
||||
<div class="entry_name">Product List</div>
|
||||
</div>
|
||||
<div class="worktable_content_entry_item" @click="turnToPage({path:'/home/productmanage',type:'add'})" v-if="buttonShow.PRODUCT_LIST">
|
||||
<img class="entry_bg" src="@/assets/images/purple_bg.png">
|
||||
<div class="entry_name">Add Product</div>
|
||||
</div>
|
||||
<div class="worktable_content_entry_item" @click="turnToPage({path:'/home/storemanage',type:''})" v-if="buttonShow.STORE_LIST">
|
||||
<img class="entry_bg" src="@/assets/images/pink_bg.png">
|
||||
<div class="entry_name">Store List</div>
|
||||
</div>
|
||||
<div class="worktable_content_entry_item" @click="turnToPage({path:'/home/storemanage',type:'add'})" v-if="buttonShow.STORE_LIST">
|
||||
<img class="entry_bg" src="@/assets/images/orange_bg.png">
|
||||
<div class="entry_name">Add Store</div>
|
||||
</div>
|
||||
<div class="worktable_content_entry_item" @click="turnToPage({path:'/home/usermanage',type:'add'})" v-if="buttonShow.USER_MANAGER">
|
||||
<img class="entry_bg" src="@/assets/images/blue_bg.png">
|
||||
<div class="entry_name">Add User</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent,reactive,ref,toRefs,onMounted } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { useRouter,useRoute } from 'vue-router'
|
||||
import { Https } from "@/tool/https";
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name:'worktable',
|
||||
setup(){
|
||||
const router = useRouter()
|
||||
const workData = reactive({
|
||||
userName:'',
|
||||
roleName:'',
|
||||
storeAddress:'',
|
||||
storeName:'',
|
||||
storeCount:0,
|
||||
onSaleProductCount:0,
|
||||
orderSuccessCount:0,
|
||||
})
|
||||
let userInfo:any = ref({})
|
||||
let buttonShow:any = reactive({
|
||||
PRODUCT_LIST:false,
|
||||
STORE_LIST:false,
|
||||
USER_MANAGER:false,
|
||||
})
|
||||
let nowHours = ref(0)
|
||||
|
||||
const turnToPage = (data:any) =>{
|
||||
router.push({path:data.path,query:data.type?{type:data.type}:{}})
|
||||
}
|
||||
|
||||
const getWorkData = () =>{
|
||||
Https.axiosGet(Https.httpUrls.countWorkBench).then(
|
||||
(rv: any) => {
|
||||
if (rv) {
|
||||
workData.userName = rv.userName
|
||||
workData.roleName = rv.roleName
|
||||
workData.storeAddress = rv.storeAddress
|
||||
workData.storeName = rv.storeName
|
||||
workData.storeCount = rv.storeCount
|
||||
workData.onSaleProductCount = rv.onSaleProductCount
|
||||
workData.orderSuccessCount = rv.orderSuccessCount
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const getButtonRole = (menuList:any) =>{
|
||||
let keyList = ['PRODUCT_LIST',"STORE_LIST","USER_MANAGER"]
|
||||
for(let item of menuList){
|
||||
if(!item.children){
|
||||
if(keyList.includes(item.code)){
|
||||
buttonShow[item.code] = item.isShow
|
||||
}
|
||||
}else{
|
||||
getButtonRole(item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let menuList:any = sessionStorage.getItem('menuList')
|
||||
menuList = JSON.parse(menuList)
|
||||
let date = new Date()
|
||||
nowHours.value = date.getHours()
|
||||
getButtonRole(menuList)
|
||||
getWorkData()
|
||||
})
|
||||
|
||||
|
||||
|
||||
return {
|
||||
turnToPage,
|
||||
...toRefs(workData),
|
||||
userInfo,
|
||||
buttonShow,
|
||||
nowHours,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.worktable_page{
|
||||
padding-left: 28px;
|
||||
|
||||
.worktable_top_info{
|
||||
background: #fff;
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-left: 117px;
|
||||
position: relative;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.worktable_logo{
|
||||
width: 100px;
|
||||
height: 96px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.top_left_header{
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
color: #333333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.work_user_info{
|
||||
font-size: 16px;
|
||||
color: #A0A1A6;
|
||||
|
||||
.user_info_margin{
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.work_statistical_data_list{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.work_statistical_data_item{
|
||||
padding: 8px 40px 0;
|
||||
height: 60px;
|
||||
border-right:1px solid #E7E7E7;
|
||||
|
||||
:last-child{
|
||||
border: none;
|
||||
}
|
||||
|
||||
.work_statistical_data_title{
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: #333333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.work_statistical_data_num{
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.color_FF6D60{
|
||||
color: #FF6D60;
|
||||
}
|
||||
.color_0CB4B3{
|
||||
color: #0CB4B3;
|
||||
}
|
||||
.color_FEAD75{
|
||||
color: #FEAD75;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.worktable_content{
|
||||
padding: 20px 25px 30px;
|
||||
background: #fff;
|
||||
|
||||
.worktable_content_header{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
margin-bottom: 40px;
|
||||
|
||||
.icon-a-gengduocaidangongneng{
|
||||
font-size: 24px;
|
||||
color: #80B8F8;
|
||||
margin-right: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.worktable_content_entry_list{
|
||||
display: flex;
|
||||
|
||||
|
||||
.worktable_content_entry_item{
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 114px;
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
|
||||
.entry_bg{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.entry_name{
|
||||
color: #fff;
|
||||
z-index: 2;
|
||||
font-size: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
44
tsconfig.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"useDefineForClassFields": true,
|
||||
"sourceMap": true,
|
||||
"allowJs": true, //编译时允许有js
|
||||
"baseUrl": ".",
|
||||
"outDir": "./",
|
||||
"types": [
|
||||
"webpack-env"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx",
|
||||
"src/**/*.js",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
45
vue.config.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const {defineConfig} = require('@vue/cli-service')
|
||||
const path = require('path');
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: ['vuetify'],
|
||||
lintOnSave:false,//关闭语法检查
|
||||
devServer: {
|
||||
// hot: true, // 热更新
|
||||
port: '8060',
|
||||
proxy: {
|
||||
"/api": {
|
||||
// target: 'http://18.167.251.121:5568', //后端接口地址
|
||||
target: 'http://192.168.1.9:5560/', //后端接口地址
|
||||
changeOrigin: true, //是否允许跨越
|
||||
}
|
||||
},
|
||||
},
|
||||
pluginOptions: {
|
||||
"style-resources-loader": {
|
||||
preProcessor: "less",
|
||||
patterns: [
|
||||
// 存放less变量文件的路径
|
||||
path.resolve(__dirname, "./src/assets/style/style.less")
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
css: {
|
||||
loaderOptions: {
|
||||
less: {
|
||||
lessOptions: {
|
||||
modifyVars: {
|
||||
'primary-color': '#ec6800'
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||