mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
Merge branch 'main' of github.com:maxkb-dev/maxkb
This commit is contained in:
commit
dc934c2f61
|
|
@ -1,3 +1,4 @@
|
|||
VITE_APP_NAME=ui
|
||||
VITE_BASE_PATH=/ui/
|
||||
VITE_APP_PORT=3000
|
||||
VITE_APP_PORT=3000
|
||||
VITE_APP_TITLE = '智能知识库'
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"element-plus": "^2.3.7",
|
||||
"element-plus": "^2.3.14",
|
||||
"lodash": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.6",
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
"prettier": "^3.0.0",
|
||||
"sass": "^1.66.1",
|
||||
"typescript": "~5.1.6",
|
||||
"unplugin-vue-define-options": "^1.3.18",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^0.34.2",
|
||||
"vue-tsc": "^1.8.8"
|
||||
|
|
@ -48,6 +49,24 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.15.tgz",
|
||||
|
|
@ -59,6 +78,20 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
|
|
@ -290,6 +323,28 @@
|
|||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.0.5.tgz",
|
||||
"integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rushstack/eslint-patch": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz",
|
||||
|
|
@ -332,6 +387,12 @@
|
|||
"@types/chai": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.2.tgz",
|
||||
"integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/jsdom": {
|
||||
"version": "21.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.2.tgz",
|
||||
|
|
@ -714,6 +775,31 @@
|
|||
"@volar/language-core": "1.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue-macros/common": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vue-macros/common/-/common-1.8.0.tgz",
|
||||
"integrity": "sha512-auDJJzE0z3uRe3867e0DsqcseKImktNf5ojCZgUKqiVxb2yTlwlgOVAYCgoep9oITqxkXQymSvFeKhedi8PhaA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.17",
|
||||
"@rollup/pluginutils": "^5.0.4",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"ast-kit": "^0.11.2",
|
||||
"local-pkg": "^0.4.3",
|
||||
"magic-string-ast": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.7.0 || ^3.2.25"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vue": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
|
||||
|
|
@ -1189,6 +1275,47 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-kit": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmmirror.com/ast-kit/-/ast-kit-0.11.2.tgz",
|
||||
"integrity": "sha512-Q0DjXK4ApbVoIf9GLyCo252tUH44iTnD/hiJ2TQaJeydYWSpKk0sI34+WMel8S9Wt5pbLgG02oJ+gkgX5DV3sQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.22.14",
|
||||
"@rollup/pluginutils": "^5.0.4",
|
||||
"pathe": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-walker-scope": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/ast-walker-scope/-/ast-walker-scope-0.5.0.tgz",
|
||||
"integrity": "sha512-NsyHMxBh4dmdEHjBo1/TBZvCKxffmZxRYhmclfu0PP6Aftre47jOHYaYaNqJcV0bxihxFXhDkzLHUwHc0ocd0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.22.7",
|
||||
"ast-kit": "^0.9.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ast-walker-scope/node_modules/ast-kit": {
|
||||
"version": "0.9.5",
|
||||
"resolved": "https://registry.npmmirror.com/ast-kit/-/ast-kit-0.9.5.tgz",
|
||||
"integrity": "sha512-kbL7ERlqjXubdDd+szuwdlQ1xUxEz9mCz1+m07ftNVStgwRb2RWw+U6oKo08PAvOishMxiqz1mlJyLl8yQx2Qg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"pathe": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
|
|
@ -1733,9 +1860,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/element-plus": {
|
||||
"version": "2.3.12",
|
||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.3.12.tgz",
|
||||
"integrity": "sha512-fAWpbKCyt+l1dsqSNPOs/F/dBN4Wp5CGAyxbiS5zqDwI4q3QPM+LxLU2h3GUHMIBtMGCvmsG98j5HPMkTKkvcA==",
|
||||
"version": "2.3.14",
|
||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.14.tgz",
|
||||
"integrity": "sha512-9yvxUaU4jXf2ZNPdmIxoj/f8BG8CDcGM6oHa9JIqxLjQlfY4bpzR1E5CjNimnOX3rxO93w1TQ0jTVt0RSxh9kA==",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^3.4.1",
|
||||
"@element-plus/icons-vue": "^2.0.6",
|
||||
|
|
@ -1901,7 +2028,7 @@
|
|||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-10.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
|
|
@ -3378,6 +3505,18 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string-ast": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/magic-string-ast/-/magic-string-ast-0.3.0.tgz",
|
||||
"integrity": "sha512-0shqecEPgdFpnI3AP90epXyxZy9g6CRZ+SZ7BcqFwYmtFEnZ1jpevcV5HoyVnlDS9gCnc1UIg3Rsvp3Ci7r8OA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"magic-string": "^0.30.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
|
|
@ -4945,6 +5084,15 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
@ -5152,6 +5300,32 @@
|
|||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.5.0.tgz",
|
||||
"integrity": "sha512-9ZdRwbh/4gcm1JTOkp9lAkIDrtOyOxgHmY7cjuwI8L/2RTikMcVG25GsZwNAgRuap3iDw2jeq7eoqtAsz5rW3A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.10.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"webpack-sources": "^3.2.3",
|
||||
"webpack-virtual-modules": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-define-options": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-vue-define-options/-/unplugin-vue-define-options-1.3.18.tgz",
|
||||
"integrity": "sha512-AaE10FCccfezT48yyYuUXdnTF9z8vQuXrlpNF5uQtq/AOD2pdkf38vnmJm8bJjpoqEkR6u72wNCJLZKXSUw+Og==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue-macros/common": "1.8.0",
|
||||
"ast-walker-scope": "^0.5.0",
|
||||
"unplugin": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/untildify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
|
||||
|
|
@ -5480,6 +5654,21 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-virtual-modules": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz",
|
||||
"integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
|
|
@ -5648,11 +5837,34 @@
|
|||
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-string-parser": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.15.tgz",
|
||||
"integrity": "sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
|
|
@ -5819,6 +6031,17 @@
|
|||
"resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
|
||||
"integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ=="
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.0.5.tgz",
|
||||
"integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"@rushstack/eslint-patch": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz",
|
||||
|
|
@ -5858,6 +6081,12 @@
|
|||
"@types/chai": "*"
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.2.tgz",
|
||||
"integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jsdom": {
|
||||
"version": "21.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.2.tgz",
|
||||
|
|
@ -6120,6 +6349,20 @@
|
|||
"@volar/language-core": "1.10.1"
|
||||
}
|
||||
},
|
||||
"@vue-macros/common": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vue-macros/common/-/common-1.8.0.tgz",
|
||||
"integrity": "sha512-auDJJzE0z3uRe3867e0DsqcseKImktNf5ojCZgUKqiVxb2yTlwlgOVAYCgoep9oITqxkXQymSvFeKhedi8PhaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.22.17",
|
||||
"@rollup/pluginutils": "^5.0.4",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"ast-kit": "^0.11.2",
|
||||
"local-pkg": "^0.4.3",
|
||||
"magic-string-ast": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
|
||||
|
|
@ -6466,6 +6709,40 @@
|
|||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"ast-kit": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmmirror.com/ast-kit/-/ast-kit-0.11.2.tgz",
|
||||
"integrity": "sha512-Q0DjXK4ApbVoIf9GLyCo252tUH44iTnD/hiJ2TQaJeydYWSpKk0sI34+WMel8S9Wt5pbLgG02oJ+gkgX5DV3sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.22.14",
|
||||
"@rollup/pluginutils": "^5.0.4",
|
||||
"pathe": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"ast-walker-scope": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/ast-walker-scope/-/ast-walker-scope-0.5.0.tgz",
|
||||
"integrity": "sha512-NsyHMxBh4dmdEHjBo1/TBZvCKxffmZxRYhmclfu0PP6Aftre47jOHYaYaNqJcV0bxihxFXhDkzLHUwHc0ocd0Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.22.7",
|
||||
"ast-kit": "^0.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"ast-kit": {
|
||||
"version": "0.9.5",
|
||||
"resolved": "https://registry.npmmirror.com/ast-kit/-/ast-kit-0.9.5.tgz",
|
||||
"integrity": "sha512-kbL7ERlqjXubdDd+szuwdlQ1xUxEz9mCz1+m07ftNVStgwRb2RWw+U6oKo08PAvOishMxiqz1mlJyLl8yQx2Qg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.22.7",
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"pathe": "^1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
|
|
@ -6869,9 +7146,9 @@
|
|||
}
|
||||
},
|
||||
"element-plus": {
|
||||
"version": "2.3.12",
|
||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.3.12.tgz",
|
||||
"integrity": "sha512-fAWpbKCyt+l1dsqSNPOs/F/dBN4Wp5CGAyxbiS5zqDwI4q3QPM+LxLU2h3GUHMIBtMGCvmsG98j5HPMkTKkvcA==",
|
||||
"version": "2.3.14",
|
||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.14.tgz",
|
||||
"integrity": "sha512-9yvxUaU4jXf2ZNPdmIxoj/f8BG8CDcGM6oHa9JIqxLjQlfY4bpzR1E5CjNimnOX3rxO93w1TQ0jTVt0RSxh9kA==",
|
||||
"requires": {
|
||||
"@ctrl/tinycolor": "^3.4.1",
|
||||
"@element-plus/icons-vue": "^2.0.6",
|
||||
|
|
@ -7006,7 +7283,7 @@
|
|||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-10.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
|
|
@ -8064,6 +8341,15 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
},
|
||||
"magic-string-ast": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/magic-string-ast/-/magic-string-ast-0.3.0.tgz",
|
||||
"integrity": "sha512-0shqecEPgdFpnI3AP90epXyxZy9g6CRZ+SZ7BcqFwYmtFEnZ1jpevcV5HoyVnlDS9gCnc1UIg3Rsvp3Ci7r8OA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"magic-string": "^0.30.2"
|
||||
}
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
|
|
@ -9164,6 +9450,12 @@
|
|||
"integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
@ -9315,6 +9607,29 @@
|
|||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"dev": true
|
||||
},
|
||||
"unplugin": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.5.0.tgz",
|
||||
"integrity": "sha512-9ZdRwbh/4gcm1JTOkp9lAkIDrtOyOxgHmY7cjuwI8L/2RTikMcVG25GsZwNAgRuap3iDw2jeq7eoqtAsz5rW3A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^8.10.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"webpack-sources": "^3.2.3",
|
||||
"webpack-virtual-modules": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"unplugin-vue-define-options": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-vue-define-options/-/unplugin-vue-define-options-1.3.18.tgz",
|
||||
"integrity": "sha512-AaE10FCccfezT48yyYuUXdnTF9z8vQuXrlpNF5uQtq/AOD2pdkf38vnmJm8bJjpoqEkR6u72wNCJLZKXSUw+Og==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vue-macros/common": "1.8.0",
|
||||
"ast-walker-scope": "^0.5.0",
|
||||
"unplugin": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"untildify": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
|
||||
|
|
@ -9509,6 +9824,18 @@
|
|||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"dev": true
|
||||
},
|
||||
"webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"dev": true
|
||||
},
|
||||
"webpack-virtual-modules": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz",
|
||||
"integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"element-plus": "^2.3.7",
|
||||
"element-plus": "^2.3.14",
|
||||
"lodash": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.6",
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
"prettier": "^3.0.0",
|
||||
"sass": "^1.66.1",
|
||||
"typescript": "~5.1.6",
|
||||
"unplugin-vue-define-options": "^1.3.18",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^0.34.2",
|
||||
"vue-tsc": "^1.8.8"
|
||||
|
|
|
|||
|
|
@ -5,65 +5,5 @@
|
|||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put } from '@/request/index'
|
||||
import type { TeamMember, TeamMemberRequest } from '@/api/type/team'
|
||||
// import type { Ref } from 'vue'
|
||||
|
||||
const prefix = '/team/member'
|
||||
|
||||
/**
|
||||
* 获取团队成员列表
|
||||
*/
|
||||
const getTeamMember: () => Promise<Result<TeamMember[]>> = () => {
|
||||
return get(`${prefix}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加成员
|
||||
* @param 参数 { "username_or_email": "string" }
|
||||
*/
|
||||
const postCreatTeamMember: (body: TeamMemberRequest) => Promise<Result<boolean>> = (body) => {
|
||||
return post(`${prefix}`, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除成员
|
||||
* @param 参数 member_id
|
||||
*/
|
||||
const delTeamMember: (member_id: String) => Promise<Result<boolean>> = (member_id) => {
|
||||
return del(`${prefix}/${member_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成员权限
|
||||
* @param 参数 member_id
|
||||
*/
|
||||
const getMemberPermissions: (member_id: String) => Promise<Result<any>> = (member_id) => {
|
||||
return get(`${prefix}/${member_id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取成员权限
|
||||
* @param 参数 member_id
|
||||
* @param 参数 {
|
||||
"team_member_permission_list": [
|
||||
{
|
||||
"target_id": "string",
|
||||
"type": "string",
|
||||
"operate": {
|
||||
"USE": true,
|
||||
"MANAGE": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const putMemberPermissions: (member_id: String, body: any) => Promise<Result<any>> = (
|
||||
member_id,
|
||||
body
|
||||
) => {
|
||||
return put(`${prefix}/${member_id}`, undefined, body)
|
||||
}
|
||||
|
||||
export default {
|
||||
getTeamMember,
|
||||
postCreatTeamMember,
|
||||
delTeamMember,
|
||||
getMemberPermissions,
|
||||
putMemberPermissions
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
interface TeamMember {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
team_id: string
|
||||
/**
|
||||
* 类型:type:manage 所有者;
|
||||
*/
|
||||
type: string
|
||||
user_id: string
|
||||
}
|
||||
|
||||
interface TeamMemberRequest {
|
||||
username_or_email: string
|
||||
}
|
||||
|
||||
export type { TeamMember, TeamMemberRequest }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Result } from './../../request/Result'
|
||||
import { Result } from '@/request/Result'
|
||||
import { get, post } from '@/request/index'
|
||||
import type {
|
||||
LoginRequest,
|
||||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
ResetPasswordRequest,
|
||||
User,
|
||||
ResetCurrentUserPasswordRequest
|
||||
} from './type'
|
||||
} from '@/api/type/user'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
/**
|
||||
|
|
@ -20,7 +20,7 @@ const login: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<s
|
|||
request,
|
||||
loading
|
||||
) => {
|
||||
return post('/user/login', undefined, request, loading)
|
||||
return post('/user/login', request, undefined, loading)
|
||||
}
|
||||
/**
|
||||
* 登出
|
||||
|
|
@ -41,7 +41,7 @@ const register: (request: RegisterRequest, loading?: Ref<boolean>) => Promise<Re
|
|||
request,
|
||||
loading
|
||||
) => {
|
||||
return post('/user/register', undefined, request, loading)
|
||||
return post('/user/register', request, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,7 +54,7 @@ const checkCode: (request: CheckCodeRequest, loading?: Ref<boolean>) => Promise<
|
|||
request,
|
||||
loading
|
||||
) => {
|
||||
return post('/user/check_code', undefined, request, loading)
|
||||
return post('/user/check_code', request, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,7 +68,7 @@ const sendEmit: (
|
|||
type: 'register' | 'reset_password',
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (email, type, loading) => {
|
||||
return post('/user/send_email', undefined, { email, type }, loading)
|
||||
return post('/user/send_email', { email, type }, undefined, loading)
|
||||
}
|
||||
/**
|
||||
* 发送邮件到当前用户
|
||||
|
|
@ -88,7 +88,7 @@ const resetCurrentUserPassword: (
|
|||
request: ResetCurrentUserPasswordRequest,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (request, loading) => {
|
||||
return post('/user/current/reset_password', undefined, request, loading)
|
||||
return post('/user/current/reset_password', request, undefined, loading)
|
||||
}
|
||||
/**
|
||||
* 获取用户基本信息
|
||||
|
|
@ -109,7 +109,7 @@ const resetPassword: (
|
|||
request: ResetPasswordRequest,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (request, loading) => {
|
||||
return post('/user/re_password', undefined, request, loading)
|
||||
return post('/user/re_password', request, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="content-container">
|
||||
<div class="content-container__header mb-10" v-if="slots.header || header">
|
||||
<slot name="header">
|
||||
<span>{{ header }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="content-container__main">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useSlots } from 'vue'
|
||||
defineOptions({ name: 'LayoutContent' })
|
||||
const slots = useSlots()
|
||||
defineProps({
|
||||
header: String
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scope>
|
||||
.content-container {
|
||||
transition: 0.3s;
|
||||
|
||||
.content-container__header {
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.content-container__main {
|
||||
background-color: var(--app-view-bg-color);
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
// overflow: auto;
|
||||
// height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,16 +1,33 @@
|
|||
<template>
|
||||
<component :is="Object.keys(iconMap).includes(iconName) ? iconMap[iconName].iconReader() : iconMap['404'].iconReader()">
|
||||
<component
|
||||
v-if="isIconfont"
|
||||
:is="
|
||||
Object.keys(iconMap).includes(iconName)
|
||||
? iconMap[iconName].iconReader()
|
||||
: iconMap['404'].iconReader()
|
||||
"
|
||||
class="el-icon app-icon"
|
||||
>
|
||||
</component>
|
||||
<el-icon v-else-if="iconName">
|
||||
<component :is="iconName" />
|
||||
</el-icon>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { iconMap } from "@/components/icons/index"
|
||||
withDefaults(defineProps<{
|
||||
iconName?: string;
|
||||
}>(), {
|
||||
iconName: '404'
|
||||
});
|
||||
|
||||
import { computed } from 'vue'
|
||||
import { iconMap } from '@/components/icons/index'
|
||||
defineOptions({ name: 'AppIcon' })
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
iconName?: string
|
||||
}>(),
|
||||
{
|
||||
iconName: '404'
|
||||
}
|
||||
)
|
||||
|
||||
const isIconfont = computed(() => props.iconName?.includes('app-'))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { h } from 'vue'
|
||||
export const iconMap: any = {
|
||||
'404': {
|
||||
'app-404': {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
|
|
@ -33,131 +33,102 @@ export const iconMap: any = {
|
|||
])
|
||||
}
|
||||
},
|
||||
home: {
|
||||
|
||||
'app-dataset': {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
style: 'height:14px;width:14px',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M362.666667 895.914667V639.850667c0-36.266667 33.109333-63.850667 72.533333-63.850667h153.6c39.253333 0 72.533333 27.648 72.533333 63.850667v256.064h59.904c61.269333 0 110.762667-47.957333 110.762667-106.730667V414.165333L557.162667 139.328a63.808 63.808 0 0 0-90.325334 0L192 414.165333v375.018667c0 58.88 49.386667 106.730667 110.762667 106.730667H362.666667z m42.666666 0h213.333334V639.850667c0-10.709333-12.586667-21.184-29.866667-21.184h-153.6c-17.408 0-29.866667 10.389333-29.866667 21.184v256.064z m469.333334-439.082667v332.352c0 82.645333-68.885333 149.397333-153.429334 149.397333H302.762667C218.133333 938.581333 149.333333 871.936 149.333333 789.184V456.832l-27.584 27.584a21.333333 21.333333 0 1 1-30.165333-30.165333L436.672 109.162667a106.474667 106.474667 0 0 1 150.656 0l345.088 345.088a21.333333 21.333333 0 0 1-30.165333 30.165333L874.666667 456.832z',
|
||||
fill: '#666666'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
app: {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
style: 'height:14px;width:14px',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M906.62890625 212.8203125C906.62890625 161.58007812 865.05664063 120.0078125 813.81640625 120.0078125H645.2421875C594.00195313 120.0078125 552.4296875 161.58007812 552.4296875 212.8203125v168.57421875c0 51.24023438 41.57226563 92.8125 92.8125 92.8125h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V212.8203125z m-56.25 173.93554688c0 17.05078125-28.125 31.20117188-45.26367188 31.20117187H640.3203125c-17.05078125 0-30.76171875-14.0625-30.76171875-31.20117188V207.81054687c0-17.05078125 13.7109375-30.67382813 30.76171875-30.67382812h178.9453125c17.05078125 0 31.02539063 13.62304688 31.02539063 30.67382813v178.94531249z m56.25 251.45507812c0-51.24023438-41.57226563-92.8125-92.8125-92.8125H645.2421875C594.00195313 545.3984375 552.4296875 586.97070313 552.4296875 638.2109375v168.57421875c0 51.24023438 41.57226563 92.8125 92.8125 92.8125h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V638.2109375z m-56.25 173.3203125c0 17.05078125-13.88671875 30.9375-30.9375 30.9375H640.49609375c-17.05078125 0-30.9375-13.88671875-30.9375-30.9375V632.5859375c0-17.05078125 13.88671875-30.9375 30.9375-30.9375h178.9453125c17.05078125 0 30.9375 13.88671875 30.9375 30.9375v178.9453125zM468.0546875 638.2109375c0-51.24023438-41.57226563-92.8125-92.8125-92.8125H206.66796875C155.42773437 545.3984375 113.85546875 586.97070313 113.85546875 638.2109375v168.57421875C113.85546875 858.02539063 155.42773437 899.59765625 206.66796875 899.59765625h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V638.2109375z m-57.12890625 173.3203125c0 17.05078125-13.88671875 30.9375-30.9375 30.9375H201.04296875c-17.05078125 0-30.9375-13.88671875-30.9375-30.9375V632.5859375c0-17.05078125 13.88671875-30.9375 30.9375-30.9375h178.9453125c17.05078125 0 30.9375 13.88671875 30.9375 30.9375v178.9453125z m57.12890625-598.7109375C468.0546875 161.58007812 426.48242187 120.0078125 375.2421875 120.0078125H206.66796875C155.42773437 120.0078125 113.85546875 161.58007812 113.85546875 212.8203125v168.57421875C113.85546875 432.63476562 155.42773437 474.20703125 206.66796875 474.20703125h168.57421875c51.24023438 0 92.8125-41.57226563 92.8125-92.8125V212.8203125z m-57.12890625 174.19921875c0 17.05078125-13.88671875 30.9375-30.9375 30.9375H201.04296875c-17.05078125 0-30.9375-13.88671875-30.9375-30.9375V208.07421875c0-17.05078125 13.88671875-30.9375 30.9375-30.9375h178.9453125c17.05078125 0 30.9375 13.88671875 30.9375 30.9375v178.9453125z',
|
||||
fill: '#768696'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
dataset: {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
style: 'height:14px;width:14px',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M859.5 193H446.939c-1.851-53.25-45.747-96-99.439-96h-183C109.635 97 65 141.635 65 196.5v632c0 54.864 44.635 99.5 99.5 99.5h695c54.864 0 99.5-44.636 99.5-99.5v-536c0-54.865-44.636-99.5-99.5-99.5z m-695-33h183c20.126 0 36.5 16.374 36.5 36.5v28c0 17.397 14.103 31.5 31.5 31.5h444c20.126 0 36.5 16.374 36.5 36.5V321H128V196.5c0-20.126 16.374-36.5 36.5-36.5z m695 705h-695c-20.126 0-36.5-16.374-36.5-36.5V384h768v444.5c0 20.126-16.374 36.5-36.5 36.5z',
|
||||
fill: '#070102'
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
setting: {
|
||||
'app-applicaiton': {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
style: 'height:14px;width:14px',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M512 328c-100.8 0-184 83.2-184 184S411.2 696 512 696 696 612.8 696 512 612.8 328 512 328z m0 320c-75.2 0-136-60.8-136-136s60.8-136 136-136 136 60.8 136 136-60.8 136-136 136z',
|
||||
fill: '#070102'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M857.6 572.8c-20.8-12.8-33.6-35.2-33.6-60.8s12.8-46.4 33.6-60.8c14.4-9.6 20.8-27.2 16-44.8-8-27.2-19.2-52.8-32-76.8-8-14.4-25.6-24-43.2-19.2-24 4.8-48-1.6-65.6-19.2-17.6-17.6-24-41.6-19.2-65.6 3.2-16-4.8-33.6-19.2-43.2-24-14.4-51.2-24-76.8-32-16-4.8-35.2 1.6-44.8 16-12.8 20.8-35.2 33.6-60.8 33.6s-46.4-12.8-60.8-33.6c-9.6-14.4-27.2-20.8-44.8-16-27.2 8-52.8 19.2-76.8 32-14.4 8-24 25.6-19.2 43.2 4.8 24-1.6 49.6-19.2 65.6-17.6 17.6-41.6 24-65.6 19.2-16-3.2-33.6 4.8-43.2 19.2-14.4 24-24 51.2-32 76.8-4.8 16 1.6 35.2 16 44.8 20.8 12.8 33.6 35.2 33.6 60.8s-12.8 46.4-33.6 60.8c-14.4 9.6-20.8 27.2-16 44.8 8 27.2 19.2 52.8 32 76.8 8 14.4 25.6 22.4 43.2 19.2 24-4.8 49.6 1.6 65.6 19.2 17.6 17.6 24 41.6 19.2 65.6-3.2 16 4.8 33.6 19.2 43.2 24 14.4 51.2 24 76.8 32 16 4.8 35.2-1.6 44.8-16 12.8-20.8 35.2-33.6 60.8-33.6s46.4 12.8 60.8 33.6c8 11.2 20.8 17.6 33.6 17.6 3.2 0 8 0 11.2-1.6 27.2-8 52.8-19.2 76.8-32 14.4-8 24-25.6 19.2-43.2-4.8-24 1.6-49.6 19.2-65.6 17.6-17.6 41.6-24 65.6-19.2 16 3.2 33.6-4.8 43.2-19.2 14.4-24 24-51.2 32-76.8 4.8-17.6-1.6-35.2-16-44.8z m-56 92.8c-38.4-6.4-76.8 6.4-104 33.6-27.2 27.2-40 65.6-33.6 104-17.6 9.6-36.8 17.6-56 24-22.4-30.4-57.6-49.6-97.6-49.6-38.4 0-73.6 17.6-97.6 49.6-19.2-6.4-38.4-14.4-56-24 6.4-38.4-6.4-76.8-33.6-104-27.2-27.2-65.6-40-104-33.6-9.6-17.6-17.6-36.8-24-56 30.4-22.4 49.6-57.6 49.6-97.6 0-38.4-17.6-73.6-49.6-97.6 6.4-19.2 14.4-38.4 24-56 38.4 6.4 76.8-6.4 104-33.6 27.2-27.2 40-65.6 33.6-104 17.6-9.6 36.8-17.6 56-24 22.4 30.4 57.6 49.6 97.6 49.6 38.4 0 73.6-17.6 97.6-49.6 19.2 6.4 38.4 14.4 56 24-6.4 38.4 6.4 76.8 33.6 104 27.2 27.2 65.6 40 104 33.6 9.6 17.6 17.6 36.8 24 56-30.4 22.4-49.6 57.6-49.6 97.6 0 38.4 17.6 73.6 49.6 97.6-6.4 19.2-14.4 38.4-24 56z',
|
||||
fill: '#070102'
|
||||
d: 'M951.901 244.015l-413.3-238.57a33.606 33.606 0 0 0-33.909 0L91.3 244.016c-10.426 6.12-16.99 17.221-16.99 29.346v477.184c0 12.149 6.447 23.343 16.99 29.37l413.3 238.662c5.213 2.933 11.101 4.515 16.99 4.515 5.794 0 11.775-1.582 16.988-4.515l413.3-238.661c10.427-6.121 16.99-17.222 16.99-29.37V273.36a33.908 33.908 0 0 0-16.966-29.346zM892.23 726.016l-370.618 213.97-370.642-213.97v-427.87L521.588 84.178l370.642 213.97v427.869z m8.797 5.073M285.207 348.393a34.095 34.095 0 0 0-46.336 12.567 33.908 33.908 0 0 0 12.474 46.36l235.94 136.215v269.498a33.745 33.745 0 0 0 33.884 33.885 33.745 33.745 0 0 0 33.886-33.885V543.977L791.9 407.227a34.025 34.025 0 0 0 12.451-46.36 34.025 34.025 0 0 0-46.336-12.474l-236.404 136.54-236.405-136.54z m0 0',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
password: {
|
||||
|
||||
'app-exit': {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
style: 'height:14px;width:14px',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M807.28626 393.9647l-59.057047 0 0-78.729086c0-130.193201-105.96438-236.099253-236.229213-236.099253S275.770787 185.042413 275.770787 315.235614l0 78.729086-59.057047 0c-32.616862 0-59.057047 26.425859-59.057047 59.025325L157.656693 885.838314c0 32.598442 26.441209 59.025325 59.057047 59.025325l590.57252 0c32.616862 0 59.057047-26.425859 59.057047-59.025325L866.343307 452.989001C866.343307 420.390559 839.903122 393.9647 807.28626 393.9647zM334.827835 315.235614c0-97.644901 79.473029-177.074951 177.172165-177.074951s177.172165 79.43005 177.172165 177.074951l0 78.729086L334.827835 393.9647 334.827835 315.235614zM807.28626 885.838314 216.71374 885.838314 216.71374 452.989001l590.57252 0L807.28626 885.838314z'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M512 777.635963c16.302291 0 29.528524-13.219069 29.528524-29.512151L541.528524 590.723969c0-16.293081-13.226233-29.512151-29.528524-29.512151s-29.528524 13.219069-29.528524 29.512151l0 157.399843C482.471476 764.416893 495.697709 777.635963 512 777.635963z'
|
||||
d: 'M874.666667 855.744a19.093333 19.093333 0 0 1-19.136 18.922667H168.469333A19.2 19.2 0 0 1 149.333333 855.530667V168.469333A19.2 19.2 0 0 1 168.469333 149.333333h687.061334c10.581333 0 19.136 8.533333 19.136 18.922667V320h42.666666V168.256A61.717333 61.717333 0 0 0 855.530667 106.666667H168.469333A61.866667 61.866667 0 0 0 106.666667 168.469333v687.061334A61.866667 61.866667 0 0 0 168.469333 917.333333h687.061334A61.76 61.76 0 0 0 917.333333 855.744V704h-42.666666v151.744zM851.84 533.333333l-131.797333 131.754667a21.141333 21.141333 0 0 0 0.213333 29.973333 21.141333 21.141333 0 0 0 29.973333 0.192l165.589334-165.589333a20.821333 20.821333 0 0 0 6.122666-14.976 21.44 21.44 0 0 0-6.314666-14.997333l-168.533334-168.533334a21.141333 21.141333 0 0 0-29.952-0.213333 21.141333 21.141333 0 0 0 0.213334 29.973333L847.296 490.666667H469.333333v42.666666h382.506667z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
exit: {
|
||||
'app-team': {
|
||||
iconReader: () => {
|
||||
return h('el-icon', { style: 'display:flex' }, [
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
style: 'height:14px;width:14px',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M874.666667 855.744a19.093333 19.093333 0 0 1-19.136 18.922667H168.469333A19.2 19.2 0 0 1 149.333333 855.530667V168.469333A19.2 19.2 0 0 1 168.469333 149.333333h687.061334c10.581333 0 19.136 8.533333 19.136 18.922667V320h42.666666V168.256A61.717333 61.717333 0 0 0 855.530667 106.666667H168.469333A61.866667 61.866667 0 0 0 106.666667 168.469333v687.061334A61.866667 61.866667 0 0 0 168.469333 917.333333h687.061334A61.76 61.76 0 0 0 917.333333 855.744V704h-42.666666v151.744zM851.84 533.333333l-131.797333 131.754667a21.141333 21.141333 0 0 0 0.213333 29.973333 21.141333 21.141333 0 0 0 29.973333 0.192l165.589334-165.589333a20.821333 20.821333 0 0 0 6.122666-14.976 21.44 21.44 0 0 0-6.314666-14.997333l-168.533334-168.533334a21.141333 21.141333 0 0 0-29.952-0.213333 21.141333 21.141333 0 0 0 0.213334 29.973333L847.296 490.666667H469.333333v42.666666h382.506667z'
|
||||
d: 'M 824.2 699.9 c -25.4 -25.4 -54.7 -45.7 -86.4 -60.4 C 783.1 602.8 812 546.8 812 484 c 0 -110.8 -92.4 -201.7 -203.2 -200 c -109.1 1.7 -197 90.6 -197 200 c 0 62.8 29 118.8 74.2 155.5 c -31.7 14.7 -60.9 34.9 -86.4 60.4 C 345 754.6 314 826.8 312 903.8 c -0.1 4.5 3.5 8.2 8 8.2 h 56 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 C 493.8 707.7 551.1 684 612 684 c 60.9 0 118.2 23.7 161.3 66.8 C 814.5 792 838 846.3 840 904.3 c 0.1 4.3 3.7 7.7 8 7.7 h 56 c 4.5 0 8.1 -3.7 8 -8.2 c -2 -77 -33 -149.2 -87.8 -203.9 Z M 612 612 c -34.2 0 -66.4 -13.3 -90.5 -37.5 c -24.5 -24.5 -37.9 -57.1 -37.5 -91.8 c 0.3 -32.8 13.4 -64.5 36.3 -88 c 24 -24.6 56.1 -38.3 90.4 -38.7 c 33.9 -0.3 66.8 12.9 91 36.6 c 24.8 24.3 38.4 56.8 38.4 91.4 c 0 34.2 -13.3 66.3 -37.5 90.5 c -24.2 24.2 -56.4 37.5 -90.6 37.5 Z M 361.5 510.4 c -0.9 -8.7 -1.4 -17.5 -1.4 -26.4 c 0 -15.9 1.5 -31.4 4.3 -46.5 c 0.7 -3.6 -1.2 -7.3 -4.5 -8.8 c -13.6 -6.1 -26.1 -14.5 -36.9 -25.1 c -25.8 -25.2 -39.7 -59.3 -38.7 -95.4 c 0.9 -32.1 13.8 -62.6 36.3 -85.6 c 24.7 -25.3 57.9 -39.1 93.2 -38.7 c 31.9 0.3 62.7 12.6 86 34.4 c 7.9 7.4 14.7 15.6 20.4 24.4 c 2 3.1 5.9 4.4 9.3 3.2 c 17.6 -6.1 36.2 -10.4 55.3 -12.4 c 5.6 -0.6 8.8 -6.6 6.3 -11.6 c -32.5 -64.3 -98.9 -108.7 -175.7 -109.9 c -110.9 -1.7 -203.3 89.2 -203.3 199.9 c 0 62.8 28.9 118.8 74.2 155.5 c -31.8 14.7 -61.1 35 -86.5 60.4 c -54.8 54.7 -85.8 126.9 -87.8 204 c -0.1 4.5 3.5 8.2 8 8.2 h 56.1 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 c 29.4 -29.4 65.4 -49.8 104.7 -59.7 c 3.9 -1 6.5 -4.7 6 -8.7 Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-add-users': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M 892 772 h -80 v -80 c 0 -4.4 -3.6 -8 -8 -8 h -48 c -4.4 0 -8 3.6 -8 8 v 80 h -80 c -4.4 0 -8 3.6 -8 8 v 48 c 0 4.4 3.6 8 8 8 h 80 v 80 c 0 4.4 3.6 8 8 8 h 48 c 4.4 0 8 -3.6 8 -8 v -80 h 80 c 4.4 0 8 -3.6 8 -8 v -48 c 0 -4.4 -3.6 -8 -8 -8 Z M 373.5 498.4 c -0.9 -8.7 -1.4 -17.5 -1.4 -26.4 c 0 -15.9 1.5 -31.4 4.3 -46.5 c 0.7 -3.6 -1.2 -7.3 -4.5 -8.8 c -13.6 -6.1 -26.1 -14.5 -36.9 -25.1 c -25.8 -25.2 -39.7 -59.3 -38.7 -95.4 c 0.9 -32.1 13.8 -62.6 36.3 -85.6 c 24.7 -25.3 57.9 -39.1 93.2 -38.7 c 31.9 0.3 62.7 12.6 86 34.4 c 7.9 7.4 14.7 15.6 20.4 24.4 c 2 3.1 5.9 4.4 9.3 3.2 c 17.6 -6.1 36.2 -10.4 55.3 -12.4 c 5.6 -0.6 8.8 -6.6 6.3 -11.6 c -32.5 -64.3 -98.9 -108.7 -175.7 -109.9 c -110.8 -1.7 -203.2 89.2 -203.2 200 c 0 62.8 28.9 118.8 74.2 155.5 c -31.8 14.7 -61.1 35 -86.5 60.4 c -54.8 54.7 -85.8 126.9 -87.8 204 c -0.1 4.5 3.5 8.2 8 8.2 h 56.1 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 c 29.4 -29.4 65.4 -49.8 104.7 -59.7 c 3.8 -1.1 6.4 -4.8 5.9 -8.8 Z M 824 472 c 0 -109.4 -87.9 -198.3 -196.9 -200 C 516.3 270.3 424 361.2 424 472 c 0 62.8 29 118.8 74.2 155.5 c -31.7 14.7 -60.9 34.9 -86.4 60.4 C 357 742.6 326 814.8 324 891.8 c -0.1 4.5 3.5 8.2 8 8.2 h 56 c 4.3 0 7.9 -3.4 8 -7.7 c 1.9 -58 25.4 -112.3 66.7 -153.5 C 505.8 695.7 563 672 624 672 c 110.4 0 200 -89.5 200 -200 Z m -109.5 90.5 C 690.3 586.7 658.2 600 624 600 s -66.3 -13.3 -90.5 -37.5 C 509 538 495.7 505.4 496 470.7 c 0.3 -32.8 13.4 -64.5 36.3 -88 c 24 -24.6 56.1 -38.3 90.4 -38.7 c 33.9 -0.3 66.8 12.9 91 36.6 c 24.8 24.3 38.4 56.8 38.4 91.4 c -0.1 34.2 -13.4 66.3 -37.6 90.5 Z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { type App } from 'vue'
|
||||
import AppIcon from './icons/AppIcon.vue'
|
||||
import LoginLayout from './login-layout/index.vue'
|
||||
import LoginContainer from './login-container/index.vue'
|
||||
import LayoutContent from './content-container/LayoutContent.vue'
|
||||
import TagsInput from './tags-input/index.vue'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
app.component(AppIcon.name, AppIcon)
|
||||
app.component(LoginLayout.name, LoginLayout)
|
||||
app.component(LoginContainer.name, LoginContainer)
|
||||
app.component(LayoutContent.name, LayoutContent)
|
||||
app.component(TagsInput.name, TagsInput)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<div class="login-warp">
|
||||
<div class="login-container">
|
||||
<el-row class="container">
|
||||
<el-col :span="14" class="left-container">
|
||||
<div class="login-image"></div>
|
||||
</el-col>
|
||||
<el-col :span="10" class="right-container">
|
||||
<slot></slot>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.login-warp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.right-container {
|
||||
display: flex;
|
||||
margin-top: 20vh;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.left-container {
|
||||
.login-image {
|
||||
background-image: url('@/assets/login.png');
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
<template >
|
||||
<el-dialog v-model="resetPasswordDialog" title="修改密码">
|
||||
<el-form class="reset-password-form" ref="resetPasswordFormRef" :model="resetPasswordForm" :rules="rules">
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input type="password" size="large" class="input-item" v-model="resetPasswordForm.password"
|
||||
placeholder="请输入密码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="re_password">
|
||||
<el-input type="password" size="large" class="input-item" v-model="resetPasswordForm.re_password"
|
||||
placeholder="请输入确认密码">
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input size="large" class="input-item" :disabled="true" v-bind:modelValue="userStore.userInfo?.email"
|
||||
@change="() => { }" placeholder="请输入邮箱">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input size="large" class="code-input" v-model="resetPasswordForm.code" placeholder="请输入验证码">
|
||||
<template #prepend>
|
||||
<el-button :icon="Key" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button size="large" class="send-email-button" @click="sendEmail" :loading="loading">获取验证码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="resetPassword">
|
||||
修改密码
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import type { ResetCurrentUserPasswordRequest } from "@/api/user/type";
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from "element-plus"
|
||||
import UserApi from "@/api/user"
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { Lock, UserFilled, Key } from '@element-plus/icons-vue'
|
||||
import { useRouter } from "vue-router"
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore()
|
||||
|
||||
const resetPasswordDialog = ref<boolean>(false);
|
||||
|
||||
const resetPasswordForm = ref<ResetCurrentUserPasswordRequest>({
|
||||
code: "",
|
||||
password: "",
|
||||
re_password: ""
|
||||
});
|
||||
|
||||
const resetPasswordFormRef = ref<FormInstance>();
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const rules = ref<FormRules<ResetCurrentUserPasswordRequest>>({
|
||||
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码' }
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
re_password: [{
|
||||
required: true,
|
||||
message: '请输入确认密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
|
||||
callback(new Error('密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}]
|
||||
|
||||
})
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
const sendEmail = () => {
|
||||
UserApi.sendEmailToCurrent(loading)
|
||||
.then(() => {
|
||||
ElMessage.success("发送验证码成功")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
resetPasswordForm.value = {
|
||||
code: "",
|
||||
password: "",
|
||||
re_password: ""
|
||||
}
|
||||
resetPasswordDialog.value = true
|
||||
}
|
||||
const resetPassword = () => {
|
||||
resetPasswordFormRef.value?.validate().then(() => {
|
||||
return UserApi.resetCurrentUserPassword(resetPasswordForm.value)
|
||||
}).then(() => {
|
||||
return userStore.logout()
|
||||
}).then(() => {
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
const close = () => { resetPasswordDialog.value = false }
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.code-input {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.send-email-button {
|
||||
margin-left: 12px;
|
||||
width: 158px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<template >
|
||||
<el-dropdown trigger="click" size="small" type="primary">
|
||||
<el-avatar> {{ firstUserName }} </el-avatar>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openResetPassword">
|
||||
<AppIcon iconName="password"></AppIcon><span style="margin-left:5px">修改密码</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<AppIcon iconName="exit"></AppIcon><span style="margin-left:5px">退出</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<ResetPassword ref="resetPasswordRef"></ResetPassword>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useRouter } from "vue-router";
|
||||
import AppIcon from "@/components/icons/AppIcon.vue"
|
||||
import ResetPassword from "@/components/layout/top-bar/components/avatar/ResetPasssword.vue"
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const firstUserName = computed(() => {
|
||||
return userStore.userInfo?.username?.substring(0, 1)
|
||||
})
|
||||
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>();
|
||||
|
||||
const openResetPassword = () => {
|
||||
resetPasswordRef.value?.open()
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
userStore.logout().then(() => {
|
||||
router.push({ name: "login" })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.el-avatar {
|
||||
--el-avatar-size: 30px;
|
||||
--el-avatar-bg-color: var(--app-base-action-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-dropdown-menu--small {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<template>
|
||||
<div class="menu-item-container" :class="isActive ? 'active' : ''" @click="router.push({ name: menu.name })">
|
||||
<div class="icon">
|
||||
<AppIcon :iconName="menu.meta ? menu.meta.icon as string : '404'"></AppIcon>
|
||||
</div>
|
||||
<div class="title">{{ menu.meta?.title }} </div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute, type RouteRecordRaw } from 'vue-router'
|
||||
import { computed } from "vue";
|
||||
import AppIcon from "@/components/icons/AppIcon.vue"
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const props = defineProps<{
|
||||
menu: RouteRecordRaw
|
||||
}>()
|
||||
|
||||
const isActive = computed(() => {
|
||||
return route.name == props.menu.name && route.path == props.menu.path
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.menu-item-container {
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--app-base-text-hover-bg-color);
|
||||
border-bottom: 3px solid var(--app-base-text-hover-color);
|
||||
height: calc(100% - 3px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<template >
|
||||
<div class="top-menu-container">
|
||||
<MenuItem :menu="menu" v-hasPermission="menu.meta?.permission" v-for="(menu, index) in topMenuList" :key="index">
|
||||
</MenuItem>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { getChildRouteListByPathAndName } from "@/router/index"
|
||||
import MenuItem from "@/components/layout/top-bar/components/top-menu/MenuItem.vue"
|
||||
const topMenuList = computed(() => {
|
||||
return getChildRouteListByPathAndName("/", "home")
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.top-menu-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
<template>
|
||||
<div class="top-bar-container">
|
||||
<div class="app-title-container">
|
||||
<div class="app-title-icon"></div>
|
||||
<div class="app-title-text">智能客服</div>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="app-top-menu-container">
|
||||
<TopMenu></TopMenu>
|
||||
</div>
|
||||
<div class="flex-auto"></div>
|
||||
<div class="avatar">
|
||||
<Avatar></Avatar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TopMenu from "@/components/layout/top-bar/components/top-menu/index.vue"
|
||||
import Avatar from "@/components/layout/top-bar/components/avatar/index.vue"
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.top-bar-container {
|
||||
border-bottom: 1px solid rgba(229, 229, 229, 1);
|
||||
height: calc(100% - 1px);
|
||||
background-color: var(--app-header-background-color, #fff);
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.app-title-container {
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
|
||||
.app-title-icon {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.app-title-text {
|
||||
color: var(--app-base-action-text-color);
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 60%;
|
||||
width: 1px;
|
||||
margin-left: 20px;
|
||||
background-color: rgba(229, 229, 229, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="login-form-container">
|
||||
<div class="login-title">
|
||||
<div class="title flex-center">
|
||||
<div class="logo"></div>
|
||||
<div>{{ title || defaultTitle }}</div>
|
||||
</div>
|
||||
<div class="sub-title" v-if="subTitle">{{ subTitle }}</div>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const defaultTitle = import.meta.env.VITE_APP_TITLE
|
||||
defineOptions({ name: 'LoginContainer' })
|
||||
defineProps({
|
||||
title: String,
|
||||
subTitle: String
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.login-form-container {
|
||||
width: 420px;
|
||||
|
||||
.login-title {
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #101010;
|
||||
height: 60px;
|
||||
.logo {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
text-align: center;
|
||||
color: #101010;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="login-warp flex-center">
|
||||
<div class="login-container w-full h-full">
|
||||
<el-row class="container w-full h-full">
|
||||
<el-col
|
||||
:xs="8"
|
||||
:sm="6"
|
||||
:md="14"
|
||||
:lg="14"
|
||||
:xl="14"
|
||||
class="left-container"
|
||||
v-if="screenWidth && screenWidth >= 990"
|
||||
>
|
||||
<div class="login-image"></div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="10" :lg="10" :xl="10" class="right-container flex-center">
|
||||
<slot></slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
defineOptions({ name: 'LoginLayout' })
|
||||
const screenWidth: Ref<number | null> = ref(null)
|
||||
onMounted(() => {
|
||||
screenWidth.value = document.body.clientWidth
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
screenWidth.value = document.body.clientWidth
|
||||
})()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.login-warp {
|
||||
height: 100vh;
|
||||
|
||||
.login-image {
|
||||
background: url(@/assets/login.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<!-- 外层div -->
|
||||
<div ref="InputTag" class="tags-input">
|
||||
<div class="tags-container" v-if="tagsList.length">
|
||||
<!-- 标签 -->
|
||||
<el-tag
|
||||
v-for="(item, index) in tagsList"
|
||||
:key="index"
|
||||
@close="removeTag(item)"
|
||||
closable
|
||||
class="mr-10"
|
||||
>{{ item }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<!-- 输入框 -->
|
||||
<el-input
|
||||
:validate-event="false"
|
||||
v-model="currentval"
|
||||
:placeholder="tagsList.length == 0 ? placeholder : ''"
|
||||
@keydown.enter="addTags"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
defineOptions({ name: 'TagsInput' })
|
||||
const props = defineProps({
|
||||
tags: {
|
||||
// 多个
|
||||
type: Array<String>,
|
||||
default: () => []
|
||||
},
|
||||
tag: {
|
||||
// 单个
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入'
|
||||
},
|
||||
limit: {
|
||||
// 最多生成标签数
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
reg: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:tags', 'update:tag'])
|
||||
const currentval = ref('')
|
||||
const tagsList = ref<String[]>([])
|
||||
|
||||
watch([tagsList, currentval], (val) => {
|
||||
if (val[0]?.length > 0) {
|
||||
emit('update:tags', val[0])
|
||||
} else if (val[1]) {
|
||||
emit('update:tag', val[1])
|
||||
}
|
||||
})
|
||||
|
||||
function addTags() {
|
||||
const val = currentval.value.trim()
|
||||
if (val) {
|
||||
tagsList.value.push(val)
|
||||
}
|
||||
currentval.value = ''
|
||||
}
|
||||
function removeTag(tag: String) {
|
||||
tagsList.value.splice(tagsList.value.indexOf(tag), 1)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tags-input {
|
||||
width: 100%;
|
||||
min-height: 70px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
:deep(.el-input__wrapper) {
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
resize: none;
|
||||
}
|
||||
.tags-container {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import type { App } from 'vue'
|
||||
import { hasPermission } from '@/common/permission'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
|
||||
const display = async (el: any, binding: any) => {
|
||||
const has = hasPermission(
|
||||
binding.value.permission ? binding.value.permission : binding.value,
|
||||
binding.value.compare ? binding.value.compare : 'OR'
|
||||
binding.value?.permission || binding.value,
|
||||
binding.value?.compare || 'OR'
|
||||
)
|
||||
if (!has) {
|
||||
el.style.display = 'none'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { TopBar, AppMain } from '../components'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-layout">
|
||||
<div class="app-header">
|
||||
<TopBar />
|
||||
</div>
|
||||
<div class="app-main">
|
||||
<AppMain />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.app-layout {
|
||||
background-color: var(--app-layout-bg-color);
|
||||
}
|
||||
|
||||
.app-main {
|
||||
height: calc(100vh - var(--app-header-height));
|
||||
padding: 0 !important;
|
||||
}
|
||||
.app-header {
|
||||
background-color: var(--app-header-bg-color);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition appear name="fade-transform" mode="out-in">
|
||||
<keep-alive :include="cachedViews">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onBeforeUpdate } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const cachedViews: any = ref([])
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
let isCached = route.meta?.cache
|
||||
let name = route.name
|
||||
if (isCached && name && !cachedViews.value.includes(name)) {
|
||||
cachedViews.value.push(name)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { default as Sidebar } from './sidebar/index.vue'
|
||||
export { default as AppMain } from './app-main/index.vue'
|
||||
export { default as TopBar } from './top-bar/index.vue'
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div v-if="!menu.meta || !menu.meta.hidden" class="sidebar-item">
|
||||
<el-menu-item ref="subMenu" :index="menu.path" popper-class="sidebar-popper">
|
||||
<template #title>
|
||||
<AppIcon v-if="menu.meta && menu.meta.icon" :iconName="menu.meta.icon" />
|
||||
<span v-if="menu.meta && menu.meta.title">{{ menu.meta.title }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type RouteRecordRaw } from 'vue-router'
|
||||
|
||||
defineProps<{
|
||||
menu: RouteRecordRaw
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sidebar-item {
|
||||
.el-menu-item {
|
||||
padding-left: 30px !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
color: var(--el-menu-active-color);
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<div class="sidebar">
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu :default-active="activeMenu">
|
||||
<sidebar-item
|
||||
:menu="menu"
|
||||
v-hasPermission="menu.meta?.permission"
|
||||
v-for="(menu, index) in subMenuList"
|
||||
:key="index"
|
||||
>
|
||||
</sidebar-item>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getChildRouteListByPathAndName } from '@/router/index'
|
||||
import SidebarItem from './SidebarItem.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const subMenuList = computed(() => {
|
||||
return getChildRouteListByPathAndName(route.path, route.name)
|
||||
})
|
||||
|
||||
const activeMenu = computed(() => {
|
||||
const { meta, path } = route
|
||||
if (meta.activeMenu) {
|
||||
return meta.activeMenu
|
||||
}
|
||||
return path
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.sidebar {
|
||||
.el-menu {
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<template>
|
||||
<el-dialog v-model="resetPasswordDialog" title="修改密码">
|
||||
<el-form
|
||||
class="reset-password-form"
|
||||
ref="resetPasswordFormRef"
|
||||
:model="resetPasswordForm"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.password"
|
||||
placeholder="请输入密码"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="re_password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.re_password"
|
||||
placeholder="请输入确认密码"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
:disabled="true"
|
||||
v-bind:modelValue="user.userInfo?.email"
|
||||
@change="() => {}"
|
||||
placeholder="请输入邮箱"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<div class="flex-between w-full">
|
||||
<el-input
|
||||
size="large"
|
||||
class="code-input"
|
||||
v-model="resetPasswordForm.code"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Key" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button
|
||||
size="large"
|
||||
class="send-email-button ml-10"
|
||||
@click="sendEmail"
|
||||
:loading="loading"
|
||||
>获取验证码</el-button
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button type="primary" @click="resetPassword"> 修改密码 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { ResetCurrentUserPasswordRequest } from '@/api/type/user'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import UserApi from '@/api/user'
|
||||
import useStore from '@/stores'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const { user } = useStore()
|
||||
|
||||
const resetPasswordDialog = ref<boolean>(false)
|
||||
|
||||
const resetPasswordForm = ref<ResetCurrentUserPasswordRequest>({
|
||||
code: '',
|
||||
password: '',
|
||||
re_password: ''
|
||||
})
|
||||
|
||||
const resetPasswordFormRef = ref<FormInstance>()
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const rules = ref<FormRules<ResetCurrentUserPasswordRequest>>({
|
||||
code: [{ required: true, message: '请输入验证码' }],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
re_password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入确认密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
|
||||
callback(new Error('密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
const sendEmail = () => {
|
||||
UserApi.sendEmailToCurrent(loading).then(() => {
|
||||
MsgSuccess('发送验证码成功')
|
||||
})
|
||||
}
|
||||
|
||||
const open = () => {
|
||||
resetPasswordForm.value = {
|
||||
code: '',
|
||||
password: '',
|
||||
re_password: ''
|
||||
}
|
||||
resetPasswordDialog.value = true
|
||||
}
|
||||
const resetPassword = () => {
|
||||
resetPasswordFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
return UserApi.resetCurrentUserPassword(resetPasswordForm.value)
|
||||
})
|
||||
.then(() => {
|
||||
return user.logout()
|
||||
})
|
||||
.then(() => {
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
const close = () => {
|
||||
resetPasswordDialog.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<el-dropdown trigger="click" type="primary">
|
||||
<el-avatar :size="30"> {{ firstUserName }} </el-avatar>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openResetPassword">
|
||||
<AppIcon iconName="Lock"></AppIcon><span style="margin-left: 5px">修改密码</span>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">
|
||||
<AppIcon iconName="app-exit"></AppIcon><span style="margin-left: 5px">退出</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<ResetPassword ref="resetPasswordRef"></ResetPassword>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import useStore from '@/stores';
|
||||
import { useRouter } from 'vue-router'
|
||||
import ResetPassword from './ResetPasssword.vue'
|
||||
const { user } = useStore();
|
||||
const router = useRouter()
|
||||
const firstUserName = computed(() => {
|
||||
return user.userInfo?.username?.substring(0, 1)
|
||||
})
|
||||
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
|
||||
|
||||
const openResetPassword = () => {
|
||||
resetPasswordRef.value?.open()
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
user.logout().then(() => {
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="top-bar-container flex-between border-b">
|
||||
<div class="flex-center h-full">
|
||||
<div class="app-title-container flex-center">
|
||||
<div class="app-title-icon"></div>
|
||||
<div class="app-title-text ml-10">{{ defaultTitle }}</div>
|
||||
</div>
|
||||
<el-divider direction="vertical" class="line" />
|
||||
<TopMenu></TopMenu>
|
||||
</div>
|
||||
<div class="avatar">
|
||||
<Avatar></Avatar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import TopMenu from './top-menu/index.vue'
|
||||
import Avatar from './avatar/index.vue'
|
||||
const defaultTitle = import.meta.env.VITE_APP_TITLE
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.top-bar-container {
|
||||
height: var(--app-header-height);
|
||||
box-sizing: border-box;
|
||||
padding: var(--app-header-padding);
|
||||
|
||||
.app-title-container {
|
||||
margin-right: 20px;
|
||||
.app-title-icon {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.app-title-text {
|
||||
color: var(--el-color-primary);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.line {
|
||||
height: 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div
|
||||
class="menu-item-container flex-center h-full"
|
||||
:class="isActive ? 'active' : ''"
|
||||
@click="router.push({ name: menu.name })"
|
||||
>
|
||||
<div class="icon">
|
||||
<AppIcon :iconName="menu.meta ? (menu.meta.icon as string) : '404'" />
|
||||
</div>
|
||||
<div class="title">{{ menu.meta?.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute, type RouteRecordRaw } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const props = defineProps<{
|
||||
menu: RouteRecordRaw
|
||||
}>()
|
||||
|
||||
const isActive = computed(() => {
|
||||
return route.name == props.menu.name && route.path == props.menu.path
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.menu-item-container {
|
||||
padding: 0 20px;
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
font-size: 15px;
|
||||
margin-right: 5px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: 600;
|
||||
color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-bottom: 3px solid var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div class="top-menu-container flex h-full">
|
||||
<MenuItem
|
||||
:menu="menu"
|
||||
v-hasPermission="menu.meta?.permission"
|
||||
v-for="(menu, index) in topMenuList"
|
||||
:key="index"
|
||||
>
|
||||
</MenuItem>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { getChildRouteListByPathAndName } from '@/router/index'
|
||||
import MenuItem from './MenuItem.vue'
|
||||
|
||||
const topMenuList = computed(() => {
|
||||
return getChildRouteListByPathAndName('/', 'home')
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.top-menu-container {
|
||||
align-items: center;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="main-layout h-full flex">
|
||||
<div class="sidebar-container border-r"><Sidebar /></div>
|
||||
<div class="view-container">
|
||||
<AppMain />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Sidebar, AppMain } from '../components'
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.sidebar-container {
|
||||
box-sizing: border-box;
|
||||
transition: width 0.28s;
|
||||
width: var(--sidebar-width);
|
||||
min-width: var(--sidebar-width);
|
||||
background-color: var(--sidebar-bg-color);
|
||||
}
|
||||
.view-container {
|
||||
width: 100%;
|
||||
padding: var(--app-view-padding);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,25 +1,28 @@
|
|||
import 'nprogress/nprogress.css'
|
||||
import '@/styles/index.scss'
|
||||
import ElementPlus from 'element-plus'
|
||||
import * as ElementPlusIcons from '@element-plus/icons-vue'
|
||||
import 'element-plus/dist/index.css'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import { createApp } from 'vue'
|
||||
import { store } from '@/stores'
|
||||
import theme from '@/theme'
|
||||
import directives from '@/directives'
|
||||
import App from './App.vue'
|
||||
import router from '@/router'
|
||||
import Components from '@/components'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(store)
|
||||
app.use(directives)
|
||||
const ElementPlusIconsVue: object = ElementPlusIcons
|
||||
// 将elementIcon放到全局
|
||||
app.config.globalProperties.$antIcons = ElementPlusIconsVue
|
||||
app.use(ElementPlus)
|
||||
|
||||
for (const [key, component] of Object.entries(ElementPlusIcons)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn
|
||||
})
|
||||
|
||||
app.use(theme)
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.use(Components)
|
||||
app.mount('#app')
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import axios, { type AxiosRequestConfig } from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { MsgError } from '@/utils/message'
|
||||
import type { NProgress } from 'nprogress'
|
||||
import type { Ref } from 'vue'
|
||||
import type { Result } from '@/request/Result'
|
||||
import { store } from '@/stores/index'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import useStore from '@/stores'
|
||||
import router from '@/router'
|
||||
|
||||
import { ref, type WritableComputedRef } from 'vue'
|
||||
|
|
@ -24,10 +23,10 @@ instance.interceptors.request.use(
|
|||
if (config.headers === undefined) {
|
||||
config.headers = {}
|
||||
}
|
||||
const userStore = useUserStore(store)
|
||||
const token = userStore.getToken()
|
||||
const { user } = useStore()
|
||||
const token = user.getToken()
|
||||
if (token) {
|
||||
config.headers['AUTHORIZATION'] = token
|
||||
config.headers['AUTHORIZATION'] = `${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
|
|
@ -41,7 +40,7 @@ instance.interceptors.response.use(
|
|||
(response: any) => {
|
||||
if (response.data) {
|
||||
if (response.data.code !== 200 && !(response.data instanceof Blob)) {
|
||||
ElMessage.error(response.data.message)
|
||||
MsgError(response.data.message)
|
||||
}
|
||||
}
|
||||
if (response.headers['content-type'] === 'application/octet-stream') {
|
||||
|
|
@ -51,7 +50,7 @@ instance.interceptors.response.use(
|
|||
},
|
||||
(err: any) => {
|
||||
if (err.code === 'ECONNABORTED') {
|
||||
ElMessage.error(err.message)
|
||||
MsgError(err.message)
|
||||
console.error(err)
|
||||
}
|
||||
if (err.response?.status === 401) {
|
||||
|
|
@ -59,7 +58,7 @@ instance.interceptors.response.use(
|
|||
}
|
||||
|
||||
if (err.response?.status === 403) {
|
||||
ElMessage.error(
|
||||
MsgError(
|
||||
err.response.data && err.response.data.message ? err.response.data.message : '没有权限访问'
|
||||
)
|
||||
}
|
||||
|
|
@ -130,10 +129,10 @@ export const get: (
|
|||
*/
|
||||
export const post: (
|
||||
url: string,
|
||||
params?: unknown,
|
||||
data?: unknown,
|
||||
params?: unknown,
|
||||
loading?: NProgress | Ref<boolean>
|
||||
) => Promise<Result<any> | any> = (url, params, data, loading) => {
|
||||
) => Promise<Result<any> | any> = (url, data, params, loading) => {
|
||||
return promise(request({ url: url, method: 'post', data, params }), loading)
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +150,7 @@ export const put: (
|
|||
data?: unknown,
|
||||
loading?: NProgress | Ref<boolean>
|
||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
||||
return promise(request({ url: url, method: 'put', data, params }), loading)
|
||||
return promise(request({ url: url, method: 'put', params, data }), loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -167,7 +166,7 @@ export const del: (
|
|||
data?: unknown,
|
||||
loading?: NProgress | Ref<boolean>
|
||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
||||
return promise(request({ url: url, method: 'delete', data, params }), loading)
|
||||
return promise(request({ url: url, method: 'delete', params, data }), loading)
|
||||
}
|
||||
|
||||
export const exportExcel: (
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { Role } from '@/common/permission/type'
|
||||
export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/views/home/index.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '/first',
|
||||
name: 'first',
|
||||
meta: { icon: 'app', title: '首页' },
|
||||
component: () => import('@/views/first/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/app',
|
||||
name: 'app',
|
||||
meta: { icon: 'app', title: '应用', permission: 'APPLICATION:READ' },
|
||||
component: () => import('@/views/app/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/dataset',
|
||||
name: 'dataset',
|
||||
meta: { icon: 'dataset', title: '数据集', permission: 'DATASET:READ' },
|
||||
component: () => import('@/views/dataset/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
meta: { icon: 'setting', title: '数据设置', permission: 'SETTING:READ' },
|
||||
component: () => import('@/views/setting/index.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: () => import('@/views/register/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/forgot_password',
|
||||
name: 'forgot_password',
|
||||
component: () => import('@/views/forgot-password/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/reset_password/:code/:email',
|
||||
name: 'reset_password',
|
||||
component: () => import('@/views/reset-password/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
name: '404',
|
||||
component: () => import('@/views/404/index.vue')
|
||||
}
|
||||
]
|
||||
|
|
@ -1,21 +1,19 @@
|
|||
import { hasPermission } from '@/common/permission/index'
|
||||
import { hasPermission } from '@/utils/permission/index'
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
type NavigationGuardNext,
|
||||
type RouteLocationNormalized,
|
||||
type RouteRecordRaw
|
||||
type RouteRecordRaw,
|
||||
type RouteRecordName
|
||||
} from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { store } from '@/stores'
|
||||
import { routes } from '@/router/data'
|
||||
import useStore from '@/stores';
|
||||
import { routes } from '@/router/routes'
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: routes
|
||||
})
|
||||
|
||||
// 解决刷新获取用户信息问题
|
||||
let userStore: any = null
|
||||
|
||||
// 路由前置拦截器
|
||||
router.beforeEach(
|
||||
|
|
@ -24,21 +22,19 @@ router.beforeEach(
|
|||
next()
|
||||
return
|
||||
}
|
||||
if (userStore === null) {
|
||||
userStore = useUserStore(store)
|
||||
}
|
||||
const { user } = useStore();
|
||||
const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password']
|
||||
|
||||
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
|
||||
const token = userStore.getToken()
|
||||
const token = user.getToken()
|
||||
if (!token) {
|
||||
next({
|
||||
path: '/login'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!userStore.userInfo) {
|
||||
await userStore.profile()
|
||||
if (!user.userInfo) {
|
||||
await user.profile()
|
||||
}
|
||||
}
|
||||
// 判断是否有菜单权限
|
||||
|
|
@ -51,14 +47,14 @@ router.beforeEach(
|
|||
}
|
||||
)
|
||||
|
||||
export const getChildRouteListByPathAndName = (path: string, name: string) => {
|
||||
export const getChildRouteListByPathAndName = (path: string, name?: RouteRecordName | null | undefined) => {
|
||||
return getChildRouteList(routes, path, name)
|
||||
}
|
||||
|
||||
export const getChildRouteList: (
|
||||
routeList: Array<RouteRecordRaw>,
|
||||
path: string,
|
||||
name: string
|
||||
name?: RouteRecordName | null | undefined
|
||||
) => Array<RouteRecordRaw> = (routeList, path, name) => {
|
||||
for (let index = 0; index < routeList.length; index++) {
|
||||
const route = routeList[index]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
const applicationRouter = {
|
||||
path: '/app',
|
||||
name: 'app',
|
||||
meta: { icon: 'app-applicaiton', title: '应用', permission: 'APPLICATION:READ' },
|
||||
component: () => import('@/views/app/index.vue')
|
||||
}
|
||||
|
||||
export default applicationRouter
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import Layout from '@/layout/main-layout/index.vue'
|
||||
const datasetRouter = {
|
||||
path: '/dataset',
|
||||
name: 'dataset',
|
||||
meta: { icon: 'app-dataset', title: '数据集', permission: 'DATASET:READ' },
|
||||
redirect: '/dataset',
|
||||
children: [
|
||||
{
|
||||
path: '/dataset',
|
||||
name: 'dataset',
|
||||
component: () => import('@/views/dataset/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/dataset/doc',
|
||||
name: 'DatasetDoc',
|
||||
meta: { icon: 'House', title: '文档', activeMenu: '/dataset' },
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
redirect: '/dataset/doc',
|
||||
children: [
|
||||
{
|
||||
path: '/dataset/doc',
|
||||
name: 'DatasetDoc',
|
||||
meta: { icon: 'House', title: '文档' },
|
||||
component: () => import('@/views/dataset/DatasetDoc.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default datasetRouter
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import Layout from '@/layout/main-layout/index.vue'
|
||||
const settingRouter = {
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
meta: { icon: 'Setting', title: '系统设置', permission: 'SETTING:READ' },
|
||||
redirect: '/setting',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
meta: { icon: 'app-team', title: '团队管理' },
|
||||
component: () => import('@/views/setting/index.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default settingRouter
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { Role } from '@/utils/permission/type'
|
||||
|
||||
const modules: any = import.meta.glob('./modules/*.ts', { eager: true })
|
||||
const rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)]
|
||||
|
||||
export const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/layout/app-layout/index.vue'),
|
||||
redirect: '/setting',
|
||||
children: [
|
||||
{
|
||||
path: '/first',
|
||||
name: 'first',
|
||||
meta: { icon: 'House', title: '首页' },
|
||||
component: () => import('@/views/first/index.vue')
|
||||
},
|
||||
...rolesRoutes
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'register',
|
||||
component: () => import('@/views/login/register/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/forgot_password',
|
||||
name: 'forgot_password',
|
||||
component: () => import('@/views/login/forgot-password/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/reset_password/:code/:email',
|
||||
name: 'reset_password',
|
||||
component: () => import('@/views/login/reset-password/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
name: '404',
|
||||
component: () => import('@/views/404/index.vue')
|
||||
}
|
||||
]
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import type { App } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
const store = createPinia();
|
||||
import { createPinia } from 'pinia'
|
||||
const store = createPinia()
|
||||
export { store }
|
||||
import useUserStore from './modules/user'
|
||||
|
||||
export function setupStore(app: App<Element>) {
|
||||
app.use(store);
|
||||
}
|
||||
const useStore = () => ({
|
||||
user: useUserStore()
|
||||
})
|
||||
|
||||
export { store };
|
||||
export default useStore
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { User } from '@/api/type/user'
|
||||
import UserApi from '@/api/user'
|
||||
|
||||
export interface appStateTypes {
|
||||
userInfo: User | null
|
||||
token: any
|
||||
}
|
||||
|
||||
const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
state: (): appStateTypes => ({
|
||||
userInfo: null,
|
||||
token: ''
|
||||
}),
|
||||
actions: {
|
||||
getToken(): String | null {
|
||||
if (this.token) {
|
||||
return this.token
|
||||
}
|
||||
return localStorage.getItem('token')
|
||||
},
|
||||
|
||||
getPermissions() {
|
||||
if (this.userInfo) {
|
||||
return this.userInfo?.permissions
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
getRole() {
|
||||
if (this.userInfo) {
|
||||
return this.userInfo?.role
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
|
||||
async profile() {
|
||||
return UserApi.profile().then((ok) => {
|
||||
this.userInfo = ok.data
|
||||
})
|
||||
},
|
||||
|
||||
async login(username: string, password: string) {
|
||||
return UserApi.login({ username, password }).then((ok) => {
|
||||
this.token = ok.data
|
||||
localStorage.setItem('token', ok.data)
|
||||
return this.profile()
|
||||
})
|
||||
},
|
||||
|
||||
async logout() {
|
||||
return UserApi.logout().then(() => {
|
||||
localStorage.removeItem('token')
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default useUserStore
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { User } from '@/api/user/type'
|
||||
import UserApi from '@/api/user'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const userInfo = ref<User>()
|
||||
// 用户认证token
|
||||
const token = ref<string>()
|
||||
|
||||
const getToken = () => {
|
||||
if (token.value) {
|
||||
return token.value
|
||||
}
|
||||
return localStorage.getItem('token')
|
||||
}
|
||||
|
||||
const getPermissions = () => {
|
||||
if (userInfo.value) {
|
||||
return userInfo.value.permissions
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const getRole = () => {
|
||||
if (userInfo.value) {
|
||||
return userInfo.value.role
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const profile = () => {
|
||||
return UserApi.profile().then((ok) => {
|
||||
userInfo.value = ok.data
|
||||
})
|
||||
}
|
||||
|
||||
const login = (username: string, password: string) => {
|
||||
return UserApi.login({ username, password }).then((ok) => {
|
||||
token.value = ok.data
|
||||
localStorage.setItem('token', ok.data)
|
||||
return profile()
|
||||
})
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
return UserApi.logout().then(() => {
|
||||
localStorage.removeItem('token')
|
||||
return true
|
||||
})
|
||||
}
|
||||
return { token, getToken, userInfo, profile, login, logout, getPermissions, getRole }
|
||||
})
|
||||
|
|
@ -1,21 +1,26 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Helvetica, PingFang SC, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
|
||||
'微软雅黑', Arial, sans-serif;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
height:100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:focus {
|
||||
|
|
@ -34,6 +39,16 @@ a:hover {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// 滚动条整体部分
|
||||
::-webkit-scrollbar {
|
||||
width: 6px; // 纵向滚动条宽度
|
||||
|
|
@ -52,177 +67,75 @@ a:hover {
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
// 创建表单
|
||||
.create-catalog-container {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.h-full {
|
||||
height: 100%;
|
||||
margin-top: -20px;
|
||||
.padding-top-30{
|
||||
padding-top:30px;
|
||||
}
|
||||
.padding-top-40{
|
||||
padding-top:40px;
|
||||
}
|
||||
// 表单外套
|
||||
.form-div{
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
min-width: 300px;
|
||||
form{
|
||||
.el-form-item {margin-bottom: 28px;}
|
||||
label{
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #1F2329;
|
||||
flex: none;
|
||||
order: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除按钮样式
|
||||
.delete-button-class{
|
||||
cursor: pointer;
|
||||
color: #646a73
|
||||
}
|
||||
|
||||
// 添加按钮样式
|
||||
.add-button-class{
|
||||
cursor: pointer;
|
||||
border: 0 solid;
|
||||
//width: 105px;
|
||||
height: 22px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
letter-spacing: -0.1px;
|
||||
color: #3370FF;
|
||||
.span-class{
|
||||
vertical-align:2px;
|
||||
color: #3370FF;
|
||||
padding-left: 5px
|
||||
}
|
||||
}
|
||||
|
||||
button{
|
||||
height: 32px;
|
||||
min-width: 80px
|
||||
}
|
||||
.save-btn{
|
||||
background-color: #3370FF;
|
||||
}
|
||||
.cancel-btn{
|
||||
|
||||
}
|
||||
|
||||
// 下方操作按钮区域
|
||||
.footer {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
padding: 24px 0px 0px 0px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin: 0px -50px 0px;
|
||||
|
||||
.footer-form {
|
||||
min-width: 400px;
|
||||
}
|
||||
.footer-center {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
margin: 0px 80px 0px;
|
||||
text-align: right;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
padding-left: 15px;
|
||||
font-size: smaller;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义弹出框样式
|
||||
.custom-dialog{
|
||||
//标题样式
|
||||
.el-dialog__header{
|
||||
padding: 24px !important;
|
||||
}
|
||||
//关闭按钮样式
|
||||
.el-dialog__headerbtn .el-dialog__close{
|
||||
height: auto !important;
|
||||
color: #646A73 !important;
|
||||
font-size: x-large !important;
|
||||
}
|
||||
.el-dialog__headerbtn .el-dialog__close:hover{
|
||||
background: rgba(31, 35, 41, 0.1) !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
//内容间距
|
||||
.el-dialog__body{
|
||||
padding: 0px 24px 0px 24px;
|
||||
}
|
||||
.el-dialog__footer{
|
||||
padding-bottom: 29px !important;
|
||||
}
|
||||
//下方按钮
|
||||
.footer-btn{
|
||||
button{
|
||||
height: 32px;
|
||||
min-width: 80px
|
||||
}
|
||||
.save-btn{
|
||||
background-color: #3370FF;
|
||||
}
|
||||
.cancel-btn{
|
||||
|
||||
}
|
||||
}
|
||||
.mt-10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.custom-radio-group.el-radio-group{
|
||||
border: 1px solid #BBBFC4;
|
||||
border-radius: 5px;
|
||||
height: 30px;
|
||||
label{
|
||||
border: 0px solid;
|
||||
padding: 2px 10px 2px 4px;
|
||||
}
|
||||
.el-radio-button__inner{
|
||||
padding: 4px;
|
||||
border: 0px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.el-radio-button{
|
||||
height: auto;
|
||||
}
|
||||
.el-radio-button is-active{
|
||||
height: auto;
|
||||
}
|
||||
.el-radio-button__original-radio:checked + .el-radio-button__inner{
|
||||
color: #3370FF;
|
||||
border: 0;
|
||||
box-shadow: 0 0 0 0;
|
||||
background: rgba(51, 112, 255, 0.1);
|
||||
}
|
||||
|
||||
.ml-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.mr-10 {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.mb-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.p-15 {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.border-b {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
.border-r {
|
||||
border-right: 1px solid var(--el-border-color);
|
||||
}
|
||||
.border-t {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
.border-b-light {
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.cursor{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main-calc-height {
|
||||
height: calc(100vh - 125px);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
// 抽屉样式整体修改
|
||||
.el-drawer{
|
||||
|
||||
.el-drawer__header{
|
||||
padding: 0;
|
||||
margin: 0 24px;
|
||||
height: 56px;
|
||||
border-bottom: 1px solid #D5D6D8;
|
||||
.el-drawer__title {
|
||||
color: #1f2329;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
}
|
||||
.el-drawer__body{
|
||||
--el-drawer-padding-primary:24px
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
:root {
|
||||
--el-menu-item-height: 45px;
|
||||
}
|
||||
.el-avatar {
|
||||
--el-avatar-bg-color: var(--el-color-primary);
|
||||
--el-avatar-size-small: 33px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.el-popper {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
|
@ -5,4 +13,32 @@
|
|||
.el-form {
|
||||
--el-form-inline-content-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.el-dialog {
|
||||
.dialog-sub-title {
|
||||
color: var(--el-text-color-regular);
|
||||
margin: 5px 0;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 15px var(--el-dialog-padding-primary) 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 抽屉样式整体修改
|
||||
.el-drawer {
|
||||
.el-drawer__header {
|
||||
padding: 0;
|
||||
margin: 0 24px;
|
||||
height: 56px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
.el-drawer__title {
|
||||
color: #1f2329;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
.el-drawer__body {
|
||||
--el-drawer-padding-primary: 24px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
@use "./variables/index.scss";
|
||||
@use "./app.scss";
|
||||
@use "./element-plus.scss";
|
||||
@use "./drawer.scss";
|
||||
|
||||
@import 'element-plus/dist/index.css';
|
||||
@import './variables.scss';
|
||||
@import './app.scss';
|
||||
@import './element-plus.scss';
|
||||
@import 'nprogress/nprogress.css'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
@mixin flex-row($justify: flex-start, $align: stretch) {
|
||||
display: flex;
|
||||
@if $justify != flex-start {
|
||||
justify-content: $justify;
|
||||
}
|
||||
@if $align != stretch {
|
||||
align-items: $align;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin variant($color, $background-color, $border-color) {
|
||||
color: $color;
|
||||
background-color: $background-color;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
:root {
|
||||
--el-color-primary: rgba(51, 112, 255, 1);
|
||||
--app-layout-bg-color: #f3f5f6;
|
||||
--app-base-text-color: rgba(31, 35, 41, 1);
|
||||
--app-view-padding: 15px;
|
||||
--app-view-bg-color: #ffffff;
|
||||
--hover-bg-color: #fafafa;
|
||||
/** header 组件 */
|
||||
--app-header-height: 56px;
|
||||
--app-header-padding: 0 20px;
|
||||
--app-header-bg-color: #ffffff;
|
||||
/** sidebar 组件 */
|
||||
--sidebar-bg-color: #ffffff;
|
||||
--sidebar-width: 198px;
|
||||
|
||||
--team-manage-left-width : 280px;
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
:root{
|
||||
--app-base-text-color:rgba(31, 35, 41, 1);
|
||||
--app-base-text-font-size:14px;
|
||||
--app-base-text-hover-color:rgba(51, 112, 255, 1);
|
||||
--app-base-text-hover-bg-color:rgba(51, 112, 255, 0.1);
|
||||
--app-base-action-text-color:var(--app-base-text-hover-color );
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
:root{
|
||||
--app-header-height: 56px;
|
||||
--app-header-padding: 0 20px;
|
||||
--app-header-bg-color: #252b3c;
|
||||
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@use "./header.scss";
|
||||
@use "./app.scss";
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
|
||||
export const MsgSuccess = (message: string) => {
|
||||
ElMessage.success({
|
||||
message: message,
|
||||
type: 'success',
|
||||
showClose: true,
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
|
||||
export const MsgInfo = (message: string) => {
|
||||
ElMessage.info({
|
||||
message: message,
|
||||
type: 'info',
|
||||
showClose: true,
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
|
||||
export const MsgWarning = (message: string) => {
|
||||
ElMessage.warning({
|
||||
message: message,
|
||||
type: 'warning',
|
||||
showClose: true,
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
|
||||
export const MsgError = (message: string) => {
|
||||
ElMessage.error({
|
||||
message: message,
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
|
||||
export const MsgConfirm = (message: string, options = {}) => {
|
||||
const defaultOptions: Object = {
|
||||
type: 'warning',
|
||||
...options
|
||||
}
|
||||
return ElMessageBox.confirm(message, '确认', defaultOptions)
|
||||
}
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
import { store } from '@/stores'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { Role, Permission, ComplexPermission } from '@/common/permission/type'
|
||||
import useStore from '@/stores';
|
||||
import { Role, Permission, ComplexPermission } from '@/utils/permission/type'
|
||||
/**
|
||||
* 是否包含当前权限
|
||||
* @param permission 当前权限
|
||||
* @returns True 包含 false 不包含
|
||||
*/
|
||||
const hasPermissionChild = (permission: Role | string | Permission | ComplexPermission) => {
|
||||
const userStore = useUserStore(store)
|
||||
const permissions = userStore.getPermissions()
|
||||
const role = userStore.getRole()
|
||||
const { user } = useStore();
|
||||
const permissions = user.getPermissions()
|
||||
const role = user.getRole()
|
||||
if (!permission) {
|
||||
return true
|
||||
}
|
||||
if (permission instanceof Role) {
|
||||
return role === permission.role
|
||||
}
|
||||
|
|
@ -24,6 +26,7 @@ const hasPermissionChild = (permission: Role | string | Permission | ComplexPerm
|
|||
if (typeof permission === 'string') {
|
||||
return permissions.includes(permission)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
/**
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<div>dataset 文档</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<template >
|
||||
<template>
|
||||
<div>
|
||||
dataset
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
<template >
|
||||
<div>
|
||||
<el-button v-hasPermission="'USER:DELETE'">用户删除权限</el-button>
|
||||
<el-button v-hasPermission="'USER:READ'">用户只读权限</el-button>
|
||||
<el-button v-hasPermission="new Role('USER')">普通用户角色</el-button>
|
||||
<el-button v-hasPermission="[new Role('ADMIN'), new Role('USER')]">普通用户或者管理员</el-button>
|
||||
<el-button
|
||||
v-hasPermission="{ permission: ['USER:READ', new Role('USER')], compare: 'AND' }">普通角色并且用户只读权限</el-button>
|
||||
<template>
|
||||
<div>
|
||||
<el-button v-hasPermission="'USER:DELETE'">用户删除权限</el-button>
|
||||
<el-button v-hasPermission="'USER:READ'">用户只读权限</el-button>
|
||||
<el-button v-hasPermission="new Role('USER')">普通用户角色</el-button>
|
||||
<el-button v-hasPermission="[new Role('ADMIN'), new Role('USER')]"
|
||||
>普通用户或者管理员</el-button
|
||||
>
|
||||
<el-button v-hasPermission="{ permission: ['USER:READ', new Role('USER')], compare: 'AND' }"
|
||||
>普通角色并且用户只读权限</el-button
|
||||
>
|
||||
|
||||
首页
|
||||
</div>
|
||||
首页
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Role } from "@/common/permission/type"
|
||||
import { Role } from '@/utils/permission/type'
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
<template>
|
||||
<LoiginLayout>
|
||||
<div class="register-form-container">
|
||||
<div class="register-form-title">
|
||||
<div class="title">
|
||||
<div class="logo"></div>
|
||||
<div>智能客服</div>
|
||||
</div>
|
||||
<div class="sub-title">忘记密码</div>
|
||||
</div>
|
||||
<el-form class="register-form" ref="resetPasswordFormRef" :model="CheckEmailForm" :rules="rules">
|
||||
|
||||
<el-form-item prop="email">
|
||||
<el-input size="large" class="input-item" v-model="CheckEmailForm.email" placeholder="请输入邮箱">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input size="large" class="code-input" v-model="CheckEmailForm.code" placeholder="请输入验证码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Key" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button size="large" class="send-email-button" @click="sendEmail"
|
||||
:loading="loading">获取验证码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="register-button" @click="checkCode">立即验证</el-button>
|
||||
<div class="operate-container">
|
||||
<span class="register" @click="router.push('login')">< 返回登陆</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</LoiginLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { UserFilled, Key } from '@element-plus/icons-vue'
|
||||
import type {
|
||||
CheckCodeRequest
|
||||
} from "@/api/user/type"
|
||||
import LoiginLayout from "@/components/layout/login-layout/index.vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import UserApi from "@/api/user/index"
|
||||
import { ElMessage } from "element-plus"
|
||||
|
||||
const router = useRouter()
|
||||
const CheckEmailForm = ref<CheckCodeRequest>({
|
||||
email: "",
|
||||
code: "",
|
||||
type: 'reset_password'
|
||||
});
|
||||
|
||||
const resetPasswordFormRef = ref<FormInstance>()
|
||||
const rules = ref<FormRules<CheckCodeRequest>>({
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
|
||||
if ((!emailRegExp.test(value) && value != '')) {
|
||||
callback(new Error('请输入有效邮箱格式!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码' }
|
||||
]
|
||||
|
||||
})
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const checkCode = () => {
|
||||
resetPasswordFormRef.value?.validate()
|
||||
.then(() => UserApi.checkCode(CheckEmailForm.value, loading))
|
||||
.then(() => router.push({ name: 'reset_password', params: CheckEmailForm.value }))
|
||||
}
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
const sendEmail = () => {
|
||||
resetPasswordFormRef.value?.validateField("email", (v: boolean) => {
|
||||
if (v) {
|
||||
UserApi.sendEmit(CheckEmailForm.value.email, "reset_password", loading)
|
||||
.then(() => {
|
||||
ElMessage.success("发送验证码成功")
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.register-form-container {
|
||||
width: 420px;
|
||||
|
||||
.code-input {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.send-email-button {
|
||||
margin-left: 12px;
|
||||
width: 158px;
|
||||
}
|
||||
|
||||
.register-form-title {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.logo {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #101010;
|
||||
height: 60px;
|
||||
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
color: #101010;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.operate-container {
|
||||
margin-top: 12px;
|
||||
color: rgba(51, 112, 255, 1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.register {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.register-button {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import TopBar from "@/components/layout/top-bar/index.vue"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="common-layout">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<TopBar></TopBar>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<RouterView></RouterView>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.el-header {
|
||||
--el-header-padding: 0;
|
||||
--el-header-height: 56px;
|
||||
padding: var(--el-header-padding);
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
height: var(--el-header-height);
|
||||
}
|
||||
|
||||
.el-main {
|
||||
--el-main-padding: 0;
|
||||
width: 100vw;
|
||||
height: calc(100vh - var(--el-header-height, 60px));
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<login-layout>
|
||||
<LoginContainer>
|
||||
<h3 class="mb-20">忘记密码</h3>
|
||||
<el-form
|
||||
class="register-form"
|
||||
ref="resetPasswordFormRef"
|
||||
:model="CheckEmailForm"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="CheckEmailForm.email"
|
||||
placeholder="请输入邮箱"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<div class="flex-between w-full">
|
||||
<el-input
|
||||
size="large"
|
||||
class="code-input"
|
||||
v-model="CheckEmailForm.code"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Key" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button
|
||||
size="large"
|
||||
class="send-email-button ml-10"
|
||||
@click="sendEmail"
|
||||
:loading="loading"
|
||||
>获取验证码</el-button
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="login-submit-button w-full" @click="checkCode"
|
||||
>立即验证</el-button
|
||||
>
|
||||
<div class="operate-container mt-10">
|
||||
<el-button
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
link
|
||||
type="primary"
|
||||
icon="DArrowLeft"
|
||||
>
|
||||
返回登录
|
||||
</el-button>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</login-layout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { CheckCodeRequest } from '@/api/type/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import UserApi from '@/api/user'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
|
||||
const router = useRouter()
|
||||
const CheckEmailForm = ref<CheckCodeRequest>({
|
||||
email: '',
|
||||
code: '',
|
||||
type: 'reset_password'
|
||||
})
|
||||
|
||||
const resetPasswordFormRef = ref<FormInstance>()
|
||||
const rules = ref<FormRules<CheckCodeRequest>>({
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
|
||||
if (!emailRegExp.test(value) && value != '') {
|
||||
callback(new Error('请输入有效邮箱格式!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code: [{ required: true, message: '请输入验证码' }]
|
||||
})
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const checkCode = () => {
|
||||
resetPasswordFormRef.value
|
||||
?.validate()
|
||||
.then(() => UserApi.checkCode(CheckEmailForm.value, loading))
|
||||
.then(() => router.push({ name: 'reset_password', params: CheckEmailForm.value }))
|
||||
}
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
const sendEmail = () => {
|
||||
resetPasswordFormRef.value?.validateField('email', (v: boolean) => {
|
||||
if (v) {
|
||||
UserApi.sendEmit(CheckEmailForm.value.email, 'reset_password', loading).then(() => {
|
||||
MsgSuccess('发送验证码成功')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.login-submit-button {
|
||||
margin-top: 12px;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
@ -1,152 +1,102 @@
|
|||
<template>
|
||||
<LoiginLayout v-loading="loading">
|
||||
<div class="login-form-container">
|
||||
<div class="login-form-title">
|
||||
<div class="title">
|
||||
<div class="logo"></div>
|
||||
<div>智能客服</div>
|
||||
</div>
|
||||
<div class="sub-title">欢迎使用智能客服管理平台</div>
|
||||
</div>
|
||||
<el-form class="login-form" :rules="rules" :model="loginForm" ref="loginFormRef">
|
||||
<el-form-item>
|
||||
<el-input size="large" class="input-item" v-model="loginForm.username" placeholder="请输入用户名">
|
||||
<template #prepend>
|
||||
<el-button :icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input type="password" size="large" class="input-item" v-model="loginForm.password"
|
||||
placeholder="请输入密码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="operate-container">
|
||||
<span class="register" @click="router.push('register')">注册</span>
|
||||
<span class="forgot-password" @click="router.push('forgot_password')">忘记密码</span>
|
||||
</div>
|
||||
<el-button type="primary" class="login-button" @click="login">登录</el-button>
|
||||
</div>
|
||||
</LoiginLayout>
|
||||
<login-layout v-loading="loading">
|
||||
<LoginContainer subTitle="欢迎使用智能客服管理平台">
|
||||
<el-form class="login-form" :rules="rules" :model="loginForm" ref="loginFormRef">
|
||||
<el-form-item>
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="loginForm.password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="operate-container flex-between">
|
||||
<el-button class="register" @click="router.push('/register')" link type="primary">
|
||||
注册
|
||||
</el-button>
|
||||
<el-button
|
||||
class="forgot-password"
|
||||
@click="router.push('/forgot_password')"
|
||||
link
|
||||
type="primary"
|
||||
>
|
||||
忘记密码
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button type="primary" class="login-submit-button w-full" @click="login">登录</el-button>
|
||||
</LoginContainer>
|
||||
</login-layout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import type { LoginRequest } from "@/api/user/type"
|
||||
import { UserFilled, Lock } from '@element-plus/icons-vue'
|
||||
import LoiginLayout from "@/components/layout/login-layout/index.vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { ref } from 'vue'
|
||||
import type { LoginRequest } from '@/api/type/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { useUserStore } from "@/stores/user"
|
||||
import useStore from '@/stores'
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const userStore = useUserStore();
|
||||
const loading = ref<boolean>(false)
|
||||
const { user } = useStore()
|
||||
const router = useRouter()
|
||||
const loginForm = ref<LoginRequest>({
|
||||
username: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const rules = ref<FormRules<LoginRequest>>({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入用户名",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
|
||||
const login = () => {
|
||||
loginFormRef.value?.validate().then(() => {
|
||||
loading.value = true
|
||||
userStore.login(loginForm.value.username, loginForm.value.password)
|
||||
.then(() => { router.push({ name: 'home' }) })
|
||||
.finally(() => loading.value = false)
|
||||
})
|
||||
loginFormRef.value?.validate().then(() => {
|
||||
loading.value = true
|
||||
user
|
||||
.login(loginForm.value.username, loginForm.value.password)
|
||||
.then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.login-form-container {
|
||||
width: 420px;
|
||||
|
||||
|
||||
.login-form-title {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.logo {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #101010;
|
||||
height: 60px;
|
||||
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #101010;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.operate-container {
|
||||
color: rgba(51, 112, 255, 1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.register {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@import './index.scss';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
<template>
|
||||
<login-layout>
|
||||
<LoginContainer>
|
||||
<h3 class="mb-20">注册</h3>
|
||||
<el-form class="register-form" :model="registerForm" :rules="rules" ref="registerFormRef">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="registerForm.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="registerForm.password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="repassword">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="registerForm.re_password"
|
||||
placeholder="请输入确认密码"
|
||||
show-password
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="registerForm.email"
|
||||
placeholder="请输入邮箱"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="Message" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<div class="flex-between w-full">
|
||||
<el-input
|
||||
size="large"
|
||||
class="code-input"
|
||||
v-model="registerForm.code"
|
||||
placeholder="请输入验证码"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button :icon="Key" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button
|
||||
size="large"
|
||||
class="send-email-button ml-10"
|
||||
@click="sendEmail"
|
||||
:loading="sendEmailLoading"
|
||||
>获取验证码</el-button
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="login-submit-button w-full" @click="register"
|
||||
>注册</el-button
|
||||
>
|
||||
<div class="operate-container mt-10">
|
||||
<el-button
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
link
|
||||
type="primary"
|
||||
icon="DArrowLeft"
|
||||
>
|
||||
返回登录
|
||||
</el-button>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</login-layout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { RegisterRequest } from '@/api/type/user'
|
||||
import { UserFilled, Lock, Message, Key } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import UserApi from '@/api/user'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const registerForm = ref<RegisterRequest>({
|
||||
username: '',
|
||||
password: '',
|
||||
re_password: '',
|
||||
email: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
const rules = ref<FormRules<RegisterRequest>>({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
re_password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入确认密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (registerForm.value.password != registerForm.value.re_password) {
|
||||
callback(new Error('密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
|
||||
if (!emailRegExp.test(value) && value != '') {
|
||||
callback(new Error('请输入有效邮箱格式!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code: [{ required: true, message: '请输入验证码' }]
|
||||
})
|
||||
|
||||
const registerFormRef = ref<FormInstance>()
|
||||
const register = () => {
|
||||
registerFormRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
return UserApi.register(registerForm.value)
|
||||
})
|
||||
.then(() => {
|
||||
router.push('login')
|
||||
})
|
||||
}
|
||||
const sendEmailLoading = ref<boolean>(false)
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
const sendEmail = () => {
|
||||
registerFormRef.value?.validateField('email', (v: boolean) => {
|
||||
if (v) {
|
||||
UserApi.sendEmit(registerForm.value.email, 'register', sendEmailLoading).then(() => {
|
||||
MsgSuccess('发送验证码成功')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<login-layout>
|
||||
<LoginContainer>
|
||||
<h3 class="mb-20">修改密码</h3>
|
||||
<el-form
|
||||
class="reset-password-form"
|
||||
ref="resetPasswordFormRef"
|
||||
:model="resetPasswordForm"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="re_password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.re_password"
|
||||
placeholder="请输入确认密码"
|
||||
show-password
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="login-submit-button w-full" @click="resetPassword"
|
||||
>确认修改</el-button
|
||||
>
|
||||
<div class="operate-container mt-10">
|
||||
<el-button
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
link
|
||||
type="primary"
|
||||
icon="DArrowLeft"
|
||||
>
|
||||
返回登录
|
||||
</el-button>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</login-layout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { ResetPasswordRequest } from '@/api/type/user'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import UserApi from '@/api/user'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const resetPasswordForm = ref<ResetPasswordRequest>({
|
||||
password: '',
|
||||
re_password: '',
|
||||
email: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
const code = route.params.code
|
||||
const email = route.params.email
|
||||
if (code && email) {
|
||||
resetPasswordForm.value.code = code as string
|
||||
resetPasswordForm.value.email = email as string
|
||||
} else {
|
||||
router.push('forgot_password')
|
||||
}
|
||||
})
|
||||
|
||||
const rules = ref<FormRules<ResetPasswordRequest>>({
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
re_password: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入确认密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: '长度在 6 到 30 个字符',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
|
||||
callback(new Error('密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
const resetPasswordFormRef = ref<FormInstance>()
|
||||
const loading = ref<boolean>(false)
|
||||
const resetPassword = () => {
|
||||
resetPasswordFormRef.value
|
||||
?.validate()
|
||||
.then(() => UserApi.resetPassword(resetPasswordForm.value, loading))
|
||||
.then(() => {
|
||||
MsgSuccess('修改密码成功')
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
@import '../index.scss';
|
||||
</style>
|
||||
|
|
@ -1,236 +0,0 @@
|
|||
<template>
|
||||
<LoiginLayout>
|
||||
<div class="register-form-container">
|
||||
<div class="register-form-title">
|
||||
<div class="title">
|
||||
<div class="logo"></div>
|
||||
<div>智能客服</div>
|
||||
</div>
|
||||
<div class="sub-title">修改密码</div>
|
||||
</div>
|
||||
<el-form class="register-form" :model="registerForm" :rules="rules" ref="registerFormRef">
|
||||
<el-form-item prop="username">
|
||||
<el-input size="large" class="input-item" v-model="registerForm.username" placeholder="请输入用户名">
|
||||
<template #prepend>
|
||||
<el-button :icon="UserFilled" />
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input type="password" size="large" class="input-item" v-model="registerForm.password"
|
||||
placeholder="请输入密码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="repassword">
|
||||
<el-input type="password" size="large" class="input-item" v-model="registerForm.re_password"
|
||||
placeholder="请输入确认密码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="email">
|
||||
<el-input size="large" class="input-item" v-model="registerForm.email" placeholder="请输入邮箱">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Message" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input size="large" class="code-input" v-model="registerForm.code" placeholder="请输入验证码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Key" />
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button size="large" class="send-email-button" @click="sendEmail"
|
||||
:loading="sendEmailLoading">获取验证码</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="register-button" @click="register">注册</el-button>
|
||||
<div class="operate-container">
|
||||
<span class="register" @click="router.push('login')">< 返回登陆</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</LoiginLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import type { RegisterRequest } from "@/api/user/type"
|
||||
import { UserFilled, Lock, Message, Key } from '@element-plus/icons-vue'
|
||||
import LoiginLayout from "@/components/layout/login-layout/index.vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import UserApi from "@/api/user/index"
|
||||
import { ElMessage } from "element-plus"
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const registerForm = ref<RegisterRequest>({
|
||||
username: '',
|
||||
password: '',
|
||||
re_password: '',
|
||||
email: '',
|
||||
code: ''
|
||||
});
|
||||
|
||||
const rules = ref<FormRules<RegisterRequest>>({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入用户名",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
re_password: [{
|
||||
required: true,
|
||||
message: '请输入确认密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (registerForm.value.password != registerForm.value.re_password) {
|
||||
callback(new Error('密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
|
||||
if ((!emailRegExp.test(value) && value != '')) {
|
||||
callback(new Error('请输入有效邮箱格式!'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码' }
|
||||
]
|
||||
|
||||
})
|
||||
|
||||
const registerFormRef = ref<FormInstance>();
|
||||
const register = () => {
|
||||
registerFormRef.value?.validate().then(() => {
|
||||
return UserApi.register(registerForm.value)
|
||||
}).then(() => {
|
||||
router.push("login")
|
||||
})
|
||||
}
|
||||
const sendEmailLoading = ref<boolean>(false);
|
||||
/**
|
||||
* 发送验证码
|
||||
*/
|
||||
const sendEmail = () => {
|
||||
registerFormRef.value?.validateField("email", (v: boolean) => {
|
||||
if (v) {
|
||||
UserApi.sendEmit(registerForm.value.email, "register",sendEmailLoading)
|
||||
.then(() => {
|
||||
ElMessage.success("发送验证码成功")
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.register-form-container {
|
||||
width: 420px;
|
||||
|
||||
.code-input {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.send-email-button {
|
||||
margin-left: 12px;
|
||||
width: 158px;
|
||||
}
|
||||
|
||||
.register-form-title {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.logo {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #101010;
|
||||
height: 60px;
|
||||
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
color: #101010;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.operate-container {
|
||||
margin-top: 12px;
|
||||
color: rgba(51, 112, 255, 1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.register {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.register-button {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
<template>
|
||||
<LoiginLayout>
|
||||
<div class="register-form-container">
|
||||
<div class="register-form-title">
|
||||
<div class="title">
|
||||
<div class="logo"></div>
|
||||
<div>智能客服</div>
|
||||
</div>
|
||||
<div class="sub-title">修改密码</div>
|
||||
</div>
|
||||
<el-form class="reset-password-form" ref="resetPasswordFormRef" :model="resetPasswordForm" :rules="rules">
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input type="password" size="large" class="input-item" v-model="resetPasswordForm.password"
|
||||
placeholder="请输入密码">
|
||||
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="re_password">
|
||||
<el-input type="password" size="large" class="input-item" v-model="resetPasswordForm.re_password"
|
||||
placeholder="请输入确认密码">
|
||||
<template #prepend>
|
||||
<el-button :icon="Lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="register-button" @click="resetPassword">确认修改</el-button>
|
||||
<div class="operate-container">
|
||||
<span class="register" @click="router.push('login')">< 返回登陆</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</LoiginLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue"
|
||||
import type { ResetPasswordRequest } from "@/api/user/type"
|
||||
import { Lock } from '@element-plus/icons-vue'
|
||||
import LoiginLayout from "@/components/layout/login-layout/index.vue"
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
import { ElMessage } from "element-plus"
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import UserApi from "@/api/user/index"
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const resetPasswordForm = ref<ResetPasswordRequest>({
|
||||
password: '',
|
||||
re_password: '',
|
||||
email: '',
|
||||
code: ''
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const code = route.params.code;
|
||||
const email = route.params.email;
|
||||
if (code && email) {
|
||||
resetPasswordForm.value.code = code as string;
|
||||
resetPasswordForm.value.email = email as string;
|
||||
} else {
|
||||
router.push('forgot_password')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const rules = ref<FormRules<ResetPasswordRequest>>({
|
||||
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
re_password: [{
|
||||
required: true,
|
||||
message: '请输入确认密码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 30,
|
||||
message: "长度在 6 到 30 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
|
||||
callback(new Error('密码不一致'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}],
|
||||
|
||||
})
|
||||
const resetPasswordFormRef = ref<FormInstance>();
|
||||
const loading = ref<boolean>(false);
|
||||
const resetPassword = () => {
|
||||
resetPasswordFormRef.value?.validate()
|
||||
.then(() => UserApi.resetPassword(resetPasswordForm.value, loading))
|
||||
.then(() => {
|
||||
ElMessage.success("修改密码成功")
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scope>
|
||||
.register-form-container {
|
||||
width: 420px;
|
||||
|
||||
.code-input {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.send-email-button {
|
||||
margin-left: 12px;
|
||||
width: 158px;
|
||||
}
|
||||
|
||||
.register-form-title {
|
||||
width: 100%;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.logo {
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-size: 100% 100%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
color: #101010;
|
||||
height: 60px;
|
||||
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
color: #101010;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.operate-container {
|
||||
margin-top: 12px;
|
||||
color: rgba(51, 112, 255, 1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.register {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.register-button {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
width="600"
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<h4 :id="titleId" :class="titleClass">添加成员</h4>
|
||||
<div class="dialog-sub-title">成员登录后可以访问到您授权的数据。</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="addMemberFormRef"
|
||||
:model="memberForm"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="用户名/邮箱" prop="users">
|
||||
<tags-input
|
||||
v-model:tags="memberForm.users"
|
||||
v-model:tag="memberForm.user"
|
||||
placeholder="请输入成员的用户名或邮箱,若需添加多个成员请使用回车分割。"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" @click="submitMember(addMemberFormRef)"> 添加 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import TeamApi from '@/api/team'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const memberForm = ref({
|
||||
users: [],
|
||||
user: ''
|
||||
})
|
||||
|
||||
const addMemberFormRef = ref<FormInstance>()
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const validateUsers = (rule: any, value: any, callback: any) => {
|
||||
if (value?.length == 0 && !memberForm.value.user) {
|
||||
callback(new Error('请输入用户名/邮箱'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const rules = ref<FormRules>({
|
||||
users: [{ type: 'array', validator: validateUsers }]
|
||||
})
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
memberForm.value = {
|
||||
users: [],
|
||||
user: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitMember = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
const obj: any = {
|
||||
username_or_email: memberForm.value.users?.length
|
||||
? memberForm.value.users.toString()
|
||||
: memberForm.value.user
|
||||
}
|
||||
TeamApi.postCreatTeamMember(obj).then(() => {
|
||||
MsgSuccess('提交成功')
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
} else {
|
||||
console.log('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<el-table :data="data" :max-height="tableHeight">
|
||||
<el-table-column prop="name" label="数据集名称" />
|
||||
<el-table-column label="管理" align="center">
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
v-model="allChecked[MANAGE]"
|
||||
label="管理"
|
||||
@change="handleCheckAllChange($event, MANAGE)"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.operate[MANAGE]" @change="checkedOperateChange(MANAGE, row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="使用" align="center">
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
v-model="allChecked[USE]"
|
||||
label="使用"
|
||||
@change="handleCheckAllChange($event, USE)"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox v-model="row.operate[USE]" @change="checkedOperateChange(USE, row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
|
||||
const MANAGE = 'MANAGE'
|
||||
const USE = 'USE'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
id: String
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:data'])
|
||||
const allChecked: any = ref({
|
||||
[MANAGE]: false,
|
||||
[USE]: false
|
||||
})
|
||||
|
||||
const tableHeight = ref(100)
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(val) => {
|
||||
Object.keys(allChecked.value).map((item) => {
|
||||
allChecked.value[item] = compare(item)
|
||||
})
|
||||
emit('update:data', val)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
function handleCheckAllChange(val: string | number | boolean, Name: string | number) {
|
||||
if (val) {
|
||||
props.data.map((item: any) => {
|
||||
item.operate[Name] = true
|
||||
})
|
||||
} else {
|
||||
props.data.map((item: any) => {
|
||||
item.operate[Name] = false
|
||||
})
|
||||
}
|
||||
}
|
||||
function checkedOperateChange(Name: string | number, row: any) {
|
||||
if (Name === MANAGE) {
|
||||
props.data.map((item: any) => {
|
||||
if (item.id === row.id) {
|
||||
item.operate[USE] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
allChecked.value[Name] = compare(Name)
|
||||
}
|
||||
|
||||
function compare(attrs: string | number) {
|
||||
const filterData = props.data.filter((item: any) => item?.operate[attrs])
|
||||
return props.data.length > 0 && filterData.length === props.data.length
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tableHeight.value = window.innerHeight - 300
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
tableHeight.value = window.innerHeight - 300
|
||||
})()
|
||||
}
|
||||
Object.keys(allChecked.value).map((item) => {
|
||||
allChecked.value[item] = compare(item)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
@ -1,9 +1,233 @@
|
|||
<template >
|
||||
<div>
|
||||
setting
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
<template>
|
||||
<LayoutContent header="团队管理">
|
||||
<div class="team-manage flex main-calc-height">
|
||||
<div class="team-member p-15 border-r">
|
||||
<h3>团队成员</h3>
|
||||
<div class="align-right">
|
||||
<el-button type="primary" link @click="addMember">
|
||||
<AppIcon iconName="app-add-users" class="add-user-icon" />添加成员
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<el-input v-model="filterText" placeholder="请输入用户名搜索" suffix-icon="Search" />
|
||||
</div>
|
||||
<div class="member-list mt-10" v-loading="loading">
|
||||
<el-scrollbar>
|
||||
<ul v-if="filterMember.length > 0">
|
||||
<template v-for="(item, index) in filterMember" :key="index">
|
||||
<li
|
||||
@click.prevent="clickMemberHandle(item.id)"
|
||||
:class="currentUser === item.id ? 'active' : ''"
|
||||
class="border-b-light flex-between p-15 cursor"
|
||||
>
|
||||
<div>
|
||||
<span class="mr-10">{{ item.username }}</span>
|
||||
<el-tag effect="dark" v-if="isManage(item.type)">所有者</el-tag>
|
||||
<el-tag effect="dark" type="warning" v-else>用户</el-tag>
|
||||
</div>
|
||||
<el-dropdown trigger="click" v-if="!isManage(item.type)">
|
||||
<span class="cursor">
|
||||
<el-icon><MoreFilled /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click.prevent="deleteMember(item.id)"
|
||||
>移除</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<el-empty description="暂无数据" v-else />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<div class="permission-setting flex" v-loading="rLoading">
|
||||
<div class="team-manage__table p-15">
|
||||
<h3>权限设置</h3>
|
||||
<el-tabs v-model="activeName" class="demo-tabs">
|
||||
<el-tab-pane
|
||||
v-for="item in settingTags"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:name="item.value"
|
||||
>
|
||||
<PermissionSetting :data="item.data"></PermissionSetting>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<div class="team-manage__footer border-t p-15 flex">
|
||||
<el-button type="primary" @click="submitPermissions">保存</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CreateMemberDialog ref="CreateMemberRef" @refresh="refresh" />
|
||||
</LayoutContent>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, watch } from 'vue'
|
||||
import TeamApi from '@/api/team'
|
||||
import type { TeamMember } from '@/api/type/team'
|
||||
import CreateMemberDialog from './component/CreateMemberDialog.vue'
|
||||
import PermissionSetting from './component/PermissionSetting.vue'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
|
||||
const DATASET = 'DATASET'
|
||||
|
||||
const CreateMemberRef = ref<InstanceType<typeof CreateMemberDialog>>()
|
||||
const loading = ref(false)
|
||||
const rLoading = ref(false)
|
||||
const memberList = ref<TeamMember[]>([]) // 全部成员
|
||||
const filterMember = ref<TeamMember[]>([]) // 搜索过滤后列表
|
||||
const currentUser = ref<String>('')
|
||||
const filterText = ref('')
|
||||
|
||||
const activeName = ref(DATASET)
|
||||
|
||||
const settingTags = reactive([
|
||||
{
|
||||
label: '数据集',
|
||||
value: DATASET,
|
||||
data: [] as any
|
||||
},
|
||||
{
|
||||
label: '应用',
|
||||
value: 'application',
|
||||
data: [] as any
|
||||
}
|
||||
])
|
||||
|
||||
watch(filterText, (val) => {
|
||||
if (val) {
|
||||
filterMember.value = memberList.value.filter((v) => v.username.includes(val))
|
||||
} else {
|
||||
filterMember.value = memberList.value
|
||||
}
|
||||
})
|
||||
|
||||
function submitPermissions() {
|
||||
rLoading.value = true
|
||||
const obj: any = {
|
||||
team_member_permission_list: []
|
||||
}
|
||||
settingTags.map((item) => {
|
||||
item.data.map((v: any) => {
|
||||
obj['team_member_permission_list'].push({
|
||||
target_id: v.id,
|
||||
type: v.type,
|
||||
operate: v.operate
|
||||
})
|
||||
})
|
||||
})
|
||||
TeamApi.putMemberPermissions(currentUser.value, obj)
|
||||
.then(() => {
|
||||
MsgSuccess('提交成功')
|
||||
MemberPermissions(currentUser.value)
|
||||
})
|
||||
.catch(() => {
|
||||
rLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function MemberPermissions(id: String) {
|
||||
rLoading.value = true
|
||||
TeamApi.getMemberPermissions(id)
|
||||
.then((res) => {
|
||||
if (!res.data || Object.keys(res.data).length > 0) {
|
||||
settingTags.map((item) => {
|
||||
if (Object.keys(res.data).indexOf(item.value) !== -1) {
|
||||
item.data = res.data[item.value]
|
||||
}
|
||||
})
|
||||
}
|
||||
rLoading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
rLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteMember(id: String) {
|
||||
loading.value = true
|
||||
TeamApi.delTeamMember(id)
|
||||
.then(() => {
|
||||
MsgSuccess('删除成功')
|
||||
getMember()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function isManage(type: String) {
|
||||
return type === 'manage'
|
||||
}
|
||||
|
||||
function clickMemberHandle(id: String) {
|
||||
currentUser.value = id
|
||||
MemberPermissions(id)
|
||||
}
|
||||
function addMember() {
|
||||
CreateMemberRef.value?.open()
|
||||
}
|
||||
|
||||
function getMember() {
|
||||
loading.value = true
|
||||
TeamApi.getTeamMember()
|
||||
.then((res) => {
|
||||
memberList.value = res.data
|
||||
filterMember.value = res.data
|
||||
currentUser.value = memberList.value[0].id
|
||||
MemberPermissions(currentUser.value)
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getMember()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getMember()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.team-manage {
|
||||
.add-user-icon {
|
||||
margin-right: 5px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.team-member {
|
||||
box-sizing: border-box;
|
||||
width: var(--team-manage-left-width);
|
||||
min-width: var(--team-manage-left-width);
|
||||
.member-list {
|
||||
li {
|
||||
&.active {
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.permission-setting {
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - var(--team-manage-left-width) - 5px);
|
||||
flex-direction: column;
|
||||
}
|
||||
.team-manage__table {
|
||||
flex: 1;
|
||||
}
|
||||
.team-manage__footer {
|
||||
flex: 0 0 auto;
|
||||
justify-content: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
import type { ProxyOptions } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import DefineOptions from 'unplugin-vue-define-options/vite'
|
||||
|
||||
const envDir = './env'
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
|
|
@ -13,10 +16,11 @@ export default defineConfig(({ mode }) => {
|
|||
rewrite: (path) => path.replace(ENV.VITE_BASE_PATH, '/')
|
||||
}
|
||||
return {
|
||||
preflight: false,
|
||||
lintOnSave: false,
|
||||
base: ENV.VITE_BASE_PATH,
|
||||
envDir: envDir,
|
||||
plugins: [vue()],
|
||||
plugins: [vue(), DefineOptions()],
|
||||
server: {
|
||||
cors: true,
|
||||
host: '0.0.0.0',
|
||||
|
|
|
|||
Loading…
Reference in New Issue