feat: 初始化基本配置

This commit is contained in:
wangdan-fit2cloud 2023-10-12 16:36:16 +08:00
parent 64e93679f8
commit 5511769673
31 changed files with 1149 additions and 911 deletions

3
ui/env/.env vendored
View File

@ -1,3 +1,4 @@
VITE_APP_NAME=ui
VITE_BASE_PATH=/ui/
VITE_APP_PORT=3000
VITE_APP_PORT=3000
VITE_APP_TITLE = '智能知识库'

View File

@ -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>

341
ui/package-lock.json generated
View File

@ -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",
@ -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",
@ -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",

View File

@ -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"

View File

@ -10,6 +10,9 @@ const hasPermissionChild = (permission: Role | string | Permission | ComplexPerm
const userStore = useUserStore(store)
const permissions = userStore.getPermissions()
const role = userStore.getRole()
if (!permission) {
return true
}
if (permission instanceof Role) {
return role === permission.role
}
@ -24,6 +27,7 @@ const hasPermissionChild = (permission: Role | string | Permission | ComplexPerm
if (typeof permission === 'string') {
return permissions.includes(permission)
}
return false
}
/**

View File

@ -4,6 +4,7 @@
</template>
<script setup lang="ts">
import { iconMap } from "@/components/icons/index"
defineOptions({ name: 'AppIcon' });
withDefaults(defineProps<{
iconName?: string;
}>(), {

View File

@ -0,0 +1,12 @@
import { type App } from 'vue'
import AppIcon from './icons/AppIcon.vue'
import LoginLayout from './layout/login-layout/index.vue'
import LoginContainer from './layout/login-container/index.vue'
export default {
install(app: App) {
app.component(AppIcon.name, AppIcon)
app.component(LoginLayout.name, LoginLayout)
app.component(LoginContainer.name, LoginContainer)
}
}

View File

@ -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>

View File

@ -1,66 +1,48 @@
<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 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 {
display: flex;
align-items: center;
justify-content: center;
align-content: center;
height: 100%;
height: 100vh;
.login-image {
background: url(@/assets/login.png) no-repeat;
background-size: 100% 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%;
}
}
}
}
height: 100%;
}
}
</style>

View File

@ -2,7 +2,7 @@
<div class="top-bar-container">
<div class="app-title-container">
<div class="app-title-icon"></div>
<div class="app-title-text">智能客服</div>
<div class="app-title-text">{{ defaultTitle }}</div>
<div class="line"></div>
</div>
<div class="app-top-menu-container">
@ -15,8 +15,9 @@
</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"
import TopMenu from '@/components/layout/top-bar/components/top-menu/index.vue'
import Avatar from '@/components/layout/top-bar/components/avatar/index.vue'
const defaultTitle = import.meta.env.VITE_APP_TITLE
</script>
<style lang="scss">
.top-bar-container {
@ -46,20 +47,18 @@ import Avatar from "@/components/layout/top-bar/components/avatar/index.vue"
justify-content: center;
align-items: center;
.app-title-icon {
background-image: url('@/assets/logo.png');
background-size: 100% 100%;
width: 48px;
height: 48px;
width: 40px;
height: 40px;
}
.app-title-text {
color: var(--app-base-action-text-color);
font-size: 28px;
font-size: 20px;
font-weight: 600;
align-items: center;
}
.line {

View File

@ -3,8 +3,8 @@ import { hasPermission } from '@/common/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'

View File

@ -1,25 +1,25 @@
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 { 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
for (const [key, component] of Object.entries(ElementPlusIcons)) {
app.component(key, component)
}
app.use(ElementPlus)
app.use(theme)
app.use(router)
app.use(Components)
app.mount('#app')

View File

@ -4,7 +4,7 @@ export const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
component: () => import('@/views/home/index.vue'),
component: () => import('@/components/layout/home-layout/index.vue'),
children: [
{
path: '/first',
@ -27,7 +27,7 @@ export const routes: Array<RouteRecordRaw> = [
{
path: '/setting',
name: 'setting',
meta: { icon: 'setting', title: '数据设置', permission: 'SETTING:READ' },
meta: { icon: 'setting', title: '系统设置', permission: 'SETTING:READ' },
component: () => import('@/views/setting/index.vue')
}
]
@ -40,17 +40,17 @@ export const routes: Array<RouteRecordRaw> = [
{
path: '/register',
name: 'register',
component: () => import('@/views/register/index.vue')
component: () => import('@/views/login/register/index.vue')
},
{
path: '/forgot_password',
name: 'forgot_password',
component: () => import('@/views/forgot-password/index.vue')
component: () => import('@/views/login/forgot-password/index.vue')
},
{
path: '/reset_password/:code/:email',
name: 'reset_password',
component: () => import('@/views/reset-password/index.vue')
component: () => import('@/views/login/reset-password/index.vue')
},
{
path: '/:pathMatch(.*)',

View File

@ -1,10 +1,19 @@
* {
margin: 0;
padding: 0;
}
html {
height: 100%;
box-sizing: border-box;
}
body {
font-family: Helvetica, PingFang SC, Arial, sans-serif;
font-family:
Helvetica,
PingFang SC,
Arial,
sans-serif;
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@ -15,7 +24,7 @@ body {
}
#app {
height:100%;
height: 100%;
}
:focus {
@ -34,6 +43,16 @@ a:hover {
text-decoration: none;
}
div:focus {
outline: none;
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
// 滚动条整体部分
::-webkit-scrollbar {
width: 6px; // 纵向滚动条宽度
@ -52,30 +71,68 @@ a:hover {
background-color: transparent;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
.mt-1 {
margin-top: 10px;
}
.ml-1 {
margin-left: 10px;
}
.mr-1 {
margin-right: 10px;
}
.mb-1 {
margin-bottom: 10px;
}
.mb-2 {
margin-bottom: 20px;
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
// 创建表单
.create-catalog-container {
height: 100%;
margin-top: -20px;
.padding-top-30{
padding-top:30px;
.padding-top-30 {
padding-top: 30px;
}
.padding-top-40{
padding-top:40px;
.padding-top-40 {
padding-top: 40px;
}
// 表单外套
.form-div{
.form-div {
text-align: center;
margin: 0 auto;
width: 80%;
min-width: 300px;
form{
.el-form-item {margin-bottom: 28px;}
label{
form {
.el-form-item {
margin-bottom: 28px;
}
label {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #1F2329;
color: #1f2329;
flex: none;
order: 0;
flex-grow: 0;
@ -84,13 +141,13 @@ a:hover {
}
// 删除按钮样式
.delete-button-class{
.delete-button-class {
cursor: pointer;
color: #646a73
color: #646a73;
}
// 添加按钮样式
.add-button-class{
.add-button-class {
cursor: pointer;
border: 0 solid;
//width: 105px;
@ -102,23 +159,22 @@ a:hover {
display: flex;
align-items: center;
letter-spacing: -0.1px;
color: #3370FF;
.span-class{
vertical-align:2px;
color: #3370FF;
padding-left: 5px
color: #3370ff;
.span-class {
vertical-align: 2px;
color: #3370ff;
padding-left: 5px;
}
}
button{
button {
height: 32px;
min-width: 80px
min-width: 80px;
}
.save-btn{
background-color: #3370FF;
.save-btn {
background-color: #3370ff;
}
.cancel-btn{
.cancel-btn {
}
// 下方操作按钮区域
@ -159,70 +215,65 @@ a:hover {
}
// 自定义弹出框样式
.custom-dialog{
.custom-dialog {
//标题样式
.el-dialog__header{
.el-dialog__header {
padding: 24px !important;
}
//关闭按钮样式
.el-dialog__headerbtn .el-dialog__close{
.el-dialog__headerbtn .el-dialog__close {
height: auto !important;
color: #646A73 !important;
color: #646a73 !important;
font-size: x-large !important;
}
.el-dialog__headerbtn .el-dialog__close:hover{
.el-dialog__headerbtn .el-dialog__close:hover {
background: rgba(31, 35, 41, 0.1) !important;
border-radius: 4px !important;
}
//内容间距
.el-dialog__body{
.el-dialog__body {
padding: 0px 24px 0px 24px;
}
.el-dialog__footer{
.el-dialog__footer {
padding-bottom: 29px !important;
}
//下方按钮
.footer-btn{
button{
.footer-btn {
button {
height: 32px;
min-width: 80px
min-width: 80px;
}
.save-btn{
background-color: #3370FF;
.save-btn {
background-color: #3370ff;
}
.cancel-btn{
.cancel-btn {
}
}
}
.custom-radio-group.el-radio-group{
border: 1px solid #BBBFC4;
.custom-radio-group.el-radio-group {
border: 1px solid #bbbfc4;
border-radius: 5px;
height: 30px;
label{
label {
border: 0px solid;
padding: 2px 10px 2px 4px;
}
.el-radio-button__inner{
.el-radio-button__inner {
padding: 4px;
border: 0px;
border-radius: 5px;
}
.el-radio-button{
.el-radio-button {
height: auto;
}
.el-radio-button is-active{
.el-radio-button is-active {
height: auto;
}
.el-radio-button__original-radio:checked + .el-radio-button__inner{
color: #3370FF;
.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);
}
}

View File

@ -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
}
}

View File

@ -5,4 +5,24 @@
.el-form {
--el-form-inline-content-width: 100%;
}
// 抽屉样式整体修改
.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
}
}

View File

@ -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'

View File

@ -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;
}

View File

@ -4,4 +4,8 @@
--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 );
/** header 组件 */
--app-header-height: 56px;
--app-header-padding: 0 20px;
--app-header-bg-color: #252b3c;
}

