Init
33
.eslintrc.cjs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
node: true
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"standard-with-typescript",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
overrides: [],
|
||||||
|
parserOptions: {
|
||||||
|
project: "./tsconfig*.json",
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module"
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
"vue",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"indent": ["error", 4, {"SwitchCase": 1}],
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
endOfLine: "auto",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
55
.gitignore
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
### Auto Imports ###
|
||||||
|
src/auto-imports.d.ts
|
||||||
|
src/components.d.ts
|
||||||
8
.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"semi": false,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Framework</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12210
package-lock.json
generated
Normal file
58
package.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "pinnacle-ui",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"dev-host": "vite --host 0.0.0.0",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"build": "run-p type-check build-only",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:unit": "vitest --environment jsdom --root src/",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"element-plus": "^2.3.4",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"pinia": "^2.0.36",
|
||||||
|
"vite-plugin-inspect": "^0.7.24",
|
||||||
|
"vue": "^3.2.47",
|
||||||
|
"vue-router": "^4.1.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@iconify-json/ep": "^1.1.10",
|
||||||
|
"@types/jsdom": "^21.1.0",
|
||||||
|
"@types/node": "^18.14.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||||
|
"@typescript-eslint/parser": "^5.59.1",
|
||||||
|
"@vitejs/plugin-vue": "^4.2.1",
|
||||||
|
"@vue/test-utils": "^2.3.0",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"eslint": "^8.39.0",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
|
"eslint-config-standard-with-typescript": "^34.0.1",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-n": "^15.7.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"eslint-plugin-vue": "^9.11.0",
|
||||||
|
"jsdom": "^21.1.0",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
|
"stylelint-config-prettier": "^9.0.5",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
|
"unplugin-auto-import": "^0.15.3",
|
||||||
|
"unplugin-element-plus": "^0.7.1",
|
||||||
|
"unplugin-icons": "^0.16.1",
|
||||||
|
"unplugin-vue-components": "^0.24.1",
|
||||||
|
"vite": "^4.3.9",
|
||||||
|
"vitest": "^0.30.1",
|
||||||
|
"vue-tsc": "^1.6.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
public/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200" height="200" fill="#409eff">
|
||||||
|
<path d="M810.554166 57.547768H213.848945c-86.156711 0-156.281057 70.126346-156.281057 156.280058v596.704221c0 86.208692 70.123347 156.279058 156.281057 156.279058h596.704221c86.207693 0 156.277059-70.070366 156.277059-156.279058V213.827826c0.001-86.152712-70.068367-156.280058-156.276059-156.280058z m71.037026 752.984279a71.061018 71.061018 0 0 1-71.034027 71.036027H213.848945a71.097005 71.097005 0 0 1-71.037026-71.036027v-56.26122L289.772254 567.359538a70.753126 70.753126 0 0 1 54.3289-27.106471c19.263228 0.2839 41.485415 8.524003 55.406521 24.66333l17.676785 20.513788a28.734898 28.734898 0 0 0 2.099262 2.499121l117.408724 136.73393a42.63901 42.63901 0 0 0 64.727244-55.522481l-95.696357-111.440821c52.28162-66.776524 112.577422-144.685134 125.248967-161.05238a70.752126 70.752126 0 0 1 54.210942-26.88055c22.901949 0.68176 41.430435 8.637963 55.29656 24.663329l137.18577 159.74384c1.30854 1.420501 2.554102 2.784021 3.920622 4.034582z m0-382.630481l-76.434128-88.993713a155.645281 155.645281 0 0 0-118.431364-54.380882c-1.140599 0-2.163239 0-3.240861 0.05698-46.768558 0.90968-90.300254 22.447108-116.045203 55.464501-1.249561 1.30754-4.598383 5.343122-5.682002 6.763622-10.396345 13.468265-64.043485 82.742911-113.714023 146.277574-29.09877-25.232129-65.976805-40.405795-105.817798-38.018634a155.684267 155.684267 0 0 0-119.450006 59.557062l-79.959889 101.664259v-402.458511a71.134992 71.134992 0 0 1 71.038026-71.037026h596.704221a71.099004 71.099004 0 0 1 71.033027 71.031028z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
19
src/App.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<el-config-provider :locale="zhCn()">
|
||||||
|
<router-view></router-view>
|
||||||
|
</el-config-provider>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
zhCn() {
|
||||||
|
return zhCn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
63
src/assets/css/base.css
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
em,
|
||||||
|
i {
|
||||||
|
font-style: normal
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
vertical-align: middle
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
font-family: Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
background-color: #fff;
|
||||||
|
font: 12px/1.5 Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif;
|
||||||
|
color: #666
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide,
|
||||||
|
.none {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
visibility: hidden;
|
||||||
|
clear: both;
|
||||||
|
display: block;
|
||||||
|
content: ".";
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix {
|
||||||
|
*zoom: 1
|
||||||
|
}
|
||||||
125
src/assets/css/common.css
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
:root {
|
||||||
|
--main-color: #409eff;
|
||||||
|
--background-color: #F5F5F5;
|
||||||
|
--font-main-color: #4D4D4D;
|
||||||
|
--font-secondary-color: #9E9E9E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
color: var(--font-main-color);
|
||||||
|
user-select: none;
|
||||||
|
min-width: 1100px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-with {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-height {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-white {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-center-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-center-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-xs {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-xs > use {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-sm {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-sm > use {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-md {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-md > use {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-lg {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-lg > use {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-xl {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-xl > use {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-menu {
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-size-menu > use {
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message-box__btns button:first-child {
|
||||||
|
margin-left: 10px;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popconfirm__action button:first-child {
|
||||||
|
margin-left: 12px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popconfirm__action button:last-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__cell .el-tag {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
BIN
src/assets/img/logo.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
1
src/assets/svg/home.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="24" height="24" viewBox="0 0 24 24"><g style="mix-blend-mode:passthrough"><g style="mix-blend-mode:passthrough"><path d="M11.2633,0.229798C11.6966,-0.0765992,12.3034,-0.0765992,12.7367,0.229798C12.7367,0.229798,23.5367,7.86616,23.5367,7.86616C23.829,8.07284,24,8.39063,24,8.72727C24,8.72727,24,20.7273,24,20.7273C24,21.5953,23.6207,22.4277,22.9456,23.0414C22.2704,23.6552,21.3548,24,20.4,24C20.4,24,3.6,24,3.6,24C2.64522,24,1.72955,23.6552,1.05442,23.0414C0.379284,22.4277,0,21.5953,0,20.7273C0,20.7273,0,8.72727,0,8.72727C0,8.39063,0.170968,8.07284,0.463271,7.86616C0.463271,7.86616,11.2633,0.229798,11.2633,0.229798C11.2633,0.229798,11.2633,0.229798,11.2633,0.229798ZM9.6,21.8182C9.6,21.8182,14.4,21.8182,14.4,21.8182C14.4,21.8182,14.4,13.0909,14.4,13.0909C14.4,13.0909,9.6,13.0909,9.6,13.0909C9.6,13.0909,9.6,21.8182,9.6,21.8182C9.6,21.8182,9.6,21.8182,9.6,21.8182ZM16.8,21.8182C16.8,21.8182,16.8,12,16.8,12C16.8,11.3975,16.2628,10.9091,15.6,10.9091C15.6,10.9091,8.4,10.9091,8.4,10.9091C7.73726,10.9091,7.2,11.3975,7.2,12C7.2,12,7.2,21.8182,7.2,21.8182C7.2,21.8182,3.6,21.8182,3.6,21.8182C3.28174,21.8182,2.97652,21.7032,2.75147,21.4987C2.52643,21.2941,2.4,21.0166,2.4,20.7273C2.4,20.7273,2.4,9.26081,2.4,9.26081C2.4,9.26081,12,2.47294,12,2.47294C12,2.47294,21.6,9.26081,21.6,9.26081C21.6,9.26081,21.6,20.7273,21.6,20.7273C21.6,21.0166,21.4735,21.2941,21.2485,21.4987C21.0235,21.7032,20.7182,21.8182,20.4,21.8182C20.4,21.8182,16.8,21.8182,16.8,21.8182C16.8,21.8182,16.8,21.8182,16.8,21.8182Z" fill-rule="evenodd" fill-opacity="1"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
4
src/assets/svg/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200" height="200">
|
||||||
|
<path d="M810.554166 57.547768H213.848945c-86.156711 0-156.281057 70.126346-156.281057 156.280058v596.704221c0 86.208692 70.123347 156.279058 156.281057 156.279058h596.704221c86.207693 0 156.277059-70.070366 156.277059-156.279058V213.827826c0.001-86.152712-70.068367-156.280058-156.276059-156.280058z m71.037026 752.984279a71.061018 71.061018 0 0 1-71.034027 71.036027H213.848945a71.097005 71.097005 0 0 1-71.037026-71.036027v-56.26122L289.772254 567.359538a70.753126 70.753126 0 0 1 54.3289-27.106471c19.263228 0.2839 41.485415 8.524003 55.406521 24.66333l17.676785 20.513788a28.734898 28.734898 0 0 0 2.099262 2.499121l117.408724 136.73393a42.63901 42.63901 0 0 0 64.727244-55.522481l-95.696357-111.440821c52.28162-66.776524 112.577422-144.685134 125.248967-161.05238a70.752126 70.752126 0 0 1 54.210942-26.88055c22.901949 0.68176 41.430435 8.637963 55.29656 24.663329l137.18577 159.74384c1.30854 1.420501 2.554102 2.784021 3.920622 4.034582z m0-382.630481l-76.434128-88.993713a155.645281 155.645281 0 0 0-118.431364-54.380882c-1.140599 0-2.163239 0-3.240861 0.05698-46.768558 0.90968-90.300254 22.447108-116.045203 55.464501-1.249561 1.30754-4.598383 5.343122-5.682002 6.763622-10.396345 13.468265-64.043485 82.742911-113.714023 146.277574-29.09877-25.232129-65.976805-40.405795-105.817798-38.018634a155.684267 155.684267 0 0 0-119.450006 59.557062l-79.959889 101.664259v-402.458511a71.134992 71.134992 0 0 1 71.038026-71.037026h596.704221a71.099004 71.099004 0 0 1 71.033027 71.031028z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
2
src/assets/svg/password.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="27.428573608398438" height="32.000003814697266"
|
||||||
|
viewBox="0 0 27.428573608398438 32.000003814697266"><g><path d="M22.8571,10.6667L25.9048,10.6667C26.7463,10.6667,27.4286,11.3489,27.4286,12.1905L27.4286,30.4762C27.4286,31.3178,26.7463,32,25.9048,32L1.52381,32C0.682233,32,0,31.3178,0,30.4762L0,12.1905C0,11.3489,0.682233,10.6667,1.52381,10.6667L4.57143,10.6667L4.57143,9.14286C4.57143,4.0934,8.66483,0,13.7143,0C18.7637,0,22.8571,4.0934,22.8571,9.14286L22.8571,10.6667ZM19.8095,10.6667L19.8095,9.14286C19.8095,5.77655,17.0806,3.04762,13.7143,3.04762C10.348,3.04762,7.61905,5.77655,7.61905,9.14286L7.61905,10.6667L19.8095,10.6667ZM12.1905,19.8095L12.1905,22.8571L15.2381,22.8571L15.2381,19.8095L12.1905,19.8095ZM6.09524,19.8095L6.09524,22.8571L9.14286,22.8571L9.14286,19.8095L6.09524,19.8095ZM18.2857,19.8095L18.2857,22.8571L21.3333,22.8571L21.3333,19.8095L18.2857,19.8095Z" fill-opacity="1"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 966 B |
4
src/assets/svg/refresh.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200" height="200">
|
||||||
|
<path d="M782.954667 240.512C713.365333 170.901333 617.834667 127.701333 511.765333 127.701333 299.605333 127.701333 128.234667 299.541333 128.234667 511.701333 128.234667 723.861333 299.605333 895.701333 511.765333 895.701333 690.794667 895.701333 840.085333 773.312 882.794667 607.701333L782.954667 607.701333C743.594667 719.552 637.034667 799.701333 511.765333 799.701333 352.874667 799.701333 223.765333 670.592 223.765333 511.701333 223.765333 352.832 352.874667 223.701333 511.765333 223.701333 591.445333 223.701333 662.485333 256.832 714.325333 309.141333L559.765333 463.701333 895.765333 463.701333 895.765333 127.701333 782.954667 240.512Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 785 B |
4
src/assets/svg/setting.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="200" height="200">
|
||||||
|
<path d="M920.419872 418.785859l-76.229871 0c-7.654882-26.792088-18.339822-52.308363-31.735867-76.070394l54.859991-54.859991c24.718891-24.718891 24.718891-64.58807 0-89.306962l-44.653481-44.653481c-24.718891-24.718891-64.58807-24.718891-89.306962 0l-55.816851 55.816851c-22.32674-12.279707-46.248248-21.84831-71.286093-28.865286L606.25074 104.616726c0-34.765924-28.067902-62.833827-62.833827-62.833827l-62.833827 0c-34.765924 0-62.833827 28.067902-62.833827 62.833827l0 76.070394c-25.835228 7.335929-50.554119 17.542439-73.678243 30.300576l-57.411618-57.411618c-24.718891-24.718891-64.58807-24.718891-89.306962 0l-44.653481 44.653481c-24.718891 24.718891-24.718891 64.58807 0 89.306962l57.411618 57.411618c-12.917614 23.124124-22.964647 47.683538-30.300576 73.678243L103.580128 418.626382c-34.765924 0-62.833827 28.067902-62.833827 62.833827l0 62.833827c0 34.765924 28.067902 62.833827 62.833827 62.833827l76.229871 0c7.016976 24.878368 16.585579 48.959352 28.865286 71.286093l-55.816851 55.816851c-24.718891 24.718891-24.718891 64.58807 0 89.306962l44.653481 44.653481c24.718891 24.718891 64.58807 24.718891 89.306962 0l54.859991-54.859991c23.762031 13.555521 49.278306 24.240461 76.070394 31.735867l0 76.229871c0 34.765924 28.067902 62.833827 62.833827 62.833827l62.833827 0c34.765924 0 62.833827-28.067902 62.833827-62.833827L606.25074 845.2266c25.835228-7.335929 50.554119-17.542439 73.678243-30.300576l53.4247 53.4247c24.718891 24.718891 64.58807 24.718891 89.306962 0l44.653481-44.653481c24.718891-24.718891 24.718891-64.58807 0-89.306962L813.889425 680.965582c12.917614-23.124124 22.964647-47.683538 30.300576-73.678243l76.229871 0c34.765924 0 62.833827-28.227379 62.833827-62.833827L983.253699 481.619685C983.253699 447.013238 955.02632 418.785859 920.419872 418.785859M512 701.538078c-104.138296 0-188.50148-84.363183-188.50148-188.50148 0-104.138296 84.363183-188.50148 188.50148-188.50148 104.138296 0 188.50148 84.363183 188.50148 188.50148C700.50148 617.174895 616.138296 701.538078 512 701.538078M512 418.785859c-51.98941 0-94.25074 42.26133-94.25074 94.25074 0 51.98941 42.26133 94.25074 94.25074 94.25074 51.98941 0 94.25074-42.26133 94.25074-94.25074C606.25074 461.047189 563.98941 418.785859 512 418.785859"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
2
src/assets/svg/user.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="30.231319427490234" height="32"
|
||||||
|
viewBox="0 0 30.231319427490234 32"><g><path d="M29.0453,26.1146C28.2855,24.3136,27.192,22.6975,25.8051,21.3106C24.4182,19.9236,22.8021,18.8342,21.0011,18.0704C20.985,18.0623,20.9689,18.0583,20.9528,18.0503C23.4574,16.2412,25.0855,13.2945,25.0855,9.96985C25.0855,4.46231,20.6232,0,15.1157,0C9.60812,0,5.14581,4.46231,5.14581,9.96985C5.14581,13.2945,6.77395,16.2412,9.27847,18.0543C9.26239,18.0623,9.24631,18.0663,9.23023,18.0744C7.42923,18.8342,5.81315,19.9236,4.42621,21.3146C3.03928,22.7015,1.94983,24.3176,1.18601,26.1186C0.438272,27.8794,0.0402826,29.7487,0.0000815847,31.6704C-0.00393876,31.8513,0.140785,32,0.32169,32L2.73375,32C2.91063,32,3.05134,31.8593,3.05536,31.6864C3.13576,28.5829,4.38199,25.6764,6.58501,23.4734C8.8644,21.194,11.8915,19.9397,15.1157,19.9397C18.3398,19.9397,21.3669,21.194,23.6463,23.4734C25.8493,25.6764,27.0956,28.5829,27.176,31.6864C27.18,31.8633,27.3207,32,27.4976,32L29.9096,32C30.0905,32,30.2353,31.8513,30.2312,31.6704C30.191,29.7487,29.793,27.8794,29.0453,26.1146ZM15.1157,16.8844C13.2704,16.8844,11.5338,16.1648,10.2272,14.8583C8.92068,13.5518,8.20109,11.8151,8.20109,9.96985C8.20109,8.12462,8.92068,6.38794,10.2272,5.08141C11.5338,3.77487,13.2704,3.05528,15.1157,3.05528C16.9609,3.05528,18.6976,3.77487,20.0041,5.08141C21.3106,6.38794,22.0302,8.12462,22.0302,9.96985C22.0302,11.8151,21.3106,13.5518,20.0041,14.8583C18.6976,16.1648,16.9609,16.8844,15.1157,16.8844Z" fill-opacity="1"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
37
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineProps<{ msg: string }>()
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<button type="button" @click="count++">count is {{ count }}</button>
|
||||||
|
<p>
|
||||||
|
Edit
|
||||||
|
<code>components/HelloWorld.vue</code> to test HMR
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Check out
|
||||||
|
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the
|
||||||
|
official Vue + Vite starter
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Install
|
||||||
|
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||||
|
in your IDE for a better DX
|
||||||
|
</p>
|
||||||
|
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.read-the-docs {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
src/constants/Common.constants.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
const PRODUCTION_NAME = 'Framework'
|
||||||
|
const TOKEN_NAME = 'JWT_TOKEN'
|
||||||
|
const COLOR_PRODUCTION = '#409eff'
|
||||||
|
const COLOR_BACKGROUND = '#F5F5F5'
|
||||||
|
const COLOR_FONT_MAIN = '#4D4D4D'
|
||||||
|
const COLOR_FONT_SECONDARY = '#9E9E9E'
|
||||||
|
const SIZE_ICON_XS = '16px'
|
||||||
|
const SIZE_ICON_SM = '20px'
|
||||||
|
const SIZE_ICON_MD = '24px'
|
||||||
|
const SIZE_ICON_LG = '32px'
|
||||||
|
const SIZE_ICON_XL = '64px'
|
||||||
|
|
||||||
|
// Response Code
|
||||||
|
const SYSTEM_OK = 20000
|
||||||
|
const LOGIN_SUCCESS = 20010
|
||||||
|
const LOGIN_USERNAME_PASSWORD_ERROR = 20011
|
||||||
|
const OLD_PASSWORD_NOT_MATCH = 20012
|
||||||
|
const LOGOUT_SUCCESS = 20015
|
||||||
|
const LOGOUT_FAILED = 20016
|
||||||
|
const TOKEN_IS_ILLEGAL = 20017
|
||||||
|
const TOKEN_HAS_EXPIRED = 20018
|
||||||
|
const TOKEN_RENEW_SUCCESS = 20019
|
||||||
|
const DATABASE_SELECT_OK = 20021
|
||||||
|
const DATABASE_SAVE_OK = 20022
|
||||||
|
const DATABASE_UPDATE_OK = 20023
|
||||||
|
const DATABASE_DELETE_OK = 20024
|
||||||
|
const DATABASE_SELECT_ERROR = 20031
|
||||||
|
const DATABASE_SAVE_ERROR = 20032
|
||||||
|
const DATABASE_UPDATE_ERROR = 20033
|
||||||
|
const DATABASE_DELETE_ERROR = 20034
|
||||||
|
const DATABASE_TIMEOUT_ERROR = 20035
|
||||||
|
const DATABASE_CONNECT_ERROR = 20036
|
||||||
|
const DATABASE_DATA_TO_LONG = 20037
|
||||||
|
const DATABASE_DATA_VALIDATION_FAILED = 20038
|
||||||
|
const DATABASE_EXECUTE_ERROR = 20039
|
||||||
|
|
||||||
|
const UNAUTHORIZED = 30010
|
||||||
|
const ACCESS_DENIED = 30030
|
||||||
|
const USER_DISABLE = 30031
|
||||||
|
|
||||||
|
const SYSTEM_ERROR = 50001
|
||||||
|
const SYSTEM_TIMEOUT = 50002
|
||||||
|
|
||||||
|
export {
|
||||||
|
PRODUCTION_NAME,
|
||||||
|
TOKEN_NAME,
|
||||||
|
COLOR_PRODUCTION,
|
||||||
|
COLOR_BACKGROUND,
|
||||||
|
COLOR_FONT_MAIN,
|
||||||
|
COLOR_FONT_SECONDARY,
|
||||||
|
SIZE_ICON_XS,
|
||||||
|
SIZE_ICON_SM,
|
||||||
|
SIZE_ICON_MD,
|
||||||
|
SIZE_ICON_LG,
|
||||||
|
SIZE_ICON_XL,
|
||||||
|
SYSTEM_OK,
|
||||||
|
LOGIN_SUCCESS,
|
||||||
|
LOGIN_USERNAME_PASSWORD_ERROR,
|
||||||
|
OLD_PASSWORD_NOT_MATCH,
|
||||||
|
LOGOUT_SUCCESS,
|
||||||
|
LOGOUT_FAILED,
|
||||||
|
TOKEN_IS_ILLEGAL,
|
||||||
|
TOKEN_HAS_EXPIRED,
|
||||||
|
TOKEN_RENEW_SUCCESS,
|
||||||
|
DATABASE_SELECT_OK,
|
||||||
|
DATABASE_SAVE_OK,
|
||||||
|
DATABASE_UPDATE_OK,
|
||||||
|
DATABASE_DELETE_OK,
|
||||||
|
DATABASE_SELECT_ERROR,
|
||||||
|
DATABASE_SAVE_ERROR,
|
||||||
|
DATABASE_UPDATE_ERROR,
|
||||||
|
DATABASE_DELETE_ERROR,
|
||||||
|
DATABASE_TIMEOUT_ERROR,
|
||||||
|
DATABASE_CONNECT_ERROR,
|
||||||
|
DATABASE_DATA_TO_LONG,
|
||||||
|
DATABASE_DATA_VALIDATION_FAILED,
|
||||||
|
DATABASE_EXECUTE_ERROR,
|
||||||
|
UNAUTHORIZED,
|
||||||
|
ACCESS_DENIED,
|
||||||
|
USER_DISABLE,
|
||||||
|
SYSTEM_ERROR,
|
||||||
|
SYSTEM_TIMEOUT
|
||||||
|
}
|
||||||
15
src/main.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from '@/App.vue'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
import '@/assets/css/base.css'
|
||||||
|
import '@/assets/css/common.css'
|
||||||
|
|
||||||
|
import 'element-plus/theme-chalk/el-message.css'
|
||||||
|
import 'element-plus/theme-chalk/el-message-box.css'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
app.use(router).use(pinia).mount('#app')
|
||||||
5
src/pages/Home.vue
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script></script>
|
||||||
|
|
||||||
|
<template></template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
11
src/pages/Login.vue
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'LoginPage',
|
||||||
|
data() {},
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
370
src/pages/Main.vue
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
<template>
|
||||||
|
<el-backtop target=".main-box" :right="80" :bottom="80" />
|
||||||
|
<el-scrollbar style="height: 100vh; width: 100vw">
|
||||||
|
<div class="body">
|
||||||
|
<div class="background">
|
||||||
|
<el-container class="fill">
|
||||||
|
<el-aside width="collapse" class="background-white aside">
|
||||||
|
<el-scrollbar>
|
||||||
|
<el-menu
|
||||||
|
:collapse="isCollapsed"
|
||||||
|
:unique-opened="true"
|
||||||
|
:default-active="
|
||||||
|
this.$route.path.indexOf(
|
||||||
|
'/',
|
||||||
|
this.$route.path.indexOf('/') + 1
|
||||||
|
) !== -1
|
||||||
|
? this.$route.path.indexOf(
|
||||||
|
'/',
|
||||||
|
this.$route.path.indexOf(
|
||||||
|
'/',
|
||||||
|
this.$route.path.indexOf('/') + 1
|
||||||
|
) + 1
|
||||||
|
) !== -1
|
||||||
|
? this.$route.path.substring(
|
||||||
|
0,
|
||||||
|
this.$route.path.indexOf(
|
||||||
|
'/',
|
||||||
|
this.$route.path.indexOf(
|
||||||
|
'/',
|
||||||
|
this.$route.path.indexOf('/') + 1
|
||||||
|
) + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: this.$route.path
|
||||||
|
: this.$route.path
|
||||||
|
"
|
||||||
|
:router="true"
|
||||||
|
class="menu"
|
||||||
|
:text-color="COLOR_FONT_MAIN()"
|
||||||
|
:active-text-color="COLOR_PRODUCTION()"
|
||||||
|
>
|
||||||
|
<el-menu-item
|
||||||
|
@mousedown.left="changeCollapsed"
|
||||||
|
:disabled="true"
|
||||||
|
style="
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 1;
|
||||||
|
border-bottom: 1px #ddd solid;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-icon :size="SIZE_ICON_LG()">
|
||||||
|
<icon-framework-logo :color="COLOR_PRODUCTION()" />
|
||||||
|
</el-icon>
|
||||||
|
<template #title>
|
||||||
|
<span class="menu-production-name">
|
||||||
|
{{ PRODUCTION_NAME() }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<template v-for="(route, index) in routes">
|
||||||
|
<el-menu-item
|
||||||
|
v-if="!route.children"
|
||||||
|
:key="index"
|
||||||
|
:index="route.path ?? ''"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="route.meta.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<template #title>{{ route.meta.title }}</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-sub-menu
|
||||||
|
v-if="route.children"
|
||||||
|
:key="index"
|
||||||
|
:index="route.path ?? ''"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="route.meta.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ route.meta.title }}</span>
|
||||||
|
</template>
|
||||||
|
<el-menu-item
|
||||||
|
v-for="(sub, index) in route.children"
|
||||||
|
:key="index"
|
||||||
|
:index="
|
||||||
|
sub.path
|
||||||
|
? route.path
|
||||||
|
? route.path + '/' + sub.path
|
||||||
|
: ''
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<component :is="sub.meta.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<template #title>{{ sub.meta.title }}</template>
|
||||||
|
</el-menu-item>
|
||||||
|
</el-sub-menu>
|
||||||
|
</template>
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-aside>
|
||||||
|
<el-container>
|
||||||
|
<el-header height="56px" class="background-white main-header">
|
||||||
|
<template v-if="isLogin">
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-popover
|
||||||
|
transition="el-zoom-in-top"
|
||||||
|
popper-style="box-shadow: rgb(14 18 22 / 20%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px;"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<div style="display: flex">
|
||||||
|
<div class="user-head">
|
||||||
|
<el-avatar>
|
||||||
|
<el-icon
|
||||||
|
:size="SIZE_ICON_SM()"
|
||||||
|
:color="COLOR_FONT_MAIN()"
|
||||||
|
>
|
||||||
|
<icon-framework-user />
|
||||||
|
</el-icon>
|
||||||
|
</el-avatar>
|
||||||
|
</div>
|
||||||
|
<div class="user-info">
|
||||||
|
<div class="user-name">
|
||||||
|
<span>{{ username }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-desc">
|
||||||
|
<span>用户介绍</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div
|
||||||
|
style="display: flex; gap: 10px; flex-direction: column"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
@click="handleProfile"
|
||||||
|
style="width: 100%"
|
||||||
|
>个人档案</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-button @click="handleLogout" style="width: 100%"
|
||||||
|
>退出</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
<el-button type="primary" link v-else>登录</el-button>
|
||||||
|
</el-header>
|
||||||
|
<ElScrollbar v-if="$route.meta.requiresScrollbar">
|
||||||
|
<ElMain
|
||||||
|
class="main-box"
|
||||||
|
:class="{ noPadding: !$route.meta.requiresPadding }"
|
||||||
|
>
|
||||||
|
<ElBacktop :right="100" :bottom="100" />
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</ElMain>
|
||||||
|
</ElScrollbar>
|
||||||
|
<ElMain
|
||||||
|
v-else
|
||||||
|
class="main-box"
|
||||||
|
:class="{ noPadding: !$route.meta.requiresPadding }"
|
||||||
|
>
|
||||||
|
<ElBacktop :right="100" :bottom="100" />
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</ElMain>
|
||||||
|
</el-container>
|
||||||
|
</el-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
COLOR_FONT_MAIN,
|
||||||
|
COLOR_PRODUCTION,
|
||||||
|
PRODUCTION_NAME,
|
||||||
|
SIZE_ICON_LG,
|
||||||
|
SIZE_ICON_SM
|
||||||
|
} from '@/constants/Common.constants.js'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { getLoginStatus, getUser, getUsername, logout } from '@/utils/auth'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { getLocalStorage, setLocalStorage } from '@/utils/common'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MainFrame',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLogin: false,
|
||||||
|
routes: [],
|
||||||
|
isCollapsed: getLocalStorage('menuCollapsed') === 'true',
|
||||||
|
username: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
SIZE_ICON_LG() {
|
||||||
|
return SIZE_ICON_LG
|
||||||
|
},
|
||||||
|
PRODUCTION_NAME() {
|
||||||
|
return PRODUCTION_NAME
|
||||||
|
},
|
||||||
|
SIZE_ICON_SM() {
|
||||||
|
return SIZE_ICON_SM
|
||||||
|
},
|
||||||
|
COLOR_PRODUCTION() {
|
||||||
|
return COLOR_PRODUCTION
|
||||||
|
},
|
||||||
|
COLOR_FONT_MAIN() {
|
||||||
|
return COLOR_FONT_MAIN
|
||||||
|
},
|
||||||
|
handleLogout() {
|
||||||
|
logout()
|
||||||
|
ElMessage.success({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>退出登录</strong>'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
// this.$router.push({ name: 'Login' })
|
||||||
|
location.reload()
|
||||||
|
}, 1500)
|
||||||
|
},
|
||||||
|
changeCollapsed() {
|
||||||
|
this.isCollapsed = !this.isCollapsed
|
||||||
|
setLocalStorage('menuCollapsed', this.isCollapsed.toString())
|
||||||
|
},
|
||||||
|
handleProfile() {
|
||||||
|
this.$router.push('/profile')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
let user
|
||||||
|
|
||||||
|
if (getLoginStatus()) {
|
||||||
|
this.isLogin = true
|
||||||
|
this.username = await getUsername()
|
||||||
|
user = await getUser()
|
||||||
|
}
|
||||||
|
const allRoutes = _.cloneDeep(
|
||||||
|
_.filter(_.get(this.$router, 'options.routes[0].children'), 'meta.requiresMenu')
|
||||||
|
)
|
||||||
|
|
||||||
|
const menus = user?.menus
|
||||||
|
this.routes = allRoutes.filter((level1) => {
|
||||||
|
if (level1.meta.requiresMenuAuth) {
|
||||||
|
for (const menu of menus) {
|
||||||
|
if (_.startsWith(menu.url, level1.path)) {
|
||||||
|
let hasChildren = false
|
||||||
|
if (level1.children === undefined) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
level1.children = level1.children.filter((level2) => {
|
||||||
|
if (!level2.meta.requiresMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (const menu_ of menus) {
|
||||||
|
if (_.startsWith(menu_.url, level1.path + '/' + level2.path)) {
|
||||||
|
hasChildren = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return hasChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
let hasChildren = false
|
||||||
|
if (level1.children === undefined) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
level1.children = level1.children.filter((level2) => {
|
||||||
|
if (!level2.meta.requiresMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!level2.meta.requiresMenuAuth) {
|
||||||
|
hasChildren = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for (const menu_ of menus) {
|
||||||
|
if (_.startsWith(menu_.url, level1.path + '/' + level2.path)) {
|
||||||
|
hasChildren = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return hasChildren
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.background {
|
||||||
|
height: 100vh;
|
||||||
|
min-height: 500px;
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
border-right: 1px #ddd solid;
|
||||||
|
box-shadow: 0 0 1em #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu:not(.el-menu--collapse) {
|
||||||
|
width: 13vw;
|
||||||
|
min-width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-top > * {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-production-name {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: var(--main-color);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: right;
|
||||||
|
border-bottom: 1px #ddd solid;
|
||||||
|
box-shadow: 0 0 1em #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-header > *:not(:last-child) {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-head {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-head > span {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
color: var(--main-color);
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-desc {
|
||||||
|
color: var(--font-secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.noPadding {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
86
src/router/index.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { PRODUCTION_NAME } from '@/constants/Common.constants'
|
||||||
|
import { getLoginStatus, getUser } from '@/utils/auth'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: async () => await import('@/pages/Main.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirect: 'home'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
component: async () => await import('@/pages/Home.vue'),
|
||||||
|
name: 'home',
|
||||||
|
meta: {
|
||||||
|
title: '首页',
|
||||||
|
icon: shallowRef(IconFrameworkHome),
|
||||||
|
requiresMenu: true,
|
||||||
|
requiresScrollbar: false,
|
||||||
|
requiresPadding: true,
|
||||||
|
requiresAuth: false,
|
||||||
|
requiresMenuAuth: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
component: async () => await import('@/pages/Login.vue'),
|
||||||
|
name: 'Login',
|
||||||
|
meta: {
|
||||||
|
title: '登录',
|
||||||
|
requiresAuth: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
if (to.matched.length === 0) {
|
||||||
|
from.path !== '' ? next({ path: from.path }) : next('/')
|
||||||
|
} else {
|
||||||
|
if (to.meta.title !== '') {
|
||||||
|
document.title = `${PRODUCTION_NAME} - ${to.meta.title as string}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth === true) {
|
||||||
|
if (getLoginStatus()) {
|
||||||
|
if (to.name === 'Login') {
|
||||||
|
next('/')
|
||||||
|
} else {
|
||||||
|
if (to.meta.requiresAuth) {
|
||||||
|
const user = await getUser()
|
||||||
|
const menus = user.menus
|
||||||
|
for (const menu of menus) {
|
||||||
|
if (menu.url === '/') continue
|
||||||
|
if (_.startsWith(to.path, menu.url)) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next('/')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (to.name === 'Login') {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
next('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
148
src/services/index.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import axios, { type AxiosError, type AxiosRequestConfig, type AxiosResponse } from 'axios'
|
||||||
|
import jwtDecode from 'jwt-decode'
|
||||||
|
import { clearLocalStorage, getToken } from '@/utils/common'
|
||||||
|
import router from '@/router'
|
||||||
|
import {
|
||||||
|
ACCESS_DENIED,
|
||||||
|
DATABASE_DATA_TO_LONG,
|
||||||
|
DATABASE_DATA_VALIDATION_FAILED,
|
||||||
|
DATABASE_EXECUTE_ERROR,
|
||||||
|
TOKEN_HAS_EXPIRED,
|
||||||
|
TOKEN_IS_ILLEGAL,
|
||||||
|
UNAUTHORIZED
|
||||||
|
} from '@/constants/Common.constants'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const service = axios.create({
|
||||||
|
baseURL: 'http://localhost:8621',
|
||||||
|
timeout: 10000,
|
||||||
|
withCredentials: false
|
||||||
|
})
|
||||||
|
|
||||||
|
service.defaults.paramsSerializer = (params) => {
|
||||||
|
return Object.keys(params)
|
||||||
|
.filter((it) => {
|
||||||
|
return Object.prototype.hasOwnProperty.call(params, it)
|
||||||
|
})
|
||||||
|
.reduce((pre, curr) => {
|
||||||
|
return params[curr] !== null
|
||||||
|
? (pre !== '' ? pre + '&' : '') + curr + '=' + encodeURIComponent(params[curr])
|
||||||
|
: pre
|
||||||
|
}, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
service.interceptors.request.use(
|
||||||
|
async (config) => {
|
||||||
|
let token = getToken()
|
||||||
|
if (token != null) {
|
||||||
|
const jwt = jwtDecode(token)
|
||||||
|
if (
|
||||||
|
(jwt as any).exp * 1000 - new Date().getTime() < 1200000 &&
|
||||||
|
(jwt as any).exp * 1000 - new Date().getTime() > 0
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
await axios
|
||||||
|
.get('http://localhost:8621/token', {
|
||||||
|
headers: { token }
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const response = res.data
|
||||||
|
if (response.code === TOKEN_RENEW_SUCCESS) {
|
||||||
|
setToken(response.data?.token ?? '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
token = getToken()
|
||||||
|
config.headers.set('token', token)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
async (error) => {
|
||||||
|
return await Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
service.interceptors.response.use(
|
||||||
|
async (response) => {
|
||||||
|
switch (response.data.code) {
|
||||||
|
case UNAUTHORIZED:
|
||||||
|
case TOKEN_IS_ILLEGAL:
|
||||||
|
case TOKEN_HAS_EXPIRED:
|
||||||
|
clearLocalStorage()
|
||||||
|
ElMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>登录已过期</strong>'
|
||||||
|
})
|
||||||
|
setTimeout(function () {
|
||||||
|
router.go(0)
|
||||||
|
}, 1500)
|
||||||
|
throw response?.data
|
||||||
|
case ACCESS_DENIED:
|
||||||
|
ElMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>暂无权限操作</strong>'
|
||||||
|
})
|
||||||
|
throw response?.data
|
||||||
|
case DATABASE_DATA_TO_LONG:
|
||||||
|
ElMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>数据过长</strong>'
|
||||||
|
})
|
||||||
|
throw response?.data
|
||||||
|
case DATABASE_DATA_VALIDATION_FAILED:
|
||||||
|
ElMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>数据验证失败</strong>'
|
||||||
|
})
|
||||||
|
throw response?.data
|
||||||
|
case DATABASE_EXECUTE_ERROR:
|
||||||
|
ElMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>数据库执行出错</strong>'
|
||||||
|
})
|
||||||
|
throw response?.data
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
async (error) => {
|
||||||
|
ElMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: '<strong>服务器出错</strong>,请稍后重试'
|
||||||
|
})
|
||||||
|
return await Promise.reject(error?.response?.data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
async get<T>(url: string, data?: object): Promise<AxiosResponse<_Response<T>>> {
|
||||||
|
return await request.request('GET', url, { params: data })
|
||||||
|
},
|
||||||
|
async post<T>(url: string, data?: object): Promise<AxiosResponse<_Response<T>>> {
|
||||||
|
return await request.request('POST', url, { data })
|
||||||
|
},
|
||||||
|
async put<T>(url: string, data?: object): Promise<AxiosResponse<_Response<T>>> {
|
||||||
|
return await request.request('PUT', url, { data })
|
||||||
|
},
|
||||||
|
async delete<T>(url: string, data?: object): Promise<AxiosResponse<_Response<T>>> {
|
||||||
|
return await request.request('DELETE', url, { params: data })
|
||||||
|
},
|
||||||
|
async request<T>(
|
||||||
|
method = 'GET',
|
||||||
|
url: string,
|
||||||
|
data?: AxiosRequestConfig
|
||||||
|
): Promise<AxiosResponse<_Response<T>>> {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
service({ method, url, ...data })
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res as unknown as Promise<AxiosResponse<_Response<T>>>)
|
||||||
|
})
|
||||||
|
.catch((e: Error | AxiosError) => {
|
||||||
|
reject(e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request
|
||||||
0
src/store/index.ts
Normal file
69
src/utils/auth.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { clearLocalStorage, getCaptcha, getLocalStorage, setLocalStorage } from './common'
|
||||||
|
import { DATABASE_SELECT_OK, TOKEN_NAME } from '@/constants/Common.constants'
|
||||||
|
import request from '@/services'
|
||||||
|
import type { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
|
let captcha: Captcha
|
||||||
|
|
||||||
|
export async function login(
|
||||||
|
username: string,
|
||||||
|
password: string
|
||||||
|
): Promise<AxiosResponse<_Response<Token>>> {
|
||||||
|
return await request.post<Token>('/login', {
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout(): void {
|
||||||
|
request.get('/logout').finally(() => {
|
||||||
|
clearLocalStorage()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLoginStatus(): boolean {
|
||||||
|
return getLocalStorage(TOKEN_NAME) !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUser(): Promise<User> {
|
||||||
|
if (getLocalStorage('userInfo') !== null) {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
resolve(JSON.parse(getLocalStorage('userInfo') as string) as User)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await requestUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestUser(): Promise<User> {
|
||||||
|
let user: User | null
|
||||||
|
|
||||||
|
await request.get<User>('/user/info').then((value) => {
|
||||||
|
const response = value.data
|
||||||
|
if (response.code === DATABASE_SELECT_OK) {
|
||||||
|
user = response.data
|
||||||
|
setLocalStorage('userInfo', JSON.stringify(user))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return await new Promise<User>((resolve, reject) => {
|
||||||
|
if (user != null) {
|
||||||
|
resolve(user)
|
||||||
|
}
|
||||||
|
reject(user)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsername(): Promise<string> {
|
||||||
|
const user = await getUser()
|
||||||
|
|
||||||
|
return user.username
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCaptchaSrc(): string {
|
||||||
|
captcha = getCaptcha(300, 150, 4)
|
||||||
|
return captcha.base64Src
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyCaptcha(value: string): boolean {
|
||||||
|
return captcha.value.toLowerCase() === value.replace(/\s*/g, '').toLowerCase()
|
||||||
|
}
|
||||||
136
src/utils/common.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { TOKEN_NAME } from '@/constants/Common.constants'
|
||||||
|
|
||||||
|
export function getQueryVariable(variable: string): string | null {
|
||||||
|
const query = window.location.search.substring(1)
|
||||||
|
const vars = query.split('&')
|
||||||
|
for (const value of vars) {
|
||||||
|
const pair = value.split('=')
|
||||||
|
if (pair[0] === variable) {
|
||||||
|
return decodeURIComponent(pair[1].replace(/\+/g, ' '))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCookie(
|
||||||
|
name: string,
|
||||||
|
value: string,
|
||||||
|
daysToLive: number | null,
|
||||||
|
path: string | null
|
||||||
|
): void {
|
||||||
|
let cookie = name + '=' + encodeURIComponent(value)
|
||||||
|
|
||||||
|
if (typeof daysToLive === 'number') {
|
||||||
|
cookie = `${cookie}; max-age=${daysToLive * 24 * 60 * 60}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
cookie += '; path=' + path
|
||||||
|
}
|
||||||
|
|
||||||
|
document.cookie = cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setLocalStorage(name: string, value: string): void {
|
||||||
|
localStorage.setItem(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token: string): void {
|
||||||
|
setLocalStorage(TOKEN_NAME, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookie(name: string): string | null {
|
||||||
|
const cookieArr = document.cookie.split(';')
|
||||||
|
|
||||||
|
for (const cookie of cookieArr) {
|
||||||
|
const cookiePair = cookie.split('=')
|
||||||
|
if (cookiePair[0].trim() === name) {
|
||||||
|
return decodeURIComponent(cookiePair[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLocalStorage(name: string): string | null {
|
||||||
|
return localStorage.getItem(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToken(): string | null {
|
||||||
|
return getLocalStorage(TOKEN_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeCookie(name: string): void {
|
||||||
|
document.cookie = name + '=; max-age=0'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeLocalStorage(name: string): void {
|
||||||
|
localStorage.removeItem(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken(): void {
|
||||||
|
removeLocalStorage(TOKEN_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearLocalStorage(): void {
|
||||||
|
localStorage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCaptcha(width: number, high: number, num: number): Captcha {
|
||||||
|
const CHARTS = '23456789ABCDEFGHJKLMNPRSTUVWXYZabcdefghijklmnpqrstuvwxyz'.split('')
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
|
||||||
|
|
||||||
|
ctx.rect(0, 0, width, high)
|
||||||
|
ctx.clip()
|
||||||
|
|
||||||
|
ctx.fillStyle = randomColor(200, 250)
|
||||||
|
ctx.fillRect(0, 0, width, high)
|
||||||
|
|
||||||
|
for (let i = 0.05 * width * high; i > 0; i--) {
|
||||||
|
ctx.fillStyle = randomColor(0, 256)
|
||||||
|
ctx.fillRect(randomInt(0, width), randomInt(0, high), 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = `${high - 4}px Consolas`
|
||||||
|
ctx.fillStyle = randomColor(160, 200)
|
||||||
|
let value = ''
|
||||||
|
for (let i = 0; i < num; i++) {
|
||||||
|
const x = ((width - 10) / num) * i + 5
|
||||||
|
const y = high - 12
|
||||||
|
const r = Math.PI * randomFloat(-0.12, 0.12)
|
||||||
|
const ch = CHARTS[randomInt(0, CHARTS.length)]
|
||||||
|
value += ch
|
||||||
|
ctx.translate(x, y)
|
||||||
|
ctx.rotate(r)
|
||||||
|
ctx.fillText(ch, 0, 0)
|
||||||
|
ctx.rotate(-r)
|
||||||
|
ctx.translate(-x, -y)
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64Src = canvas.toDataURL('image/jpg')
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
base64Src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomInt(start: number, end: number): number {
|
||||||
|
if (start > end) {
|
||||||
|
const t = start
|
||||||
|
start = end
|
||||||
|
end = t
|
||||||
|
}
|
||||||
|
start = Math.ceil(start)
|
||||||
|
end = Math.floor(end)
|
||||||
|
return start + Math.floor(Math.random() * (end - start))
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomFloat(start: number, end: number): number {
|
||||||
|
return start + Math.random() * (end - start)
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomColor(start: number, end: number): string {
|
||||||
|
return `rgb(${randomInt(start, end)},${randomInt(start, end)},${randomInt(start, end)})`
|
||||||
|
}
|
||||||
44
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Captcha {
|
||||||
|
value: string
|
||||||
|
base64Src: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RouteHandle {
|
||||||
|
auth: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface _Response<T> {
|
||||||
|
code: number
|
||||||
|
msg: string
|
||||||
|
data: T | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Token {
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
menus: Menu[]
|
||||||
|
enable: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Menu {
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoginForm {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
12
tsconfig.app.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.vitest.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
8
tsconfig.node.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
|
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"types": ["node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
9
tsconfig.vitest.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.app.json",
|
||||||
|
"exclude": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"lib": [],
|
||||||
|
"types": ["node", "jsdom"]
|
||||||
|
}
|
||||||
|
}
|
||||||
86
vite.config.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
|
import IconsResolver from 'unplugin-icons/resolver'
|
||||||
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
import Icons from 'unplugin-icons/vite'
|
||||||
|
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
|
||||||
|
import Inspect from 'vite-plugin-inspect'
|
||||||
|
|
||||||
|
const pathSrc = path.resolve(__dirname, 'src')
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
AutoImport({
|
||||||
|
// 目标文件
|
||||||
|
include: [
|
||||||
|
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
|
||||||
|
/\.vue$/,
|
||||||
|
/\.vue\?vue/, // .vue
|
||||||
|
/\.md$/ // .md
|
||||||
|
],
|
||||||
|
// Auto import functions from Vue, e.g. ref, reactive, toRef...
|
||||||
|
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
|
||||||
|
imports: ['vue', 'vue-router'],
|
||||||
|
// eslint报错解决
|
||||||
|
eslintrc: {
|
||||||
|
enabled: false, // Default `false`
|
||||||
|
filepath: '.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
|
||||||
|
globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
|
||||||
|
},
|
||||||
|
// Auto import functions from Element Plus, e.g. ElMessage, ElMessageBox... (with style)
|
||||||
|
// 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
|
||||||
|
resolvers: [
|
||||||
|
// Auto import icon components
|
||||||
|
// 自动导入图标组件
|
||||||
|
IconsResolver({
|
||||||
|
prefix: 'icon',
|
||||||
|
alias: {
|
||||||
|
system: 'system-uicons'
|
||||||
|
},
|
||||||
|
enabledCollections: ['ep'],
|
||||||
|
customCollections: ['framework']
|
||||||
|
}),
|
||||||
|
ElementPlusResolver()
|
||||||
|
],
|
||||||
|
|
||||||
|
dts: path.resolve(pathSrc, 'auto-imports.d.ts')
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
resolvers: [
|
||||||
|
IconsResolver({
|
||||||
|
prefix: 'icon',
|
||||||
|
alias: {
|
||||||
|
system: 'system-uicons'
|
||||||
|
},
|
||||||
|
enabledCollections: ['ep'],
|
||||||
|
customCollections: ['framework']
|
||||||
|
}),
|
||||||
|
ElementPlusResolver()
|
||||||
|
],
|
||||||
|
|
||||||
|
dts: path.resolve(pathSrc, 'components.d.ts')
|
||||||
|
}),
|
||||||
|
Icons({
|
||||||
|
compiler: 'vue3',
|
||||||
|
autoInstall: true,
|
||||||
|
customCollections: {
|
||||||
|
framework: FileSystemIconLoader('src/assets/svg', (svg) =>
|
||||||
|
svg.replace(/^svg /, '<svg fill="currentColor"')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Inspect()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
15
vitest.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { mergeConfig } from 'vite'
|
||||||
|
import { configDefaults, defineConfig } from 'vitest/config'
|
||||||
|
import viteConfig from './vite.config'
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
viteConfig,
|
||||||
|
defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
exclude: [...configDefaults.exclude, 'e2e/*'],
|
||||||
|
root: fileURLToPath(new URL('./', import.meta.url))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||