View File

@ -1,6 +0,0 @@
:root{
--app-header-height: 56px;
--app-header-padding: 0 20px;
--app-header-bg-color: #252b3c;
}

View File

@ -1,2 +0,0 @@
@use "./header.scss";
@use "./app.scss";

View File

@ -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')">&lt; 返回登陆</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>

View File

@ -0,0 +1,119 @@
<template>
<login-layout>
<LoginContainer>
<h3 class="mb-2">忘记密码</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-1"
@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-1">
<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 { UserFilled, Key } from '@element-plus/icons-vue'
import type { CheckCodeRequest } from '@/api/user/type'
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>
@import '../index.scss';
</style>

View File

@ -0,0 +1,4 @@
.login-submit-button {
margin-top: 12px;
height: 40px;
}

View File

@ -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/user/type'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import { useUserStore } from "@/stores/user"
import { useUserStore } from '@/stores/user'
const loading = ref<boolean>(false);
const userStore = useUserStore();
const loading = ref<boolean>(false)
const userStore = useUserStore()
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
userStore
.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;
}
}
@import './index.scss';
</style>

View File

@ -0,0 +1,203 @@
<template>
<login-layout>
<LoginContainer>
<h3 class="mb-2">注册</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-1"
@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-1">
<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/user/type'
import { UserFilled, Lock, Message, Key } from '@element-plus/icons-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>
@import '../index.scss';
</style>

View File

@ -0,0 +1,137 @@
<template>
<login-layout>
<LoginContainer>
<h3 class="mb-2">修改密码</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-1">
<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/user/type'
import { Lock } from '@element-plus/icons-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>
@import '../index.scss';
</style>

View File

@ -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')">&lt; 返回登陆</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>

View File

@ -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')">&lt; 返回登陆</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>

View File

@ -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',