Browse Source

Merge commit 'a1bc17ccd0b7d1539a6a9ed9eef5555afc058a73' into main

i18n
xiaoxian521 3 years ago
parent
commit
e6271c55a6
  1. 2
      .env
  2. 2
      .env.development
  3. 7
      .gitignore
  4. 2
      .husky/common.sh
  5. 16
      .husky/lintstagedrc.js
  6. 11
      .markdownlint.json
  7. 49
      .vscode/settings.json
  8. 22
      .vscode/vue3.0.code-snippets
  9. 20
      .vscode/vue3.2.setup-snippets
  10. 14
      README.en-US.md
  11. 10
      README.md
  12. 37
      index.html
  13. 3
      mock/asyncRoutes.ts
  14. 109
      package.json
  15. 6529
      pnpm-lock.yaml
  16. BIN
      public/favicon.ico
  17. 17
      public/serverConfig.json
  18. 8
      src/App.vue
  19. 2
      src/api/routes.ts
  20. BIN
      src/assets/401.gif
  21. BIN
      src/assets/bg.png
  22. BIN
      src/assets/car.png
  23. 26
      src/assets/iconfont/iconfont.css
  24. 44
      src/assets/iconfont/iconfont.js
  25. 38
      src/assets/iconfont/iconfont.json
  26. BIN
      src/assets/iconfont/iconfont.ttf
  27. BIN
      src/assets/iconfont/iconfont.woff
  28. BIN
      src/assets/iconfont/iconfont.woff2
  29. BIN
      src/assets/login.png
  30. 1
      src/assets/login/avatar.svg
  31. BIN
      src/assets/login/bg.png
  32. 1
      src/assets/login/illustration0.svg
  33. 1
      src/assets/login/illustration1.svg
  34. 1
      src/assets/login/illustration2.svg
  35. 1
      src/assets/login/illustration3.svg
  36. 1
      src/assets/login/illustration4.svg
  37. 1
      src/assets/login/illustration5.svg
  38. 1
      src/assets/login/illustration6.svg
  39. 1
      src/assets/svg/back_top.svg
  40. 195
      src/components/ReInfo/index.vue
  41. 148
      src/layout/components/appMain.vue
  42. 26
      src/layout/components/navbar.vue
  43. 25
      src/layout/components/panel/index.vue
  44. 416
      src/layout/components/setting/index.vue
  45. 6
      src/layout/components/sidebar/hamBurger.vue
  46. 25
      src/layout/components/sidebar/horizontal.vue
  47. 25
      src/layout/components/sidebar/logo.vue
  48. 111
      src/layout/components/sidebar/sidebarItem.vue
  49. 2
      src/layout/components/sidebar/vertical.vue
  50. 17
      src/layout/components/tag/index.vue
  51. 159
      src/layout/index.vue
  52. 12
      src/layout/theme/auroraGreen-vars.scss
  53. 24
      src/layout/theme/default-vars.scss
  54. 12
      src/layout/theme/dusk-vars.scss
  55. 11
      src/layout/theme/light-vars.scss
  56. 12
      src/layout/theme/mingQing-vars.scss
  57. 12
      src/layout/theme/pink-vars.scss
  58. 12
      src/layout/theme/saucePurple-vars.scss
  59. 12
      src/layout/theme/volcano-vars.scss
  60. 12
      src/layout/theme/yellow-vars.scss
  61. 7
      src/layout/types.ts
  62. 4
      src/main.ts
  63. 44
      src/plugins/element-plus/index.ts
  64. 75
      src/plugins/i18n/config.ts
  65. 1
      src/plugins/i18n/index.ts
  66. 119
      src/plugins/vxe-table/index.ts
  67. 10
      src/router/index.ts
  68. 2
      src/router/modules/error.ts
  69. 25
      src/router/modules/externalLink.ts
  70. 2
      src/router/modules/home.ts
  71. 12
      src/router/modules/remaining.ts
  72. 5
      src/store/modules/app.ts
  73. 4
      src/style/element-plus.scss
  74. 14
      src/style/index.scss
  75. 227
      src/style/login.css
  76. 322
      src/style/sidebar.scss
  77. 28
      src/utils/http/core.ts
  78. 13
      src/utils/storage/responsive.ts
  79. 184
      src/views/login.vue
  80. 2
      src/views/permission/button/index.vue
  81. 4
      src/views/permission/page/index.vue
  82. 17
      src/views/register.vue
  83. 4
      types/global.d.ts
  84. 80
      vite.config.ts
  85. 4583
      yarn.lock

2
.env

@ -3,7 +3,7 @@ VITE_PORT = 8848
# title # title
VITE_TITLE = vue-pure-admin VITE_TITLE = vue-pure-admin
# version # version
VITE_VERSION = 2.1.0
VITE_VERSION = 2.6.0
# open # open
VITE_OPEN = false VITE_OPEN = false

2
.env.development

@ -3,7 +3,7 @@ VITE_PORT = 8848
# title # title
VITE_TITLE = vue-pure-admin VITE_TITLE = vue-pure-admin
# version # version
VITE_VERSION = 2.1.0
VITE_VERSION = 2.6.0
# open # open
VITE_OPEN = false VITE_OPEN = false

7
.gitignore

@ -5,9 +5,10 @@ dist-ssr
*.local *.local
.eslintcache .eslintcache
yarn.lock
npm-debug.log* npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-error.log*
.pnpm-debug.log
tests/**/coverage/ tests/**/coverage/
# Editor directories and files # Editor directories and files
@ -15,4 +16,4 @@ tests/**/coverage/
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln
*.sln

2
.husky/common.sh

@ -3,7 +3,7 @@ command_exists () {
command -v "$1" >/dev/null 2>&1 command -v "$1" >/dev/null 2>&1
} }
# Workaround for Windows 10, Git Bash and Yarn
# Workaround for Windows 10, Git Bash and Pnpm
if command_exists winpty && test -t 1; then if command_exists winpty && test -t 1; then
exec < /dev/tty exec < /dev/tty
fi fi

16
.husky/lintstagedrc.js

@ -1,10 +1,10 @@
module.exports = { module.exports = {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"package.json": ["prettier --write"],
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
"*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"],
"*.md": ["prettier --write"]
// "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
// "{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
// "prettier --write--parser json"
// ],
// "package.json": ["prettier --write"],
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"]
// "*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"],
// "*.md": ["prettier --write"]
}; };

11
.markdownlint.json

@ -0,0 +1,11 @@
{
"default": true,
"MD003": false,
"MD033": false,
"MD013": false,
"MD001": false,
"MD025": false,
"MD024": false,
"MD007": { "indent": 4 },
"no-hard-tabs": false
}

49
.vscode/settings.json

@ -0,0 +1,49 @@
{
// You should install these plugins:
// ESLint
// Prettier - Code formatter
// stylelint
// vscode-icons
// TypeScript Vue Plugin (Volar)
// Vue Language Features (Volar)
"terminal.integrated.rendererType": "dom",
"editor.formatOnType": true,
"editor.formatOnSave": true,
"javascript.updateImportsOnFileMove.enabled": "always",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"editor.tabSize": 2,
"editor.formatOnPaste": true,
"files.autoSave": "afterDelay",
"git.confirmSync": false,
"workbench.startupEditor": "newUntitledFile",
"editor.suggestSelection": "first",
"editor.acceptSuggestionOnCommitCharacter": false,
"css.lint.propertyIgnoredDueToDisplay": "ignore",
// Prevent inline styles from being automatically formatted to all lowercase
"editor.quickSuggestions": {
"other": true,
"comments": true,
"strings": true
},
// Automatically fix some syntax errors of ts
"tslint.autoFixOnSave": true,
"files.associations": {
// Specifies the location of snippets in the suggestion widget
"editor.snippetSuggestions": "top"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"cSpell.userWords": ["sourcemap", "vite"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"volar.tsPlugin": true,
"typescript.tsdk": "node_modules/typescript/lib",
"i18n-ally.localesPaths": ["src/plugins/i18n"]
}

22
.vscode/vue3.0.code-snippets

@ -0,0 +1,22 @@
{
"Vue3.0快速生成模板": {
"prefix": "Vue3.0",
"body": [
"<template>",
"\t<div>\n",
"\t</div>",
"</template>\n",
"<script lang='ts'>",
"export default {",
"\tsetup(){",
"\t\treturn{\n\n\t\t}",
"\t},",
"}",
"</script>\n",
"<style scoped>\n",
"</style>",
"$2"
],
"description": "Vue3.0"
}
}

20
.vscode/vue3.2.setup-snippets

@ -0,0 +1,20 @@
{
"Vue3.2快速生成模板": {
"prefix": "Vue3.2",
"body": [
"<!-- $1 -->",
"<script setup lang='ts'>",
"\t$2",
"</script>\n",
"<template>",
"\t<div>",
"\t\t$3",
"\t</div>",
"</template>\n",
"<style scoped>",
"\t$4",
"</style>"
],
"description": "Vue3.2"
}
}

14
README.en-US.md

@ -1 +1,13 @@
<h1>vue-pure-admin精简版</h1>
<h1>vue-pure-admin Lite Edition</h1>
## introduce
The lite version is based on the shelf extracted from https://github.com/xiaoxian521/vue-pure-admin, which contains the main functions and is more suitable for actual project development
## Supporting video tutorial
bilibili: https://www.bilibili.com/video/BV1534y1S7HV/
## ⚠️ Note
The lite version does not accept any issues and prs. If you have any questions, please go to the full version https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose to mention it, thank you! ! !

10
README.md

@ -1,11 +1,13 @@
<h1>vue-pure-admin精简版</h1> <h1>vue-pure-admin精简版</h1>
## 介绍
精简版是基于 https://github.com/xiaoxian521/vue-pure-admin 提炼出的架子,包含主体功能,更适合实际项目开发
## 配套视频教程 ## 配套视频教程
bilibili:https://www.bilibili.com/video/BV1534y1S7HV/ bilibili:https://www.bilibili.com/video/BV1534y1S7HV/
## 交流群
[微信交流群,点击扫码进群](https://juejin.cn/post/6948419379566477342/)
## ⚠️ 注意
本人微信:18237613535,拉你进群
精简版不接受任何 issues 和 pr,如果有问题请到完整版 https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose 去提,谢谢!!!

37
index.html

@ -5,7 +5,7 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/iconfont.css" /> <link rel="stylesheet" href="/iconfont.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pure-Admin-Thin</title>
<title>vue-pure-admin</title>
<script> <script>
window.process = {}; window.process = {};
</script> </script>
@ -77,8 +77,43 @@
</style> </style>
<div class="g-container"> <div class="g-container">
<p>Pure-Admin</p> <p>Pure-Admin</p>
<span class="_develop"></span>
</div> </div>
</div> </div>
<script>
// 此代码仅用于开发环境的友好提示,项目打包前请去掉这段js代码 This code is only used as a friendly reminder of the development environment, please remove this js code before packaging the project
window.onload = function () {
(function () {
const ua = navigator.userAgent.toLowerCase();
const re = /(msie|firefox|chrome|opera|version).*?([\d.]+)/;
const m = ua.match(re);
const Sys = {
browser: m[1].replace(/version/, "'safari"),
version: m[2]
};
const browser = Array.of("chrome", "firefox").includes(Sys.browser);
const version = parseFloat(Sys.version);
const el = document.querySelector("._develop");
if (el) {
if (browser && version >= 90) {
let success =
document.createTextNode("当前浏览器版本很适合开发!!! 😃");
el.appendChild(success);
} else {
let warn = document.createTextNode(
"当前浏览器版本不适合开发,建议使用最新版本的谷歌或者火狐浏览器!!!😯"
);
el.appendChild(warn);
el.style.color = "red";
}
}
return Sys;
})();
};
</script>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

3
mock/asyncRoutes.ts

@ -1,13 +1,14 @@
// 根据角色动态生成路由 // 根据角色动态生成路由
import { MockMethod } from "vite-plugin-mock"; import { MockMethod } from "vite-plugin-mock";
// http://mockjs.com/examples.html#Object
const permissionRouter = { const permissionRouter = {
path: "/permission", path: "/permission",
name: "permission", name: "permission",
redirect: "/permission/page", redirect: "/permission/page",
meta: { meta: {
title: "message.permission", title: "message.permission",
icon: "el-icon-lollipop",
icon: "Lollipop",
showLink: true, showLink: true,
rank: 3 rank: 3
}, },

109
package.json

@ -1,29 +1,41 @@
{ {
"name": "vue-pure-admin", "name": "vue-pure-admin",
"version": "2.1.0",
"version": "2.6.0",
"private": true, "private": true,
"engines": {
"node": ">= 16",
"pnpm": ">= 6"
},
"scripts": { "scripts": {
"dev": "cross-env --max_old_space_size=4096 vite", "dev": "cross-env --max_old_space_size=4096 vite",
"serve": "yarn dev",
"serve": "pnpm dev",
"build": "rimraf dist && cross-env vite build", "build": "rimraf dist && cross-env vite build",
"preview": "vite preview", "preview": "vite preview",
"preview:build": "yarn build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && yarn cache clean && yarn",
"preview:build": "pnpm build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", "lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged", "lint:pretty": "pretty-quick --staged",
"lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:stylelint && yarn lint:pretty",
"prepare": "husky install"
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint && pnpm lint:pretty",
"prepare": "husky install",
"preinstall": "npx only-allow pnpm"
}, },
"browserslist": [
"> 1%",
"not ie 11",
"not op_mini all"
],
"dependencies": { "dependencies": {
"@vueuse/core": "^6.5.3",
"@element-plus/icons": "^0.0.11",
"@vueuse/core": "^6.7.1",
"@vueuse/motion": "^2.0.0-beta.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"await-to-js": "^3.0.0", "await-to-js": "^3.0.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"element-plus": "1.1.0-beta.24",
"element-plus": "1.2.0-beta.3",
"element-resize-detector": "^1.2.3", "element-resize-detector": "^1.2.3",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@ -33,58 +45,57 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "2.0.0-rc.10",
"pinia": "^2.0.0-rc.14",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^1.0.11", "responsive-storage": "^1.0.11",
"typescript-cookie": "^1.0.0", "typescript-cookie": "^1.0.0",
"vue": "^3.2.20",
"vue": "^3.2.21",
"vue-i18n": "^9.2.0-beta.3", "vue-i18n": "^9.2.0-beta.3",
"vue-router": "^4.0.11", "vue-router": "^4.0.11",
"vue-types": "^4.1.0",
"vxe-table": "^4.0.29",
"xe-utils": "3"
"vue-types": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@types/element-resize-detector": "^1.1.3",
"@types/mockjs": "^1.0.3",
"@types/node": "^14.14.14",
"@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"@vitejs/plugin-vue": "^1.6.0",
"@vitejs/plugin-vue-jsx": "^1.1.7",
"@vue/compiler-sfc": "^3.2.20",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"autoprefixer": "^10.2.4",
"babel-plugin-transform-remove-console": "^6.9.4",
"chalk": "^2.4.2",
"cross-env": "^7.0.3",
"eslint": "^7.30.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.17.0",
"husky": "^7.0.2",
"lint-staged": "^11.1.2",
"postcss": "^8.2.6",
"postcss-import": "^14.0.0",
"prettier": "^2.3.2",
"pretty-quick": "^3.1.1",
"rimraf": "^3.0.2",
"sass": "^1.38.0",
"sass-loader": "^12.1.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"typescript": "^4.4.2",
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@types/element-resize-detector": "1.1.3",
"@types/mockjs": "1.0.3",
"@types/node": "14.14.14",
"@types/nprogress": "0.2.0",
"@typescript-eslint/eslint-plugin": "4.31.0",
"@typescript-eslint/parser": "4.31.0",
"@vitejs/plugin-vue": "^1.9.4",
"@vitejs/plugin-vue-jsx": "^1.2.0",
"@vue/compiler-sfc": "^3.2.21",
"@vue/eslint-config-prettier": "6.0.0",
"@vue/eslint-config-typescript": "7.0.0",
"@zougt/vite-plugin-theme-preprocessor": "^1.3.12",
"autoprefixer": "10.2.4",
"babel-plugin-transform-remove-console": "6.9.4",
"chalk": "2.4.2",
"cross-env": "7.0.3",
"eslint": "7.30.0",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-vue": "7.17.0",
"husky": "7.0.2",
"lint-staged": "11.1.2",
"postcss": "8.2.6",
"postcss-import": "14.0.0",
"prettier": "2.3.2",
"pretty-quick": "3.1.1",
"rimraf": "3.0.2",
"sass": "^1.43.4",
"sass-loader": "^12.3.0",
"stylelint": "13.13.1",
"stylelint-config-prettier": "8.0.2",
"stylelint-config-standard": "22.0.0",
"stylelint-order": "4.1.0",
"typescript": "4.4.2",
"unplugin-element-plus": "^0.1.0", "unplugin-element-plus": "^0.1.0",
"vite": "^2.6.7",
"vite": "latest",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-style-import": "^1.2.1", "vite-plugin-style-import": "^1.2.1",
"vite-svg-loader": "^2.2.0", "vite-svg-loader": "^2.2.0",
"vue-eslint-parser": "^7.10.0"
"vue-eslint-parser": "7.10.0"
}, },
"repository": "[email protected]:xiaoxian521/vue-pure-admin.git", "repository": "[email protected]:xiaoxian521/vue-pure-admin.git",
"author": "xiaoxian521", "author": "xiaoxian521",

6529
pnpm-lock.yaml
File diff suppressed because it is too large
View File

BIN
public/favicon.ico

After

Width: 32  |  Height: 32  |  Size: 1.2 KiB

17
public/serverConfig.json

@ -1,9 +1,22 @@
{ {
"Version": "2.1.0",
"Version": "2.6.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,
"KeepAlive": true, "KeepAlive": true,
"Locale": "zh", "Locale": "zh",
"Layout": "vertical-dark"
"Layout": "vertical",
"Theme": "default",
"Grey": false,
"Weak": false,
"HideTabs": false,
"MapConfigure": {
"amapKey": "97b3248d1553172e81f168cf94ea667e",
"baiduKey": "wTHbkkEweiFqZLKunMIjcrb2RcqNXkhc",
"options": {
"resizeEnable": true,
"center": [113.6401, 34.72468],
"zoom": 12
}
}
} }

8
src/App.vue

@ -14,14 +14,8 @@ export default {
[ElConfigProvider.name]: ElConfigProvider [ElConfigProvider.name]: ElConfigProvider
}, },
computed: { computed: {
// eslint-disable-next-line vue/return-in-computed-property
currentLocale() { currentLocale() {
switch (this.$storage.locale?.locale) {
case "zh":
return zhCn;
case "en":
return en;
}
return this.$storage.locale?.locale === "zh" ? zhCn : en;
} }
} }
}; };

2
src/api/routes.ts

@ -1,4 +1,4 @@
import { http } from "/@/utils/http";
import { http } from "../utils/http";
export const getAsyncRoutes = (data?: object) => { export const getAsyncRoutes = (data?: object) => {
return http.request("get", "/getAsyncRoutes", data); return http.request("get", "/getAsyncRoutes", data);

BIN
src/assets/401.gif

Before

Width: 313  |  Height: 428  |  Size: 160 KiB

After

Width: 313  |  Height: 428  |  Size: 680 KiB

BIN
src/assets/bg.png

Before

Width: 1920  |  Height: 1080  |  Size: 1.1 MiB

BIN
src/assets/car.png

After

Width: 36  |  Height: 20  |  Size: 1.4 KiB

26
src/assets/iconfont/iconfont.css

@ -1,8 +1,8 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 2208059 */ font-family: "iconfont"; /* Project id 2208059 */
src: url("iconfont.woff2?t=1634092870259") format("woff2"),
url("iconfont.woff?t=1634092870259") format("woff"),
url("iconfont.ttf?t=1634092870259") format("truetype");
src: url("iconfont.woff2?t=1636197082361") format("woff2"),
url("iconfont.woff?t=1636197082361") format("woff"),
url("iconfont.ttf?t=1636197082361") format("truetype");
} }
.iconfont { .iconfont {
@ -13,8 +13,8 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.team-iconzuixinlianzai::before {
content: "\e6da";
.team-iconlogo::before {
content: "\e620";
} }
.team-iconxinpin::before { .team-iconxinpin::before {
@ -25,22 +25,6 @@
content: "\e615"; content: "\e615";
} }
.team-iconinternationality::before {
content: "\e67a";
}
.team-iconshanchu::before {
content: "\e617";
}
.team-iconshow-main-container::before {
content: "\e878";
}
.team-iconhidden-main-container::before {
content: "\e881";
}
.team-iconexit-fullscreen::before { .team-iconexit-fullscreen::before {
content: "\e62a"; content: "\e62a";
} }

44
src/assets/iconfont/iconfont.js
File diff suppressed because it is too large
View File

38
src/assets/iconfont/iconfont.json

@ -6,11 +6,11 @@
"description": "pure-admin", "description": "pure-admin",
"glyphs": [ "glyphs": [
{ {
"icon_id": "2508809",
"name": "最新连载",
"font_class": "zuixinlianzai",
"unicode": "e6da",
"unicode_decimal": 59098
"icon_id": "22129506",
"name": "水能",
"font_class": "logo",
"unicode": "e620",
"unicode_decimal": 58912
}, },
{ {
"icon_id": "7795613", "icon_id": "7795613",
@ -27,34 +27,6 @@
"unicode_decimal": 58901 "unicode_decimal": 58901
}, },
{ {
"icon_id": "18367956",
"name": "中英文2 中文",
"font_class": "internationality",
"unicode": "e67a",
"unicode_decimal": 59002
},
{
"icon_id": "6184565",
"name": "删除",
"font_class": "shanchu",
"unicode": "e617",
"unicode_decimal": 58903
},
{
"icon_id": "9626913",
"name": "全屏",
"font_class": "show-main-container",
"unicode": "e878",
"unicode_decimal": 59512
},
{
"icon_id": "9626952",
"name": "退出全屏",
"font_class": "hidden-main-container",
"unicode": "e881",
"unicode_decimal": 59521
},
{
"icon_id": "5698509", "icon_id": "5698509",
"name": "全屏缩小", "name": "全屏缩小",
"font_class": "exit-fullscreen", "font_class": "exit-fullscreen",

BIN
src/assets/iconfont/iconfont.ttf

BIN
src/assets/iconfont/iconfont.woff

BIN
src/assets/iconfont/iconfont.woff2

BIN
src/assets/login.png

Before

Width: 607  |  Height: 716  |  Size: 9.9 KiB

1
src/assets/login/avatar.svg

@ -0,0 +1 @@
<svg t="1636193306629" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1847" width="32" height="32"><path d="M410.558481 0.10861C410.558481 211.083075 109.682285 361.860579 109.682285 633.656511c0 174.943176 134.703259 316.787527 300.876196 316.787527s300.876197-141.817198 300.876197-316.787527C711.407525 361.751969 410.558481 210.974465 410.558481 0.10861z" fill="#386BF3" p-id="1848"></path><path d="M613.468671 73.664572c0 211.055922-300.876197 361.914883-300.876196 633.547901 0 174.943176 134.703259 316.787527 300.876196 316.787527s300.876197-141.817198 300.876197-316.787527c-0.054305-271.633018-300.876197-422.491979-300.876197-633.547901z" fill="#C3D2FB" p-id="1849"></path><path d="M312.592475 707.212473c0-183.713414 137.635722-312.171612 226.72288-441.390078 81.701694 106.111739 172.119322 218.740063 172.119323 367.725506a309.755045 309.755045 0 0 1-291.074166 316.516003 323.114046 323.114046 0 0 1-107.768037-242.851431z" fill="#303F5B" p-id="1850"></path></svg>

BIN
src/assets/login/bg.png

After

Width: 1920  |  Height: 1080  |  Size: 17 KiB

1
src/assets/login/illustration0.svg
File diff suppressed because it is too large
View File

1
src/assets/login/illustration1.svg
File diff suppressed because it is too large
View File

1
src/assets/login/illustration2.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="500" height="380" viewBox="0 0 896 529.1129" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M832.06729,623.22778s-26.37759,9.89441-38.806,32.94348S787.06,706.69574,787.06,706.69574s26.37759-9.89447,38.806-32.94348S832.06729,623.22778,832.06729,623.22778Z" transform="translate(-158 -185.8871)" fill="#3f3d56"/><path d="M867.5,657.59637s-8.64182,26.814-31.0802,40.31373-50.17651,8.57293-50.17651,8.57293,8.64175-26.81408,31.08017-40.31378S867.5,657.59637,867.5,657.59637Z" transform="translate(-158 -185.8871)" fill="#5392f0"/><rect y="527.1129" width="896" height="2" fill="#2f2e41"/><path d="M519.87238,620.97461a95.44448,95.44448,0,0,1-35.748-14.44629L485.306,604.915a93.36283,93.36283,0,0,0,34.999,14.10547c18.93164,3.40137,47.26075,1.73144,74.707-25.52735,53.41358-53.04785,104.39307-58.39062,104.90186-58.43847l.18652,1.99219c-.50146.04687-50.76806,5.31738-103.67822,57.86621-21.61328,21.46386-43.792,27.40234-61.71777,27.40234A83.49962,83.49962,0,0,1,519.87238,620.97461Z" transform="translate(-158 -185.8871)" fill="#2f2e41"/><circle cx="515.15271" cy="381.1129" r="12" fill="#2f2e41"/><circle cx="430.15271" cy="437.1129" r="12" fill="#2f2e41"/><path d="M841.5,714s-17.46191-5.41315-52.26129-10.84192L790,692.5c6-60-34-150-34-150a401.561,401.561,0,0,1,21.4693,139.0246C772.13214,672.2124,761.82056,662.16638,742,656c0,0,25.77765,22.106,33.15918,45.10175a997.84042,997.84042,0,0,0-102.02258-8.21589L682,672.5l-17,17s-7-51-22-53l11,50s-13-10-16-9l7.39746,14.79486c-49.819-.51654-109.08453,1.7356-177.76581,8.95227L476,682l-17,17s-7-51-22-53l11,50s-13-10-16-9l8.64288,17.28583Q406.9763,708.2897,370.5,714Z" transform="translate(-158 -185.8871)" fill="#3f3d56"/><path d="M565.64813,230.37817c-10.89964,11.74783,17.59745,40.25959,17.59745,40.25959s-57.70662,9.73051-53.12783,9.14083,2.20622-49.13151,2.20622-49.13151S576.54777,218.63035,565.64813,230.37817Z" transform="translate(-158 -185.8871)" fill="#a0616a"/><path d="M605.81236,356.10945l-50.139,25.6141-27.22969,15.6059s-32.09862,40.43116-38.08709,64.39234,25.92963,68.247,29.54371,72.82286a54.36088,54.36088,0,0,1,4.98908,7.42355c1.24727,1.85589,12.02944-.541,23.80342-3.06554S547.13,518.93875,547.13,518.93875s-15.02732-38.39505-16.14686-39.25912c-1.04554-.807-4.60093-7.44631-2.04309-10.35234a25.94993,25.94993,0,0,0,5.44489-8.89825,30.09064,30.09064,0,0,1,4.18709-7.94151s45.361-36.83645,59.52776-49.37835,51.82952-4.65839,51.82952-4.65839-17.78167,68.20027-22.22979,72.80616-4.929,8.70085-2.91535,16.50759,28.28157.39078,28.28157.39078L662.766,461.6996s15.74879-34.2925,24.29946-69.67451c4.27533-17.691-3.88828-28.23462-13.12073-34.35549a41.39094,41.39094,0,0,0-30.02983-5.97766l-46.34848,8.1308,32.14706-13.84923Z" transform="translate(-158 -185.8871)" fill="#2f2e41"/><path d="M420.87777,290.19133,361.02366,271.685s-24.179-31.16689-12.78824-36.6669,25.65172,26.94419,25.65172,26.94419l41.686,2.69751Z" transform="translate(-158 -185.8871)" fill="#a0616a"/><path d="M672.49431,257.78673l53.2121-33.06768s15.49333-36.27612,3.0807-38.71059-17.98787,32.56435-17.98787,32.56435l-39.64232,13.17143Z" transform="translate(-158 -185.8871)" fill="#a0616a"/><path d="M682.45318,220.40023l1.01427,39.19147-89.68779,16.025c13.19231,28.22441,9.84118,60.34675,43.04725,74.4259L524.9027,404.78717c4.9871-43.03806-15.81748-75.456-35.263-115.75876-23.68547-8.58589-51.19594-2.29078-80.33649,10.34619l-5.237-40.66416,123.87841-8.896,20.34848,7.77932,21.81842-9.17677C602.17891,238.88953,648.22076,220.77584,682.45318,220.40023Z" transform="translate(-158 -185.8871)" fill="#5392f0"/><path d="M626.64006,486.51727c-2.72,2.36681-5.25213,21.84984-5.34982,28.92023s9.21178,8.89624,14.29855,9.2494,4.47816,3.45631,7.83678,6.04854,14.39625,2.179,28.89019-2.71238-9.75274-20.92568-11.86409-21.662-11-22.78156-11-22.78156S629.36,484.15046,626.64006,486.51727Z" transform="translate(-158 -185.8871)" fill="#2f2e41"/><path d="M547.368,531.00717c3.23089,1.60043,10.61681,19.80614,12.50274,26.62107s-6.65716,10.93994-11.48848,12.5704-3.45631,4.47816-6.04855,7.83678-13.3744,5.75546-28.63472,4.696,4.13258-22.71391,5.98847-23.96118,4.86893-24.82526,4.86893-24.82526S544.13715,529.40674,547.368,531.00717Z" transform="translate(-158 -185.8871)" fill="#2f2e41"/><circle cx="389.47074" cy="35.42904" r="23.99585" fill="#a0616a"/><path d="M519.73448,218.90923a22.82668,22.82668,0,0,1-.83378-18.59281c2.35891-5.8153,7.59174-11.65569,18.87309-13.4,24.61387-3.80572,37.71267,13.43543,37.02452,19.07449s-3.99294,19.27051-3.99294,19.27051,1.47587-12.90619-4.85883-13.362-30.90178-2.37835-37.12217,4.145a14.23268,14.23268,0,0,0-3.71042,13.82977Z" transform="translate(-158 -185.8871)" fill="#2f2e41"/></svg>

1
src/assets/login/illustration3.svg
File diff suppressed because it is too large
View File

1
src/assets/login/illustration4.svg
File diff suppressed because it is too large
View File

1
src/assets/login/illustration5.svg
File diff suppressed because it is too large
View File

1
src/assets/login/illustration6.svg
File diff suppressed because it is too large
View File

1
src/assets/svg/back_top.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2.88 18.054a35.897 35.897 0 0 1 8.531-16.32.8.8 0 0 1 1.178 0c.166.18.304.332.413.455a35.897 35.897 0 0 1 8.118 15.865c-2.141.451-4.34.747-6.584.874l-2.089 4.178a.5.5 0 0 1-.894 0l-2.089-4.178a44.019 44.019 0 0 1-6.584-.874zm6.698-1.123l1.157.066L12 19.527l1.265-2.53 1.157-.066a42.137 42.137 0 0 0 4.227-.454A33.913 33.913 0 0 0 12 4.09a33.913 33.913 0 0 0-6.649 12.387c1.395.222 2.805.374 4.227.454zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg>

195
src/components/ReInfo/index.vue

@ -1,195 +0,0 @@
<script setup lang="ts">
import { ref, PropType, getCurrentInstance, watch, nextTick, toRef } from "vue";
import { useRouter, useRoute } from "vue-router";
import { initRouter } from "/@/router";
import { storageSession } from "/@/utils/storage";
export interface ContextProps {
userName: string;
passWord: string;
verify: number | null;
svg: any;
telephone?: number;
dynamicText?: string;
}
const props = defineProps({
ruleForm: {
type: Object as PropType<ContextProps>
}
});
const emit = defineEmits<{
(e: "onBehavior", evt: Object): void;
(e: "refreshVerify"): void;
}>();
const instance = getCurrentInstance();
const model = toRef(props, "ruleForm");
let tips = ref<string>("注册");
let tipsFalse = ref<string>("登录");
const route = useRoute();
const router = useRouter();
watch(
route,
async ({ path }): Promise<void> => {
await nextTick();
path.includes("register")
? (tips.value = "登录") && (tipsFalse.value = "注册")
: (tips.value = "注册") && (tipsFalse.value = "登录");
},
{ immediate: true }
);
const rules = ref<any>({
userName: [{ required: true, message: "请输入用户名", trigger: "blur" }],
passWord: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 6, message: "密码长度必须不小于6位", trigger: "blur" }
],
verify: [
{ required: true, message: "请输入验证码", trigger: "blur" },
{ type: "number", message: "验证码必须是数字类型", trigger: "blur" }
]
});
//
const onBehavior = (evt: Object): void => {
// @ts-expect-error
instance.refs.ruleForm.validate((valid: boolean) => {
if (valid) {
emit("onBehavior", evt);
} else {
return false;
}
});
};
//
const refreshVerify = (): void => {
emit("refreshVerify");
};
//
const resetForm = (): void => {
// @ts-expect-error
instance.refs.ruleForm.resetFields();
};
//
const changPage = (): void => {
tips.value === "注册" ? router.push("/register") : router.push("/login");
};
const noSecret = (): void => {
storageSession.setItem("info", {
username: "admin",
accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
});
initRouter("admin").then(() => {});
router.push("/");
};
</script>
<template>
<div class="info">
<el-form :model="model" :rules="rules" ref="ruleForm" class="rule-form">
<el-form-item prop="userName">
<el-input
clearable
v-model="model.userName"
placeholder="请输入用户名"
prefix-icon="el-icon-user"
></el-input>
</el-form-item>
<el-form-item prop="passWord">
<el-input
clearable
type="password"
show-password
v-model="model.passWord"
placeholder="请输入密码"
prefix-icon="el-icon-lock"
></el-input>
</el-form-item>
<el-form-item prop="verify">
<el-input
maxlength="2"
onkeyup="this.value=this.value.replace(/[^\d.]/g,'');"
v-model.number="model.verify"
placeholder="请输入验证码"
></el-input>
<span
class="verify"
title="刷新"
v-html="model.svg"
@click.prevent="refreshVerify"
></span>
</el-form-item>
<el-form-item>
<el-button type="primary" @click.prevent="onBehavior">{{
tipsFalse
}}</el-button>
<el-button @click="resetForm">重置</el-button>
<span class="tips" @click="changPage">{{ tips }}</span>
</el-form-item>
<span title="测试用户 直接登录" class="secret" @click="noSecret"
>免密登录</span
>
</el-form>
</div>
</template>
<style lang="scss" scoped>
.info {
width: 30vw;
height: 48vh;
background: url("../../assets/login.png") no-repeat center;
background-size: cover;
position: absolute;
border-radius: 20px;
right: 100px;
top: 30vh;
display: flex;
justify-content: center;
align-items: center;
@media screen and (max-width: 750px) {
width: 88vw;
right: 25px;
top: 22vh;
}
.rule-form {
width: 80%;
.verify {
position: absolute;
margin: -10px 0 0 -120px;
&:hover {
cursor: pointer;
}
}
.tips {
color: #409eff;
float: right;
&:hover {
cursor: pointer;
}
}
}
.secret {
color: #409eff;
&:hover {
cursor: pointer;
}
}
}
</style>

148
src/layout/components/appMain.vue

@ -1,52 +1,130 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, getCurrentInstance } from "vue";
import {
h,
ref,
computed,
Transition,
defineComponent,
getCurrentInstance
} from "vue";
import { RouterView } from "vue-router";
import backTop from "/@/assets/svg/back_top.svg";
import { usePermissionStoreHook } from "/@/store/modules/permission"; import { usePermissionStoreHook } from "/@/store/modules/permission";
const props = defineProps({
fixedHeader: Boolean
});
const keepAlive: Boolean = ref( const keepAlive: Boolean = ref(
getCurrentInstance().appContext.config.globalProperties.$config?.KeepAlive getCurrentInstance().appContext.config.globalProperties.$config?.KeepAlive
); );
const instance =
getCurrentInstance().appContext.app.config.globalProperties.$storage;
const transition = computed(() => {
const transitions = computed(() => {
return route => { return route => {
return route.meta.transition; return route.meta.transition;
}; };
}); });
const hideTabs = computed(() => {
return instance?.sets.hideTabs;
});
const layout = computed(() => {
return instance?.layout.layout === "vertical";
});
const transitionMain = defineComponent({
render() {
return h(
Transition,
{
name:
transitions.value(this.route) &&
this.route.meta.transition.enterTransition
? "pure-classes-transition"
: (transitions.value(this.route) &&
this.route.meta.transition.name) ||
"fade-transform",
enterActiveClass:
transitions.value(this.route) &&
`animate__animated ${this.route.meta.transition.enterTransition}`,
leaveActiveClass:
transitions.value(this.route) &&
`animate__animated ${this.route.meta.transition.leaveTransition}`,
mode: "out-in",
appear: true
},
{
default: () => [this.$slots.default()]
}
);
},
props: {
route: {
type: undefined,
required: true
}
}
});
</script> </script>
<template> <template>
<section class="app-main">
<el-scrollbar>
<router-view>
<template #default="{ Component, route }">
<transition
:name="
transition(route) && route.meta.transition.enterTransition
? 'pure-classes-transition'
: (transition(route) && route.meta.transition.name) ||
'fade-transform'
"
:enter-active-class="
transition(route) &&
`animate__animated ${route.meta.transition.enterTransition}`
"
:leave-active-class="
transition(route) &&
`animate__animated ${route.meta.transition.leaveTransition}`
"
mode="out-in"
appear
>
<section
:class="[props.fixedHeader ? 'app-main' : 'app-main-nofixed-header']"
:style="[
hideTabs && layout ? 'padding-top: 48px;' : '',
!hideTabs && layout ? 'padding-top: 85px;' : '',
hideTabs && !layout ? 'padding-top: 62px' : '',
!hideTabs && !layout ? 'padding-top: 98px;' : ''
]"
>
<router-view>
<template #default="{ Component, route }">
<el-scrollbar v-if="props.fixedHeader">
<el-backtop title="回到顶部" target=".app-main .el-scrollbar__wrap">
<backTop />
</el-backtop>
<transitionMain :route="route">
<keep-alive <keep-alive
v-if="keepAlive" v-if="keepAlive"
:include="usePermissionStoreHook().cachePageList" :include="usePermissionStoreHook().cachePageList"
> >
<component :is="Component" :key="route.fullPath" />
<component
:is="Component"
:key="route.fullPath"
class="main-content"
/>
</keep-alive> </keep-alive>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
</router-view>
</el-scrollbar>
<component
v-else
:is="Component"
:key="route.fullPath"
class="main-content"
/>
</transitionMain>
</el-scrollbar>
<div v-else>
<transitionMain :route="route">
<keep-alive
v-if="keepAlive"
:include="usePermissionStoreHook().cachePageList"
>
<component
:is="Component"
:key="route.fullPath"
class="main-content"
/>
</keep-alive>
<component
v-else
:is="Component"
:key="route.fullPath"
class="main-content"
/>
</transitionMain>
</div>
</template>
</router-view>
</section> </section>
</template> </template>
@ -57,4 +135,14 @@ const transition = computed(() => {
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
} }
.app-main-nofixed-header {
width: 100%;
min-height: 100vh;
position: relative;
}
.main-content {
margin: 24px;
}
</style> </style>

26
src/layout/components/navbar.vue

@ -78,6 +78,8 @@ function translationEn() {
color: locale === 'zh' ? '#f4f4f5' : '#000' color: locale === 'zh' ? '#f4f4f5' : '#000'
}" }"
@click="translationCh" @click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
>简体中文</el-dropdown-item >简体中文</el-dropdown-item
> >
<el-dropdown-item <el-dropdown-item
@ -86,6 +88,8 @@ function translationEn() {
color: locale === 'en' ? '#f4f4f5' : '#000' color: locale === 'en' ? '#f4f4f5' : '#000'
}" }"
@click="translationEn" @click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
>English</el-dropdown-item >English</el-dropdown-item
> >
</el-dropdown-menu> </el-dropdown-menu>
@ -107,11 +111,13 @@ function translationEn() {
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<i
<el-icon
class="el-icon-setting" class="el-icon-setting"
:title="$t('message.hssystemSet')" :title="$t('message.hssystemSet')"
@click="onPanel" @click="onPanel"
></i>
>
<Setting />
</el-icon>
</div> </div>
</div> </div>
</template> </template>
@ -191,8 +197,8 @@ function translationEn() {
.el-icon-setting { .el-icon-setting {
height: 48px; height: 48px;
width: 40px;
padding: 11px;
width: 38px;
padding: 12px;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
align-items: center; align-items: center;
@ -218,6 +224,18 @@ function translationEn() {
color: #606266; color: #606266;
background: #f0f0f0; background: #f0f0f0;
} }
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
} }
.logout { .logout {

25
src/layout/components/panel/index.vue

@ -1,33 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { useEventListener, onClickOutside } from "@vueuse/core";
import { onClickOutside } from "@vueuse/core";
import { emitter } from "/@/utils/mitt"; import { emitter } from "/@/utils/mitt";
let show = ref<Boolean>(false); let show = ref<Boolean>(false);
const target = ref(null); const target = ref(null);
onClickOutside(target, () => {
onClickOutside(target, event => {
if (event.clientX > target.value.offsetLeft) return;
show.value = false; show.value = false;
}); });
const addEventClick = (): void => {
useEventListener("click", closeSidebar);
};
const closeSidebar = (evt: any): void => {
const parent = evt.target.closest(".right-panel");
if (!parent) {
show.value = false;
window.removeEventListener("click", closeSidebar);
}
};
emitter.on("openPanel", () => { emitter.on("openPanel", () => {
show.value = true; show.value = true;
}); });
defineExpose({
addEventClick
});
</script> </script>
<template> <template>
@ -37,7 +22,9 @@ defineExpose({
<div class="right-panel-items"> <div class="right-panel-items">
<div class="project-configuration"> <div class="project-configuration">
<h3>项目配置</h3> <h3>项目配置</h3>
<i class="el-icon-close" @click="show = !show"></i>
<el-icon title="关闭配置" class="el-icon-close" @click="show = !show">
<Close />
</el-icon>
</div> </div>
<div style="border-bottom: 1px solid #dcdfe6"></div> <div style="border-bottom: 1px solid #dcdfe6"></div>
<slot /> <slot />

416
src/layout/components/setting/index.vue

@ -1,52 +1,84 @@
<script setup lang="ts"> <script setup lang="ts">
import { split } from "lodash-es";
import panel from "../panel/index.vue";
import { useRouter } from "vue-router";
import { emitter } from "/@/utils/mitt";
import { templateRef } from "@vueuse/core";
import { debounce } from "/@/utils/debounce";
import { useAppStoreHook } from "/@/store/modules/app";
import { storageLocal, storageSession } from "/@/utils/storage";
import { import {
reactive, reactive,
ref, ref,
unref, unref,
watch, watch,
computed,
nextTick,
useCssModule, useCssModule,
getCurrentInstance getCurrentInstance
} from "vue"; } from "vue";
import panel from "../panel/index.vue";
import { useRouter } from "vue-router";
import { emitter } from "/@/utils/mitt";
import { templateRef } from "@vueuse/core";
import { debounce } from "/@/utils/debounce";
import { themeColorsType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import { storageLocal, storageSession } from "/@/utils/storage";
import { toggleTheme } from "@zougt/vite-plugin-theme-preprocessor/dist/browser-utils";
const router = useRouter(); const router = useRouter();
const { isSelect } = useCssModule(); const { isSelect } = useCssModule();
const instance = const instance =
getCurrentInstance().appContext.app.config.globalProperties.$storage; getCurrentInstance().appContext.app.config.globalProperties.$storage;
const instanceConfig =
getCurrentInstance().appContext.app.config.globalProperties.$config;
let themeColors = ref<Array<themeColorsType>>([
//
{ rgb: "27, 42, 71", themeColor: "default" },
//
{ rgb: "255, 255, 255", themeColor: "light" },
//
{ rgb: "245, 34, 45", themeColor: "dusk" },
//
{ rgb: "250, 84, 28", themeColor: "volcano" },
//
{ rgb: "250, 219, 20", themeColor: "yellow" },
//
{ rgb: "19, 194, 194", themeColor: "mingQing" },
// 绿
{ rgb: "82, 196, 26", themeColor: "auroraGreen" },
//
{ rgb: "235, 47, 150", themeColor: "pink" },
//
{ rgb: "114, 46, 209", themeColor: "saucePurple" }
]);
const verticalRef = templateRef<HTMLElement | null>("verticalRef", null);
const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null);
let layoutTheme =
ref(storageLocal.getItem("responsive-layout")) ||
ref({
layout: instanceConfig?.Layout ?? "vertical",
theme: instanceConfig?.Theme ?? "default"
});
// bodylayoutsrc/style/sidebar.scss
if (unref(layoutTheme)) {
let layout = unref(layoutTheme).layout;
let theme = unref(layoutTheme).theme;
toggleTheme({
scopeName: `layout-theme-${theme}`
});
setLayoutModel(layout);
}
// //
const markValue = ref(storageLocal.getItem("showModel") || "smart"); const markValue = ref(storageLocal.getItem("showModel") || "smart");
const logoVal = ref(storageLocal.getItem("logoVal") || "1"); const logoVal = ref(storageLocal.getItem("logoVal") || "1");
const localOperate = (key: string, value?: any, model?: string): any => {
model && model === "set"
? storageLocal.setItem(key, value)
: storageLocal.getItem(key);
};
const settings = reactive({ const settings = reactive({
greyVal: storageLocal.getItem("greyVal"),
weekVal: storageLocal.getItem("weekVal"),
tagsVal: storageLocal.getItem("tagsVal")
greyVal: instance.sets.grey,
weakVal: instance.sets.weak,
tabsVal: instance.sets.hideTabs
}); });
settings.greyVal === null
? localOperate("greyVal", false, "set")
: document.querySelector("html")?.setAttribute("class", "html-grey");
settings.weekVal === null
? localOperate("weekVal", false, "set")
: document.querySelector("html")?.setAttribute("class", "html-weakness");
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
const targetEl = target || document.body; const targetEl = target || document.body;
let { className } = targetEl; let { className } = targetEl;
@ -55,76 +87,62 @@ function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
} }
// //
const greyChange = ({ value }): void => {
const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html")); toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
value
? localOperate("greyVal", true, "set")
: localOperate("greyVal", false, "set");
instance.sets = {
grey: value,
weak: instance.sets.weak,
hideTabs: instance.sets.hideTabs
};
}; };
// //
const weekChange = ({ value }): void => {
const weekChange = (value): void => {
toggleClass( toggleClass(
settings.weekVal,
settings.weakVal,
"html-weakness", "html-weakness",
document.querySelector("html") document.querySelector("html")
); );
value
? localOperate("weekVal", true, "set")
: localOperate("weekVal", false, "set");
instance.sets = {
grey: instance.sets.grey,
weak: value,
hideTabs: instance.sets.hideTabs
};
}; };
const tagsChange = () => { const tagsChange = () => {
let showVal = settings.tagsVal;
showVal
? storageLocal.setItem("tagsVal", true)
: storageLocal.setItem("tagsVal", false);
let showVal = settings.tabsVal;
instance.sets = {
grey: instance.sets.grey,
weak: instance.sets.weak,
hideTabs: showVal
};
emitter.emit("tagViewsChange", showVal); emitter.emit("tagViewsChange", showVal);
}; };
//
nextTick(() => {
settings.greyVal &&
document.querySelector("html")?.setAttribute("class", "html-grey");
settings.weakVal &&
document.querySelector("html")?.setAttribute("class", "html-weakness");
settings.tabsVal && tagsChange();
});
//
function onReset() { function onReset() {
storageLocal.clear(); storageLocal.clear();
storageSession.clear(); storageSession.clear();
toggleClass(false, "html-grey", document.querySelector("html"));
toggleClass(false, "html-weakness", document.querySelector("html"));
router.push("/login"); router.push("/login");
} }
function onChange({ label }) {
function onChange(label) {
storageLocal.setItem("showModel", label); storageLocal.setItem("showModel", label);
emitter.emit("tagViewsShowModel", label); emitter.emit("tagViewsShowModel", label);
} }
const verticalDarkDom = templateRef<HTMLElement | null>(
"verticalDarkDom",
null
);
const verticalLightDom = templateRef<HTMLElement | null>(
"verticalLightDom",
null
);
const horizontalDarkDom = templateRef<HTMLElement | null>(
"horizontalDarkDom",
null
);
const horizontalLightDom = templateRef<HTMLElement | null>(
"horizontalLightDom",
null
);
let dataTheme =
ref(storageLocal.getItem("responsive-layout")) ||
ref({
layout: "horizontal-dark"
});
if (unref(dataTheme)) {
//
let theme = split(unref(dataTheme).layout, "-")[1];
window.document.body.setAttribute("data-theme", theme);
//
let layout = split(unref(dataTheme).layout, "-")[0];
window.document.body.setAttribute("data-layout", layout);
}
// Logo // Logo
function logoChange() { function logoChange() {
unref(logoVal) === "1" unref(logoVal) === "1"
@ -141,155 +159,170 @@ function setFalse(Doms): any {
watch(instance, ({ layout }) => { watch(instance, ({ layout }) => {
switch (layout["layout"]) { switch (layout["layout"]) {
case "vertical-dark":
toggleClass(true, isSelect, unref(verticalDarkDom));
debounce(
setFalse([verticalLightDom, horizontalDarkDom, horizontalLightDom]),
50
);
case "vertical":
toggleClass(true, isSelect, unref(verticalRef));
debounce(setFalse([horizontalRef]), 50);
break; break;
case "vertical-light":
toggleClass(true, isSelect, unref(verticalLightDom));
debounce(
setFalse([verticalDarkDom, horizontalDarkDom, horizontalLightDom]),
50
);
break;
case "horizontal-dark":
toggleClass(true, isSelect, unref(horizontalDarkDom));
debounce(
setFalse([verticalDarkDom, verticalLightDom, horizontalLightDom]),
50
);
break;
case "horizontal-light":
toggleClass(true, isSelect, unref(horizontalLightDom));
debounce(
setFalse([verticalDarkDom, verticalLightDom, horizontalDarkDom]),
50
);
case "horizontal":
toggleClass(true, isSelect, unref(horizontalRef));
debounce(setFalse([verticalRef]), 50);
break; break;
} }
}); });
function setTheme(layout: string, theme: string) {
dataTheme.value.layout = `${layout}-${theme}`;
window.document.body.setAttribute("data-layout", layout);
window.document.body.setAttribute("data-theme", theme);
instance.layout = { layout: `${layout}-${theme}` };
//
const getThemeColor = computed(() => {
return current => {
if (
current === layoutTheme.value.theme &&
layoutTheme.value.theme !== "light"
) {
return "#fff";
} else if (
current === layoutTheme.value.theme &&
layoutTheme.value.theme === "light"
) {
return "#1d2b45";
} else {
return "transparent";
}
};
});
//
function setLayoutModel(layout: string) {
layoutTheme.value.layout = layout;
window.document.body.setAttribute("layout", layout);
instance.layout = { layout, theme: layoutTheme.value.theme };
useAppStoreHook().setLayout(layout); useAppStoreHook().setLayout(layout);
} }
//
function setLayoutThemeColor(theme: string) {
layoutTheme.value.theme = theme;
toggleTheme({
scopeName: `layout-theme-${theme}`
});
instance.layout = { layout: useAppStoreHook().layout, theme };
}
</script> </script>
<template> <template>
<panel> <panel>
<el-divider>主题风格</el-divider> <el-divider>主题风格</el-divider>
<ul class="theme-stley">
<el-tooltip class="item" content="左侧菜单暗色模式" placement="bottom">
<li
:class="dataTheme.layout === 'vertical-dark' ? $style.isSelect : ''"
ref="verticalDarkDom"
@click="setTheme('vertical', 'dark')"
>
<div></div>
<div></div>
</li>
</el-tooltip>
<el-tooltip class="item" content="左侧菜单亮色模式" placement="bottom">
<ul class="pure-theme">
<el-tooltip class="item" content="左侧菜单模式" placement="bottom">
<li <li
:class="dataTheme.layout === 'vertical-light' ? $style.isSelect : ''"
ref="verticalLightDom"
@click="setTheme('vertical', 'light')"
:class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
ref="verticalRef"
@click="setLayoutModel('vertical')"
> >
<div></div> <div></div>
<div></div> <div></div>
</li> </li>
</el-tooltip> </el-tooltip>
<el-tooltip class="item" content="顶部菜单暗色模式" placement="bottom">
<el-tooltip class="item" content="顶部菜单模式" placement="bottom">
<li <li
:class="dataTheme.layout === 'horizontal-dark' ? $style.isSelect : ''"
ref="horizontalDarkDom"
@click="setTheme('horizontal', 'dark')"
:class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
ref="horizontalRef"
@click="setLayoutModel('horizontal')"
> >
<div></div> <div></div>
<div></div> <div></div>
</li> </li>
</el-tooltip> </el-tooltip>
</ul>
<el-tooltip class="item" content="顶部菜单亮色模式" placement="bottom">
<li
:class="
dataTheme.layout === 'horizontal-light' ? $style.isSelect : ''
"
ref="horizontalLightDom"
@click="setTheme('horizontal', 'light')"
<el-divider>主题色</el-divider>
<ul class="theme-color">
<li
v-for="(item, index) in themeColors"
:key="index"
:style="{ background: `rgb(${item.rgb})` }"
@click="setLayoutThemeColor(item.themeColor)"
>
<el-icon
style="margin: 0.1em 0.1em 0 0"
:size="17"
:color="getThemeColor(item.themeColor)"
> >
<div></div>
<div></div>
</li>
</el-tooltip>
<Check />
</el-icon>
</li>
</ul> </ul>
<el-divider>界面显示</el-divider> <el-divider>界面显示</el-divider>
<ul class="setting"> <ul class="setting">
<li> <li>
<span>灰色模式</span> <span>灰色模式</span>
<vxe-switch
<el-switch
v-model="settings.greyVal" v-model="settings.greyVal"
open-label="开"
close-label="关"
inline-prompt
inactive-color="#a6a6a6"
active-text="开"
inactive-text="关"
@change="greyChange" @change="greyChange"
></vxe-switch>
>
</el-switch>
</li> </li>
<li> <li>
<span>色弱模式</span> <span>色弱模式</span>
<vxe-switch
v-model="settings.weekVal"
open-label="开"
close-label="关"
<el-switch
v-model="settings.weakVal"
inline-prompt
inactive-color="#a6a6a6"
active-text="开"
inactive-text="关"
@change="weekChange" @change="weekChange"
></vxe-switch>
>
</el-switch>
</li> </li>
<li> <li>
<span>隐藏标签页</span> <span>隐藏标签页</span>
<vxe-switch
v-model="settings.tagsVal"
open-label="开"
close-label="关"
<el-switch
v-model="settings.tabsVal"
inline-prompt
inactive-color="#a6a6a6"
active-text="开"
inactive-text="关"
@change="tagsChange" @change="tagsChange"
></vxe-switch>
>
</el-switch>
</li> </li>
<li> <li>
<span>侧边栏Logo</span> <span>侧边栏Logo</span>
<vxe-switch
<el-switch
v-model="logoVal" v-model="logoVal"
open-value="1"
close-value="-1"
open-label="开"
close-label="关"
inline-prompt
active-value="1"
inactive-value="-1"
inactive-color="#a6a6a6"
active-text="开"
inactive-text="关"
@change="logoChange" @change="logoChange"
></vxe-switch>
>
</el-switch>
</li> </li>
<li> <li>
<span>标签风格</span> <span>标签风格</span>
<vxe-radio-group v-model="markValue" @change="onChange">
<vxe-radio label="card" content="卡片"></vxe-radio>
<vxe-radio label="smart" content="灵动"></vxe-radio>
</vxe-radio-group>
<el-radio-group v-model="markValue" size="small" @change="onChange">
<el-radio label="card">卡片</el-radio>
<el-radio label="smart">灵动</el-radio>
</el-radio-group>
</li> </li>
</ul> </ul>
<el-divider /> <el-divider />
<vxe-button
status="danger"
<el-button
type="danger"
style="width: 90%; margin: 24px 15px" style="width: 90%; margin: 24px 15px"
content="清空缓存并返回登录页"
icon="fa fa-sign-out"
@click="onReset" @click="onReset"
></vxe-button>
>
<i class="fa fa-sign-out"></i>
清空缓存并返回登录页</el-button
>
</panel> </panel>
</template> </template>
@ -316,10 +349,10 @@ function setTheme(layout: string, theme: string) {
font-weight: 700; font-weight: 700;
} }
.theme-stley {
.pure-theme {
margin-top: 25px; margin-top: 25px;
width: 100%; width: 100%;
height: 180px;
height: 100px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-around; justify-content: space-around;
@ -358,28 +391,6 @@ function setTheme(layout: string, theme: string) {
&:nth-child(2) { &:nth-child(2) {
div { div {
&:nth-child(1) { &:nth-child(1) {
width: 30%;
height: 100%;
box-shadow: 0 0 1px #888;
background: #fff;
border-radius: 4px 0 0 4px;
}
&:nth-child(2) {
width: 70%;
height: 30%;
top: 0;
right: 0;
background: #fff;
box-shadow: 0 0 1px #888;
position: absolute;
}
}
}
&:nth-child(3) {
div {
&:nth-child(1) {
width: 100%; width: 100%;
height: 30%; height: 30%;
background: #1b2a47; background: #1b2a47;
@ -387,16 +398,29 @@ function setTheme(layout: string, theme: string) {
} }
} }
} }
}
}
&:nth-child(4) {
div {
&:nth-child(1) {
width: 100%;
height: 30%;
background: #fff;
box-shadow: 0 0 1px #888;
}
}
.theme-color {
width: 100%;
height: 40px;
margin-top: 20px;
display: flex;
justify-content: center;
li {
float: left;
width: 20px;
height: 20px;
margin-top: 8px;
margin-right: 8px;
font-weight: 700;
text-align: center;
border-radius: 2px;
cursor: pointer;
&:nth-child(2) {
border: 1px solid #ddd;
} }
} }
} }

6
src/layout/components/sidebar/hamBurger.vue

@ -17,7 +17,11 @@ const toggleClick = () => {
</script> </script>
<template> <template>
<div :class="classes.container" @click="toggleClick">
<div
:class="classes.container"
:title="props.isActive ? '点击折叠' : '点击展开'"
@click="toggleClick"
>
<svg <svg
:class="['hamburger', props.isActive ? 'is-active' : '']" :class="['hamburger', props.isActive ? 'is-active' : '']"
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"

25
src/layout/components/sidebar/horizontal.vue

@ -15,6 +15,7 @@ import { algorithm } from "/@/utils/algorithm";
import screenfull from "../screenfull/index.vue"; import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { storageSession } from "/@/utils/storage"; import { storageSession } from "/@/utils/storage";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { deviceDetection } from "/@/utils/deviceDetection"; import { deviceDetection } from "/@/utils/deviceDetection";
import globalization from "/@/assets/svg/globalization.svg"; import globalization from "/@/assets/svg/globalization.svg";
import { usePermissionStoreHook } from "/@/store/modules/permission"; import { usePermissionStoreHook } from "/@/store/modules/permission";
@ -117,7 +118,7 @@ onMounted(() => {
<template> <template>
<div class="horizontal-header"> <div class="horizontal-header">
<div class="horizontal-header-left" @click="backHome"> <div class="horizontal-header-left" @click="backHome">
<i class="fa fa-optin-monster"></i>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<h4>{{ title }}</h4> <h4>{{ title }}</h4>
</div> </div>
<el-menu <el-menu
@ -150,6 +151,8 @@ onMounted(() => {
color: locale === 'zh' ? '#f4f4f5' : '#000' color: locale === 'zh' ? '#f4f4f5' : '#000'
}" }"
@click="translationCh" @click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
>简体中文</el-dropdown-item >简体中文</el-dropdown-item
> >
<el-dropdown-item <el-dropdown-item
@ -158,6 +161,8 @@ onMounted(() => {
color: locale === 'en' ? '#f4f4f5' : '#000' color: locale === 'en' ? '#f4f4f5' : '#000'
}" }"
@click="translationEn" @click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
>English</el-dropdown-item >English</el-dropdown-item
> >
</el-dropdown-menu> </el-dropdown-menu>
@ -179,11 +184,13 @@ onMounted(() => {
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<i
<el-icon
class="el-icon-setting" class="el-icon-setting"
:title="$t('message.hssystemSet')" :title="$t('message.hssystemSet')"
@click="onPanel" @click="onPanel"
></i>
>
<Setting />
</el-icon>
</div> </div>
</div> </div>
</template> </template>
@ -199,6 +206,18 @@ onMounted(() => {
color: #606266; color: #606266;
background: #f0f0f0; background: #f0f0f0;
} }
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
} }
.logout { .logout {

25
src/layout/components/sidebar/logo.vue

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { getCurrentInstance } from "vue"; import { getCurrentInstance } from "vue";
import Icon from "/@/components/ReIcon/src/Icon.vue";
const props = defineProps({ const props = defineProps({
collapse: Boolean collapse: Boolean
}); });
@ -18,8 +19,8 @@ const title =
class="sidebar-logo-link" class="sidebar-logo-link"
to="/" to="/"
> >
<i class="fa fa-optin-monster"></i>
<h1 class="sidebar-title">{{ title }}</h1>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<span class="sidebar-title">{{ title }}</span>
</router-link> </router-link>
<router-link <router-link
v-else v-else
@ -28,8 +29,8 @@ const title =
class="sidebar-logo-link" class="sidebar-logo-link"
to="/" to="/"
> >
<i class="fa fa-optin-monster"></i>
<h1 class="sidebar-title">{{ title }}</h1>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<span class="sidebar-title">{{ title }}</span>
</router-link> </router-link>
</transition> </transition>
</div> </div>
@ -39,28 +40,24 @@ const title =
.sidebar-logo-container { .sidebar-logo-container {
position: relative; position: relative;
width: 100%; width: 100%;
height: 50px;
height: 48px;
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
.sidebar-logo-link { .sidebar-logo-link {
height: 100%; height: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-top: 5px;
.sidebar-title { .sidebar-title {
display: inline-block;
margin: 0;
color: #1890ff; color: #1890ff;
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
margin-top: 16px;
margin-top: 10px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
} }
.fa-optin-monster {
font-size: 30px;
color: #1890ff;
margin-top: 5px;
}
} }
.collapse { .collapse {

111
src/layout/components/sidebar/sidebarItem.vue

@ -1,9 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import path from "path"; import path from "path";
import { PropType, ref } from "vue";
import { PropType, ref, nextTick, getCurrentInstance } from "vue";
import { childrenType } from "../../types"; import { childrenType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import Icon from "/@/components/ReIcon/src/Icon.vue"; import Icon from "/@/components/ReIcon/src/Icon.vue";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const menuMode = instance.$storage.layout?.layout === "vertical";
const pureApp = useAppStoreHook();
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object as PropType<childrenType> type: Object as PropType<childrenType>
@ -19,6 +24,28 @@ const props = defineProps({
}); });
const onlyOneChild: childrenType = ref(null); const onlyOneChild: childrenType = ref(null);
// showTooltip
const hoverMenuMap = new WeakMap();
// dom
const menuTextRef = ref(null);
function hoverMenu(key) {
// showTooltip退
if (hoverMenuMap.get(key)) return;
nextTick(() => {
//
menuTextRef.value?.scrollWidth > menuTextRef.value?.clientWidth
? Object.assign(key, {
showTooltip: true
})
: Object.assign(key, {
showTooltip: false
});
hoverMenuMap.set(key, true);
});
}
function hasOneShowingChild( function hasOneShowingChild(
children: childrenType[] = [], children: childrenType[] = [],
@ -55,19 +82,51 @@ function resolvePath(routePath) {
<el-menu-item <el-menu-item
:index="resolvePath(onlyOneChild.path)" :index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }" :class="{ 'submenu-title-noDropdown': !isNest }"
style="display: flex; align-items: center"
> >
<i
:class="
onlyOneChild.meta.icon || (props.item.meta && props.item.meta.icon)
"
/>
<el-icon v-show="props.item.meta.icon">
<component
:is="
onlyOneChild.meta.icon || (props.item.meta && props.item.meta.icon)
"
></component>
</el-icon>
<template #title> <template #title>
<span>{{ $t(onlyOneChild.meta.title) }}</span>
<Icon
v-if="onlyOneChild.meta.extraIcon"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/>
<div
:style="{
width: pureApp.sidebar.opened ? '' : '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}"
>
<span v-if="!menuMode">{{ $t(onlyOneChild.meta.title) }}</span>
<el-tooltip
v-else
placement="top"
:offset="-10"
:disabled="!onlyOneChild.showTooltip"
>
<template #content> {{ $t(onlyOneChild.meta.title) }} </template>
<span
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
overflow: 'hidden',
textOverflow: 'ellipsis'
}"
@mouseover="hoverMenu(onlyOneChild)"
>
{{ $t(onlyOneChild.meta.title) }}
</span>
</el-tooltip>
<Icon
v-if="onlyOneChild.meta.extraIcon"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/>
</div>
</template> </template>
</el-menu-item> </el-menu-item>
</template> </template>
@ -79,8 +138,32 @@ function resolvePath(routePath) {
popper-append-to-body popper-append-to-body
> >
<template #title> <template #title>
<i :class="props.item.meta.icon"></i>
<span>{{ $t(props.item.meta.title) }}</span>
<el-icon v-show="props.item.meta.icon" :class="props.item.meta.icon">
<component :is="props.item.meta && props.item.meta.icon"></component>
</el-icon>
<span v-if="!menuMode">{{ $t(props.item.meta.title) }}</span>
<el-tooltip
v-else
placement="top"
:offset="-10"
:disabled="!pureApp.sidebar.opened || !props.item.showTooltip"
>
<template #content> {{ $t(props.item.meta.title) }} </template>
<div
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis'
}"
@mouseover="hoverMenu(props.item)"
>
<span style="overflow: hidden; text-overflow: ellipsis">
{{ $t(props.item.meta.title) }}
</span>
</div>
</el-tooltip>
<Icon <Icon
v-if="props.item.meta.extraIcon" v-if="props.item.meta.extraIcon"
:svg="props.item.meta.extraIcon.svg ? true : false" :svg="props.item.meta.extraIcon.svg ? true : false"

2
src/layout/components/sidebar/vertical.vue

@ -68,12 +68,14 @@ onBeforeMount(() => {
router router
:collapse-transition="false" :collapse-transition="false"
mode="vertical" mode="vertical"
class="outer-most"
@select="menuSelect" @select="menuSelect"
> >
<sidebar-item <sidebar-item
v-for="route in routeStore.wholeRoutes" v-for="route in routeStore.wholeRoutes"
:key="route.path" :key="route.path"
:item="route" :item="route"
class="outer-most"
:base-path="route.path" :base-path="route.path"
/> />
</el-menu> </el-menu>

17
src/layout/components/tag/index.vue

@ -482,14 +482,16 @@ onBeforeMount(() => {
<router-link :to="item.path" @click="tagOnClick(item)">{{ <router-link :to="item.path" @click="tagOnClick(item)">{{
$t(item.meta.title) $t(item.meta.title)
}}</router-link> }}</router-link>
<span
<el-icon
v-if=" v-if="
($route.path === item.path && index !== 0) || ($route.path === item.path && index !== 0) ||
(index === activeIndex && index !== 0) (index === activeIndex && index !== 0)
" "
class="el-icon-close" class="el-icon-close"
@click="deleteMenu(item)" @click="deleteMenu(item)"
></span>
>
<CloseBold />
</el-icon>
<div <div
:ref="'schedule' + index" :ref="'schedule' + index"
v-if="showModel !== 'card'" v-if="showModel !== 'card'"
@ -520,15 +522,19 @@ onBeforeMount(() => {
<!-- 右侧功能按钮 --> <!-- 右侧功能按钮 -->
<ul class="right-button"> <ul class="right-button">
<li> <li>
<i
<el-icon
:title="$t('message.hsrefreshRoute')" :title="$t('message.hsrefreshRoute')"
class="el-icon-refresh-right rotate" class="el-icon-refresh-right rotate"
@click="onFresh" @click="onFresh"
></i>
>
<RefreshRight />
</el-icon>
</li> </li>
<li> <li>
<el-dropdown trigger="click" placement="bottom-end"> <el-dropdown trigger="click" placement="bottom-end">
<i class="el-icon-arrow-down"></i>
<el-icon>
<ArrowDown />
</el-icon>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
@ -628,6 +634,7 @@ onBeforeMount(() => {
font-size: 10px; font-size: 10px;
color: #1890ff; color: #1890ff;
cursor: pointer; cursor: pointer;
transform: fontsize3s;
&:hover { &:hover {
border-radius: 50%; border-radius: 50%;

159
src/layout/index.vue

@ -1,32 +1,6 @@
<script lang="ts">
import { routerArrays } from "./types";
export default {
computed: {
layout() {
if (!this.$storage.layout) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.$storage.layout = { layout: "vertical-dark" };
}
if (
!this.$storage.routesInStorage ||
this.$storage.routesInStorage.length === 0
) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.$storage.routesInStorage = routerArrays;
}
if (!this.$storage.locale) {
// eslint-disable-next-line
this.$storage.locale = { locale: "zh" };
useI18n().locale.value = "zh";
}
return this.$storage?.layout.layout;
}
}
};
</script>
<script setup lang="ts"> <script setup lang="ts">
import { import {
h,
ref, ref,
unref, unref,
reactive, reactive,
@ -34,13 +8,15 @@ import {
onMounted, onMounted,
watchEffect, watchEffect,
onBeforeMount, onBeforeMount,
defineComponent,
getCurrentInstance getCurrentInstance
} from "vue"; } from "vue";
import { setType } from "./types"; import { setType } from "./types";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { routerArrays } from "./types";
import { emitter } from "/@/utils/mitt"; import { emitter } from "/@/utils/mitt";
import { useEventListener } from "@vueuse/core"; import { useEventListener } from "@vueuse/core";
import { storageLocal } from "/@/utils/storage";
import backTop from "/@/assets/svg/back_top.svg";
import { useAppStoreHook } from "/@/store/modules/app"; import { useAppStoreHook } from "/@/store/modules/app";
import fullScreen from "/@/assets/svg/full_screen.svg"; import fullScreen from "/@/assets/svg/full_screen.svg";
import exitScreen from "/@/assets/svg/exit_screen.svg"; import exitScreen from "/@/assets/svg/exit_screen.svg";
@ -53,14 +29,45 @@ import setting from "./components/setting/index.vue";
import Vertical from "./components/sidebar/vertical.vue"; import Vertical from "./components/sidebar/vertical.vue";
import Horizontal from "./components/sidebar/horizontal.vue"; import Horizontal from "./components/sidebar/horizontal.vue";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const hiddenSideBar = ref(instance.$config?.HiddenSideBar);
const pureSetting = useSettingStoreHook(); const pureSetting = useSettingStoreHook();
const instance =
getCurrentInstance().appContext.app.config.globalProperties.$storage;
const hiddenSideBar = ref(
getCurrentInstance().appContext.config.globalProperties.$config?.HiddenSideBar
);
// serverConfig.jsonstorage
const layout = computed(() => {
//
if (
!instance.$storage.routesInStorage ||
instance.$storage.routesInStorage.length === 0
) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.routesInStorage = routerArrays;
}
//
if (!instance.$storage.locale) {
// eslint-disable-next-line
instance.$storage.locale = { locale: instance.$config?.Locale ?? "zh" };
useI18n().locale.value = instance.$config?.Locale ?? "zh";
}
//
if (!instance.$storage.layout) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.layout = {
layout: instance.$config?.Layout ?? "vertical",
theme: instance.$config?.Theme ?? "default"
};
}
//
if (!instance.$storage.sets) {
// eslint-disable-next-line
instance.$storage.sets = {
grey: instance.$config?.Grey ?? false,
weak: instance.$config?.Weak ?? false,
hideTabs: instance.$config?.HideTabs ?? false
};
}
return instance.$storage?.layout.layout;
});
const set: setType = reactive({ const set: setType = reactive({
sidebar: computed(() => { sidebar: computed(() => {
@ -82,6 +89,10 @@ const set: setType = reactive({
withoutAnimation: set.sidebar.withoutAnimation, withoutAnimation: set.sidebar.withoutAnimation,
mobile: set.device === "mobile" mobile: set.device === "mobile"
}; };
}),
hideTabs: computed(() => {
return instance.$storage?.sets.hideTabs;
}) })
}); });
@ -90,11 +101,11 @@ const handleClickOutside = (params: boolean) => {
}; };
function setTheme(layoutModel: string) { function setTheme(layoutModel: string) {
let { layout } = storageLocal.getItem("responsive-layout");
let theme = layout.match(/-(.*)/)[1];
window.document.body.setAttribute("data-layout", layoutModel);
window.document.body.setAttribute("data-theme", theme);
instance.layout = { layout: `${layoutModel}-${theme}` };
window.document.body.setAttribute("layout", layoutModel);
instance.$storage.layout = {
layout: `${layoutModel}`,
theme: instance.$storage.layout?.theme
};
} }
// //
@ -141,6 +152,49 @@ onMounted(() => {
onBeforeMount(() => { onBeforeMount(() => {
useEventListener("resize", $_resizeHandler); useEventListener("resize", $_resizeHandler);
}); });
const layoutHeader = defineComponent({
render() {
return h(
"div",
{
class: { "fixed-header": set.fixedHeader },
style: [
set.hideTabs && layout.value.includes("horizontal")
? "box-shadow: 0 1px 4px rgb(0 21 41 / 8%);"
: ""
]
},
{
default: () => [
!hiddenSideBar.value && layout.value.includes("vertical")
? h(navbar)
: h("div"),
!hiddenSideBar.value && layout.value.includes("horizontal")
? h(Horizontal)
: h("div"),
h(
tag,
{},
{
default: () => [
h(
"span",
{ onClick: onFullScreen },
{
default: () => [
!hiddenSideBar.value ? h(fullScreen) : h(exitScreen)
]
}
)
]
}
)
]
}
);
}
});
</script> </script>
<template> <template>
@ -156,20 +210,21 @@ onBeforeMount(() => {
/> />
<Vertical v-show="!hiddenSideBar && layout.includes('vertical')" /> <Vertical v-show="!hiddenSideBar && layout.includes('vertical')" />
<div :class="['main-container', hiddenSideBar ? 'main-hidden' : '']"> <div :class="['main-container', hiddenSideBar ? 'main-hidden' : '']">
<div :class="{ 'fixed-header': set.fixedHeader }">
<!-- 顶部导航栏 -->
<navbar v-show="!hiddenSideBar && layout.includes('vertical')" />
<!-- tabs标签页 -->
<Horizontal v-show="!hiddenSideBar && layout.includes('horizontal')" />
<tag>
<span @click="onFullScreen">
<fullScreen v-if="!hiddenSideBar" />
<exitScreen v-else />
</span>
</tag>
<div v-if="set.fixedHeader">
<layout-header />
<!-- 主体内容 -->
<app-main :fixed-header="set.fixedHeader" />
</div> </div>
<!-- 主体内容 -->
<app-main />
<el-scrollbar v-else>
<el-backtop
title="回到顶部"
target=".main-container .el-scrollbar__wrap"
><backTop />
</el-backtop>
<layout-header />
<!-- 主体内容 -->
<app-main :fixed-header="set.fixedHeader" />
</el-scrollbar>
</div> </div>
<!-- 系统设置 --> <!-- 系统设置 -->
<setting /> <setting />

12
src/layout/theme/auroraGreen-vars.scss

@ -0,0 +1,12 @@
// 极光绿
$subMenuActiveText: #fff;
$menuBg: #0b1e15;
$menuHover: #60ac80;
$subMenuBg: #000;
$subMenuActiveBg: #60ac80;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #112f21;
$menuTitleHover: #fff;
$menuActiveBefore: #60ac80;

24
src/layout/theme/default-vars.scss

@ -0,0 +1,24 @@
/**
*此scss变量文件作为multipleScopeVars去编译时会自动移除!default以达到变量提升
*同时此scss变量文件作为默认主题变量文件被其他.scss通过 @import 必需 !default
*/
// 暗雅默认
// 菜单选中后字体样式
$subMenuActiveText: #fff !default;
//菜单背景
$menuBg: #001529 !default;
// 鼠标覆盖到菜单时的背景
$menuHover: #4091f7 !default;
// 子菜单背景
$subMenuBg: #0f0303 !default;
// 有无子集的激活菜单背景
$subMenuActiveBg: #4091f7 !default;
$navTextColor: #fff !default;
$menuText: rgba(254, 254, 254, 0.65) !default;
// logo背景颜色
$sidebarLogo: #002140 !default;
// 鼠标覆盖到菜单时的字体颜色
$menuTitleHover: #fff !default;
$menuActiveBefore: #4091f7 !default;

12
src/layout/theme/dusk-vars.scss

@ -0,0 +1,12 @@
// 薄暮
$subMenuActiveText: #fff;
$menuBg: #2a0608;
$menuHover: #e13c39;
$subMenuBg: #000;
$subMenuActiveBg: #e13c39;
$navTextColor: red;
$menuText: rgba(254, 254, 254, 0.651);
$sidebarLogo: #42090c;
$menuTitleHover: #fff;
$menuActiveBefore: #e13c39;

11
src/layout/theme/light-vars.scss

@ -0,0 +1,11 @@
// 明亮
$subMenuActiveText: #409eff;
$menuBg: #fff;
$menuHover: #e0ebf6;
$subMenuBg: #fff;
$subMenuActiveBg: #e0ebf6;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #fff;
$menuTitleHover: #000;
$menuActiveBefore: #4091f7;

12
src/layout/theme/mingQing-vars.scss

@ -0,0 +1,12 @@
// 明青
$subMenuActiveText: #fff;
$menuBg: #032121;
$menuHover: #59bfc1;
$subMenuBg: #000;
$subMenuActiveBg: #59bfc1;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #053434;
$menuTitleHover: #fff;
$menuActiveBefore: #59bfc1;

12
src/layout/theme/pink-vars.scss

@ -0,0 +1,12 @@
// 粉红
$subMenuActiveText: #fff;
$menuBg: #28081a;
$menuHover: #d84493;
$subMenuBg: #000;
$subMenuActiveBg: #d84493;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #3f0d29;
$menuTitleHover: #fff;
$menuActiveBefore: #d84493;

12
src/layout/theme/saucePurple-vars.scss

@ -0,0 +1,12 @@
// 酱紫
$subMenuActiveText: #fff;
$menuBg: #130824;
$menuHover: #693ac9;
$subMenuBg: #000;
$subMenuActiveBg: #693ac9;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #1f0c38;
$menuTitleHover: #fff;
$menuActiveBefore: #693ac9;

12
src/layout/theme/volcano-vars.scss

@ -0,0 +1,12 @@
// 火山
$subMenuActiveText: #fff;
$menuBg: #2b0e05;
$menuHover: #e85f33;
$subMenuBg: #0f0603;
$subMenuActiveBg: #e85f33;
$navTextColor: #fff;
$menuText: rgba(254, 254, 254, 0.65);
$sidebarLogo: #441708;
$menuTitleHover: #fff;
$menuActiveBefore: #e85f33;

12
src/layout/theme/yellow-vars.scss

@ -0,0 +1,12 @@
// 黄色
$subMenuActiveText: #d25f00;
$menuBg: #2b2503;
$menuHover: #f6da4d;
$subMenuBg: #0f0603;
$subMenuActiveBg: #f6da4d;
$navTextColor: #fff;
$menuText: rgba(254, 254, 254, 0.65);
$sidebarLogo: #443b05;
$menuTitleHover: #fff;
$menuActiveBefore: #f6da4d;

7
src/layout/types.ts

@ -47,6 +47,7 @@ export interface setType {
withoutAnimation: boolean; withoutAnimation: boolean;
mobile: boolean; mobile: boolean;
}; };
hideTabs: boolean;
} }
export type childrenType = { export type childrenType = {
@ -62,4 +63,10 @@ export type childrenType = {
name?: string; name?: string;
}; };
}; };
showTooltip?: boolean;
};
export type themeColorsType = {
rgb: string;
themeColor: string;
}; };

4
src/main.ts

@ -4,7 +4,7 @@ import { setupStore } from "/@/store";
import { getServerConfig } from "./config"; import { getServerConfig } from "./config";
import { createApp, Directive } from "vue"; import { createApp, Directive } from "vue";
import { usI18n } from "../src/plugins/i18n"; import { usI18n } from "../src/plugins/i18n";
import { useTable } from "../src/plugins/vxe-table";
import { MotionPlugin } from "@vueuse/motion";
import { useElementPlus } from "../src/plugins/element-plus"; import { useElementPlus } from "../src/plugins/element-plus";
import { injectResponsiveStorage } from "/@/utils/storage/responsive"; import { injectResponsiveStorage } from "/@/utils/storage/responsive";
@ -26,7 +26,7 @@ Object.keys(directives).forEach(key => {
getServerConfig(app).then(async config => { getServerConfig(app).then(async config => {
injectResponsiveStorage(app, config); injectResponsiveStorage(app, config);
setupStore(app); setupStore(app);
app.use(router).use(useElementPlus).use(useTable).use(usI18n);
app.use(router).use(MotionPlugin).use(useElementPlus).use(usI18n);
await router.isReady(); await router.isReady();
app.mount("#app"); app.mount("#app");
}); });

44
src/plugins/element-plus/index.ts

@ -30,12 +30,33 @@ import {
ElDrawer, ElDrawer,
ElPagination, ElPagination,
ElAlert, ElAlert,
ElRadio,
ElRadioButton, ElRadioButton,
ElRadioGroup, ElRadioGroup,
ElDescriptions, ElDescriptions,
ElDescriptionsItem
ElDescriptionsItem,
ElBacktop,
ElSwitch
} from "element-plus"; } from "element-plus";
// https://element-plus.org/zh-CN/component/icon.html
import {
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold
} from "@element-plus/icons";
const components = [ const components = [
ElTag, ElTag,
ElAffix, ElAffix,
@ -66,10 +87,29 @@ const components = [
ElDrawer, ElDrawer,
ElPagination, ElPagination,
ElAlert, ElAlert,
ElRadio,
ElRadioButton, ElRadioButton,
ElRadioGroup, ElRadioGroup,
ElDescriptions, ElDescriptions,
ElDescriptionsItem
ElDescriptionsItem,
ElBacktop,
ElSwitch,
// icon
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold
]; ];
const plugins = [ElLoading]; const plugins = [ElLoading];

75
src/plugins/i18n/config.ts

@ -3,38 +3,13 @@ import enLocale from "element-plus/lib/locale/lang/en";
import zhLocale from "element-plus/lib/locale/lang/zh-cn"; import zhLocale from "element-plus/lib/locale/lang/zh-cn";
// 导航菜单配置 // 导航菜单配置
// 导航菜单配置
export const menusConfig = { export const menusConfig = {
zh: { zh: {
message: { message: {
hshome: "首页", hshome: "首页",
hssysManagement: "系统管理",
hsBaseinfo: "基础信息",
hsDict: "字典管理",
hseditor: "编辑器",
hserror: "错误页面", hserror: "错误页面",
hsfourZeroFour: "404", hsfourZeroFour: "404",
hsfourZeroOne: "401", hsfourZeroOne: "401",
hscomponents: "组件",
hsvideo: "视频组件",
hsmap: "地图组件",
hsdraggable: "拖拽组件",
hssplitPane: "切割面板",
hsbutton: "按钮组件",
hscropping: "图片裁剪",
hscountTo: "数字动画",
hsselector: "选择器组件",
hsflowChart: "流程图",
hsseamless: "无缝滚动",
hscontextmenu: "右键菜单",
hsmenus: "多级菜单",
hsmenu1: "菜单1",
"hsmenu1-1": "菜单1-1",
"hsmenu1-2": "菜单1-2",
"hsmenu1-2-1": "菜单1-2-1",
"hsmenu1-2-2": "菜单1-2-2",
"hsmenu1-3": "菜单1-3",
hsmenu2: "菜单2",
permission: "权限管理", permission: "权限管理",
permissionPage: "页面权限", permissionPage: "页面权限",
permissionButton: "按钮权限", permissionButton: "按钮权限",
@ -44,33 +19,9 @@ export const menusConfig = {
en: { en: {
message: { message: {
hshome: "Home", hshome: "Home",
hssysManagement: "System Manage",
hsBaseinfo: "Base Info",
hsDict: "Dict Manage",
hseditor: "Editor",
hserror: "Error Page", hserror: "Error Page",
hsfourZeroFour: "404", hsfourZeroFour: "404",
hsfourZeroOne: "401", hsfourZeroOne: "401",
hscomponents: "Components",
hsvideo: "Video Components",
hsmap: "Map Components",
hsdraggable: "Draggable Components",
hssplitPane: "Split Pane",
hsbutton: "Button Components",
hscropping: "Picture Cropping",
hscountTo: "Digital Animation",
hsselector: "Selector Components",
hsflowChart: "Flow Chart",
hsseamless: "Seamless Scroll",
hscontextmenu: "Context Menu",
hsmenus: "MultiLevel Menu",
hsmenu1: "Menu1",
"hsmenu1-1": "Menu1-1",
"hsmenu1-2": "Menu1-2",
"hsmenu1-2-1": "Menu1-2-1",
"hsmenu1-2-2": "Menu1-2-2",
"hsmenu1-3": "Menu1-3",
hsmenu2: "Menu2",
permission: "Permission Manage", permission: "Permission Manage",
permissionPage: "Page Permission", permissionPage: "Page Permission",
permissionButton: "Button Permission", permissionButton: "Button Permission",
@ -88,10 +39,14 @@ export const buttonConfig = {
hsexitfullscreen: "退出全屏", hsexitfullscreen: "退出全屏",
hsrefreshRoute: "刷新路由", hsrefreshRoute: "刷新路由",
hslogin: "登陆", hslogin: "登陆",
hsregister: "注册",
hsadd: "新增",
hsmark: "标记/取消",
hssave: "保存",
hssearch: "搜索",
hsexpendAll: "全部展开", hsexpendAll: "全部展开",
hscollapseAll: "全部折叠", hscollapseAll: "全部折叠",
hssystemSet: "系统设置",
hssystemSet: "打开项目配置",
hsdelete: "删除",
hsreload: "重新加载", hsreload: "重新加载",
hscloseCurrentTab: "关闭当前标签页", hscloseCurrentTab: "关闭当前标签页",
hscloseLeftTabs: "关闭左侧标签页", hscloseLeftTabs: "关闭左侧标签页",
@ -107,10 +62,14 @@ export const buttonConfig = {
hsexitfullscreen: "exitFullscreen", hsexitfullscreen: "exitFullscreen",
hsrefreshRoute: "refreshRoute", hsrefreshRoute: "refreshRoute",
hslogin: "login", hslogin: "login",
hsregister: "register",
hsadd: "Add",
hsmark: "Mark/Cancel",
hssave: "Save",
hssearch: "Search",
hsexpendAll: "Expand All", hsexpendAll: "Expand All",
hscollapseAll: "Collapse All", hscollapseAll: "Collapse All",
hssystemSet: "System Set",
hssystemSet: "Open ProjectConfig",
hsdelete: "Delete",
hsreload: "Reload", hsreload: "Reload",
hscloseCurrentTab: "Close CurrentTab", hscloseCurrentTab: "Close CurrentTab",
hscloseLeftTabs: "Close LeftTabs", hscloseLeftTabs: "Close LeftTabs",
@ -121,6 +80,16 @@ export const buttonConfig = {
} }
}; };
// 配置
// export const xxxx = {
// zh: {
// message: {},
// },
// en: {
// message: {},
// },
// };
const localesList = [menusConfig, buttonConfig]; const localesList = [menusConfig, buttonConfig];
export const localesConfigs = { export const localesConfigs = {

1
src/plugins/i18n/index.ts

@ -1,3 +1,4 @@
// 多组件库的国际化和本地项目国际化兼容
import { App } from "vue"; import { App } from "vue";
import { createI18n } from "vue-i18n"; import { createI18n } from "vue-i18n";
import { localesConfigs } from "./config"; import { localesConfigs } from "./config";

119
src/plugins/vxe-table/index.ts

@ -1,119 +0,0 @@
import "xe-utils";
import { App } from "vue";
import { i18n } from "../i18n/index";
import "font-awesome/css/font-awesome.css";
import {
// 核心
VXETable,
// 表格功能
Header,
Footer,
Icon,
Filter,
Edit,
Menu,
Export,
Keyboard,
Validator,
// 可选组件
Column,
Colgroup,
Grid,
Tooltip,
Toolbar,
Pager,
Form,
FormItem,
FormGather,
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
RadioButton,
Switch,
Input,
Select,
Optgroup,
Option,
Textarea,
Button,
Modal,
List,
Pulldown,
// 表格
Table
} from "vxe-table";
// 全局默认参数
VXETable.setup({
size: "medium",
version: 0,
zIndex: 1002,
table: {
// 自动监听父元素的变化去重新计算表格
autoResize: true,
// 鼠标移到行是否要高亮显示
highlightHoverRow: true
},
input: {
clearable: true
},
// 对组件内置的提示语进行国际化翻译
// @ts-ignore
i18n: (key, args) => i18n.global.t(key, args),
// 可选,对参数中的列头、校验提示..等进行自动翻译(只对支持国际化的有效)
translate(key, args) {
// 例如,只翻译 "message." 开头的键值
if (key && key.indexOf("message.") > -1) {
return i18n.global.t(key, args);
}
if (key && key.indexOf("el.") > -1) {
return i18n.global.t(key, args);
}
return key;
}
});
export function useTable(app: App) {
app
.use(Header)
.use(Footer)
.use(Icon)
.use(Filter)
.use(Edit)
.use(Menu)
.use(Export)
.use(Keyboard)
.use(Validator)
// 可选组件
.use(Column)
.use(Colgroup)
.use(Grid)
.use(Tooltip)
.use(Toolbar)
.use(Pager)
.use(Form)
.use(FormItem)
.use(FormGather)
.use(Checkbox)
.use(CheckboxGroup)
.use(Radio)
.use(RadioGroup)
.use(RadioButton)
.use(Switch)
.use(Input)
.use(Select)
.use(Optgroup)
.use(Option)
.use(Textarea)
.use(Button)
.use(Modal)
.use(List)
.use(Pulldown)
// 安装表格
.use(Table);
}

10
src/router/index.ts

@ -5,6 +5,7 @@ import {
createWebHashHistory, createWebHashHistory,
RouteRecordNormalized RouteRecordNormalized
} from "vue-router"; } from "vue-router";
import { RouteConfigs } from "/@/layout/types";
import { split, uniqBy } from "lodash-es"; import { split, uniqBy } from "lodash-es";
import { i18n } from "/@/plugins/i18n"; import { i18n } from "/@/plugins/i18n";
import { openLink } from "/@/utils/link"; import { openLink } from "/@/utils/link";
@ -18,6 +19,7 @@ import { usePermissionStoreHook } from "/@/store/modules/permission";
import homeRouter from "./modules/home"; import homeRouter from "./modules/home";
import Layout from "/@/layout/index.vue"; import Layout from "/@/layout/index.vue";
import errorRouter from "./modules/error"; import errorRouter from "./modules/error";
import externalLink from "./modules/externalLink";
import remainingRouter from "./modules/remaining"; import remainingRouter from "./modules/remaining";
// 动态路由 // 动态路由
import { getAsyncRoutes } from "/@/api/routes"; import { getAsyncRoutes } from "/@/api/routes";
@ -25,7 +27,11 @@ import { getAsyncRoutes } from "/@/api/routes";
// https://cn.vitejs.dev/guide/features.html#glob-import // https://cn.vitejs.dev/guide/features.html#glob-import
const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue"); const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue");
const constantRoutes: Array<RouteComponent> = [homeRouter, errorRouter];
const constantRoutes: Array<RouteComponent> = [
homeRouter,
externalLink,
errorRouter
];
// 按照路由中meta下的rank等级升序来排序路由 // 按照路由中meta下的rank等级升序来排序路由
export const ascending = arr => { export const ascending = arr => {
@ -181,7 +187,7 @@ export function resetRouter() {
} }
// 路由白名单 // 路由白名单
const whiteList = ["/login", "/register"];
const whiteList = ["/login"];
router.beforeEach((to, _from, next) => { router.beforeEach((to, _from, next) => {
if (to.meta?.keepAlive) { if (to.meta?.keepAlive) {

2
src/router/modules/error.ts

@ -6,7 +6,7 @@ const errorRouter = {
component: Layout, component: Layout,
redirect: "/error/401", redirect: "/error/401",
meta: { meta: {
icon: "el-icon-position",
icon: "Position",
title: "message.hserror", title: "message.hserror",
showLink: true, showLink: true,
rank: 7 rank: 7

25
src/router/modules/externalLink.ts

@ -0,0 +1,25 @@
import Layout from "/@/layout/index.vue";
const externalLink = {
path: "/external",
name: "external",
component: Layout,
meta: {
icon: "Link",
title: "message.externalLink",
showLink: true,
rank: 190
},
children: [
{
path: "https://github.com/xiaoxian521/vue-pure-admin",
meta: {
title: "message.externalLink",
showLink: true,
rank: 191
}
}
]
};
export default externalLink;

2
src/router/modules/home.ts

@ -6,7 +6,7 @@ const homeRouter = {
component: Layout, component: Layout,
redirect: "/welcome", redirect: "/welcome",
meta: { meta: {
icon: "el-icon-s-home",
icon: "HomeFilled",
showLink: true, showLink: true,
rank: 0 rank: 0
}, },

12
src/router/modules/remaining.ts

@ -12,21 +12,11 @@ const remainingRouter = [
} }
}, },
{ {
path: "/register",
name: "register",
component: () => import("/@/views/register.vue"),
meta: {
title: "message.hsregister",
showLink: false,
rank: 102
}
},
{
path: "/redirect", path: "/redirect",
name: "redirect", name: "redirect",
component: Layout, component: Layout,
meta: { meta: {
icon: "el-icon-s-home",
icon: "HomeFilled",
title: "message.hshome", title: "message.hshome",
showLink: false, showLink: false,
rank: 104 rank: 104

5
src/store/modules/app.ts

@ -2,6 +2,7 @@ import { storageLocal } from "/@/utils/storage";
import { deviceDetection } from "/@/utils/deviceDetection"; import { deviceDetection } from "/@/utils/deviceDetection";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "/@/store"; import { store } from "/@/store";
import { getConfig } from "/@/config";
interface AppState { interface AppState {
sidebar: { sidebar: {
@ -21,9 +22,9 @@ export const useAppStore = defineStore({
: true, : true,
withoutAnimation: false withoutAnimation: false
}, },
// 这里的layout用于监听容器拖拉后恢复对应的导航模式
layout: layout:
storageLocal.getItem("responsive-layout")?.layout.match(/(.*)-/)[1] ??
"vertical",
storageLocal.getItem("responsive-layout")?.layout ?? getConfig().Layout,
device: deviceDetection() ? "mobile" : "desktop" device: deviceDetection() ? "mobile" : "desktop"
}), }),
getters: { getters: {

4
src/style/element-plus.scss

@ -37,10 +37,6 @@
box-sizing: content-box; box-sizing: content-box;
} }
.el-loading-mask {
z-index: -1;
}
// el-tooltip的权重 // el-tooltip的权重
.is-dark { .is-dark {
z-index: 99999 !important; z-index: 99999 !important;

14
src/style/index.scss

@ -65,20 +65,6 @@ ul {
} }
} }
// main-container global css
.app-container {
padding: 20px;
}
.login,
.register {
width: 100vw;
height: 100vh;
overflow-x: hidden;
background: url("../assets/bg.png") no-repeat center;
background-size: cover;
}
/* 头部用户信息样式重置 */ /* 头部用户信息样式重置 */
.hidden { .hidden {
display: none !important; display: none !important;

227
src/style/login.css

@ -0,0 +1,227 @@
.wave {
position: fixed;
height: 100%;
left: 0;
bottom: 0;
z-index: -1;
}
.container {
width: 100vw;
height: 100vh;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 18rem;
padding: 0 2rem;
}
.img {
display: flex;
justify-content: flex-end;
align-items: center;
}
.img img {
width: 500px;
}
.login-box {
display: flex;
align-items: center;
text-align: center;
}
.login-form {
width: 360px;
}
.avatar {
width: 350px;
height: 80px;
}
.login-form h2 {
text-transform: uppercase;
margin: 15px 0;
color: #999;
font: bold 200% Consolas, Monaco, monospace;
}
.input-group {
position: relative;
display: grid;
grid-template-columns: 7% 93%;
margin: 25px 0;
padding: 5px 0;
border-bottom: 2px solid #d9d9d9;
}
.input-group:nth-child(1) {
margin-bottom: 4px;
}
.input-group::before,
.input-group::after {
content: "";
position: absolute;
bottom: -2px;
width: 0;
height: 2px;
background-color: #c5d3f7;
transition: 0.5s;
}
.input-group::after {
right: 50%;
}
.input-group::before {
left: 50%;
}
.icon {
display: flex;
justify-content: center;
align-items: center;
}
.icon i {
color: #d9d9d9;
transition: 0.5s;
}
.input-group > div {
position: relative;
height: 45px;
}
.input-group > div > h5 {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: #d9d9d9;
font-size: 18px;
transition: 0.3s;
margin: 0;
padding: 0;
}
.input-group.focus .icon i {
color: #5392f0;
}
.input-group.focus div h5 {
top: -5px;
font-size: 15px;
}
.input-group.focus::after,
.input-group.focus::before {
width: 50%;
}
.input {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border: none;
outline: none;
background: none;
padding: 0.5rem 0.7rem;
font-size: 1.2rem;
color: #555;
font-family: "Roboto", sans-serif;
}
a {
display: block;
text-align: right;
text-decoration: none;
color: #999;
font-size: 0.9rem;
transition: 0.3s;
}
a:hover {
color: #5392f0;
}
.btn {
display: block;
width: 100%;
height: 50px;
border-radius: 25px;
margin: 1rem 0;
font-size: 1.2rem;
outline: none;
border: none;
background-image: linear-gradient(to right, #567dbe, #5392f0, #567dbe);
cursor: pointer;
color: #fff;
text-transform: uppercase;
font-family: "Roboto", sans-serif;
background-size: 200%;
transition: 0.5s;
}
.btn:hover {
background-position: right;
}
.copyright {
position: absolute;
width: 100%;
height: 50px;
bottom: 2px;
color: #5392f0;
text-align: center;
font-size: 18px;
font-family: "Roboto", sans-serif;
}
@media screen and (max-width: 1080px) {
.container {
grid-gap: 9rem;
}
}
@media screen and (max-width: 1024px) {
.login-form {
width: 290px;
}
.login-form h2 {
font-size: 2.4rem;
margin: 8px 0;
}
.img img {
width: 360px;
}
.avatar {
width: 280px;
height: 80px;
}
}
@media screen and (max-width: 768px) {
.wave {
display: none;
}
.img {
display: none;
}
.container {
grid-template-columns: 1fr;
}
.login-box {
justify-content: center;
}
}

322
src/style/sidebar.scss

@ -1,26 +1,39 @@
@import "../layout/theme/default-vars.scss";
@mixin merge-style( @mixin merge-style(
// 菜单选中后字体样式
$subMenuActiveText,
//菜单背景
$menuBg,
// 鼠标覆盖菜单时的背景
$menuHover,
// 子菜单背景
$subMenuBg,
// 鼠标覆盖子菜单时的背景
$subMenuHover,
// vertical模式下主体内容距离网页文档左侧的距离 // vertical模式下主体内容距离网页文档左侧的距离
$sideBarWidth,
$navTextColor
$sideBarWidth
) { ) {
$menuText: #7a80b4;
$menuActiveText: #7a80b4; $menuActiveText: #7a80b4;
@media screen and (min-width: 150px) and (max-width: 420px) {
.app-main-nofixed-header {
overflow-y: hidden;
}
}
@media screen and (min-width: 420px) {
.app-main-nofixed-header {
overflow: hidden;
}
}
.main-container { .main-container {
height: 100vh;
min-height: 100%; min-height: 100%;
transition: margin-left 0.28s; transition: margin-left 0.28s;
margin-left: $sideBarWidth; margin-left: $sideBarWidth;
position: relative; position: relative;
background: #f0f2f5;
@media screen and (min-width: 150px) and (max-width: 420px) {
.app-main .el-scrollbar__view:first-child {
overflow-y: hidden;
}
}
@media screen and (min-width: 420px) {
.app-main .el-scrollbar__view:first-child {
overflow: hidden;
}
}
} }
.fixed-header { .fixed-header {
@ -39,7 +52,7 @@
width: 100% !important; width: 100% !important;
+ .app-main { + .app-main {
padding-top: 37px;
padding-top: 37px !important;
} }
} }
} }
@ -50,8 +63,8 @@
.sidebar-container { .sidebar-container {
transition: width 0.28s; transition: width 0.28s;
width: $sideBarWidth;
background-color: $menuBg;
width: $sideBarWidth !important;
background: $menuBg;
height: 100%; height: 100%;
position: fixed; position: fixed;
font-size: 0; font-size: 0;
@ -91,8 +104,10 @@
a { a {
display: inline-block; display: inline-block;
display: flex;
padding-left: 10px;
flex-wrap: wrap;
width: 100%; width: 100%;
overflow: hidden;
} }
.el-menu { .el-menu {
@ -104,15 +119,18 @@
.el-menu-item, .el-menu-item,
.el-sub-menu__title { .el-sub-menu__title {
color: $menuText; color: $menuText;
padding: 0 20px 0 40px;
&:hover {
color: $menuTitleHover !important;
}
} }
// menu hover // menu hover
.submenu-title-noDropdown, .submenu-title-noDropdown,
.el-sub-menu__title { .el-sub-menu__title {
// background: $menuBg;
&:hover { &:hover {
background-color: $menuHover !important;
background-color: transparent;
} }
} }
@ -135,17 +153,23 @@
font-size: 12px; font-size: 12px;
min-width: $sideBarWidth !important; min-width: $sideBarWidth !important;
background-color: $subMenuBg !important; background-color: $subMenuBg !important;
}
&:hover {
background-color: $subMenuHover !important;
}
// 无子集的激活菜单背景
.is-active.submenu-title-noDropdown.outer-most {
background: $subMenuActiveBg;
}
// 有子集的激活菜单背景
.is-active.nest-menu {
background: $subMenuActiveBg !important;
} }
} }
.horizontal-header { .horizontal-header {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
background-color: $menuBg;
background: $menuBg;
width: 100%; width: 100%;
height: 62px; height: 62px;
align-items: center; align-items: center;
@ -160,10 +184,6 @@
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
&:hover {
background: $menuHover;
}
i { i {
font-size: 30px; font-size: 30px;
color: #1890ff; color: #1890ff;
@ -173,7 +193,7 @@
h4 { h4 {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
color: $navTextColor;
color: $subMenuActiveText;
transition: all 0.5s; transition: all 0.5s;
} }
} }
@ -189,7 +209,7 @@
display: flex; display: flex;
min-width: 280px; min-width: 280px;
align-items: center; align-items: center;
color: $navTextColor;
color: $subMenuActiveText;
justify-content: flex-end; justify-content: flex-end;
.screen-full { .screen-full {
@ -205,7 +225,7 @@
width: 40px; width: 40px;
padding: 11px; padding: 11px;
cursor: pointer; cursor: pointer;
color: $navTextColor;
color: $subMenuActiveText;
&:hover { &:hover {
background: $menuHover; background: $menuHover;
@ -220,7 +240,7 @@
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
cursor: pointer; cursor: pointer;
color: $navTextColor;
color: $subMenuActiveText;
&:hover { &:hover {
background: $menuHover; background: $menuHover;
@ -240,7 +260,7 @@
.el-icon-setting { .el-icon-setting {
height: 62px; height: 62px;
width: 40px; width: 40px;
padding: 11px;
padding: 12px;
display: flex; display: flex;
cursor: pointer; cursor: pointer;
align-items: center; align-items: center;
@ -261,16 +281,16 @@
.el-menu-item, .el-menu-item,
.el-sub-menu__title { .el-sub-menu__title {
color: $menuText; color: $menuText;
&:hover {
color: $menuTitleHover !important;
}
} }
.submenu-title-noDropdown, .submenu-title-noDropdown,
.el-sub-menu__title { .el-sub-menu__title {
height: 60px; height: 60px;
background: $menuBg; background: $menuBg;
&:hover {
background-color: $menuHover !important;
}
} }
.is-active > .el-sub-menu__title, .is-active > .el-sub-menu__title,
@ -296,22 +316,24 @@
background-color: $subMenuBg !important; background-color: $subMenuBg !important;
.el-menu-item { .el-menu-item {
color: $menuText;
background-color: $subMenuBg;
&:hover {
background-color: $subMenuHover;
span {
font-size: 12px;
margin-left: 10px;
} }
} }
.el-sub-menu__title { .el-sub-menu__title {
color: $menuText; color: $menuText;
span {
margin-left: 10px;
}
} }
} }
& > .el-menu { & > .el-menu {
i { i {
margin-right: 16px;
margin-right: 20px;
} }
} }
@ -329,9 +351,15 @@
font-size: 12px; font-size: 12px;
min-width: $sideBarWidth !important; min-width: $sideBarWidth !important;
background-color: $subMenuBg !important; background-color: $subMenuBg !important;
}
.el-menu-item,
.el-sub-menu__title {
color: $menuText;
background-color: $subMenuBg;
&:hover { &:hover {
background-color: $menuHover !important;
color: $menuTitleHover !important;
} }
} }
@ -340,11 +368,8 @@
color: $subMenuActiveText !important; color: $subMenuActiveText !important;
} }
.nest-menu .el-sub-menu > .el-sub-menu__title,
.el-menu-item {
&:hover {
background-color: $menuHover !important;
}
.el-menu-item.is-active.nest-menu {
background: $subMenuActiveBg !important;
} }
.el-menu-item, .el-menu-item,
@ -362,8 +387,13 @@
} }
} }
// horizontal菜单折叠
// horizontal菜单
.el-menu--horizontal { .el-menu--horizontal {
& > .el-sub-menu .el-sub-menu__icon-arrow {
position: static !important;
margin-top: 0;
}
.el-menu--popup { .el-menu--popup {
background-color: $subMenuBg !important; background-color: $subMenuBg !important;
@ -371,13 +401,18 @@
color: $menuText; color: $menuText;
background-color: $subMenuBg; background-color: $subMenuBg;
&:hover {
background-color: $subMenuHover;
span {
font-size: 12px;
margin-left: 10px;
} }
} }
.el-sub-menu__title { .el-sub-menu__title {
color: $menuText; color: $menuText;
span {
margin-left: 10px;
}
} }
} }
@ -394,7 +429,7 @@
background-color: $subMenuBg !important; background-color: $subMenuBg !important;
&:hover { &:hover {
background-color: $menuHover !important;
color: $menuTitleHover !important;
} }
} }
@ -413,17 +448,22 @@
} }
} }
.is-active {
transition: color 0.3s;
color: $subMenuActiveText !important;
}
.nest-menu .el-sub-menu > .el-sub-menu__title, .nest-menu .el-sub-menu > .el-sub-menu__title,
.el-menu-item { .el-menu-item {
&:hover { &:hover {
background-color: $menuHover !important;
color: $menuTitleHover !important;
} }
} }
// 有子集的激活菜单背景
.is-active.nest-menu {
background: $subMenuActiveBg !important;
}
.el-menu-item.is-active {
transition: color 0.3s;
color: $subMenuActiveText !important;
}
} }
.el-scrollbar__wrap { .el-scrollbar__wrap {
@ -435,6 +475,52 @@
min-width: $sideBarWidth !important; min-width: $sideBarWidth !important;
} }
// 有子菜单
.el-menu--collapse
.is-active.outer-most.el-sub-menu
> .el-sub-menu__title::before {
position: absolute;
top: 0;
left: 5px;
width: 3px;
height: 100%;
background-color: $menuActiveBefore;
content: "";
clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0);
}
// 无子菜单
.el-menu--collapse .is-active.submenu-title-noDropdown.outer-most::before {
position: absolute;
top: 0;
left: 5px;
width: 3px;
height: 100%;
background-color: $menuActiveBefore;
content: "";
clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0);
}
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before,
.el-menu--collapse .submenu-title-noDropdown.outer-most::before {
content: "";
display: block;
position: absolute;
height: 0;
width: 3px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
top: 50%;
}
// 手机端 // 手机端
.mobile { .mobile {
.fixed-header { .fixed-header {
@ -448,7 +534,7 @@
.sidebar-container { .sidebar-container {
transition: transform 0.28s; transition: transform 0.28s;
width: $sideBarWidth !important;
width: $sideBarWidth;
} }
&.hideSidebar { &.hideSidebar {
@ -468,14 +554,17 @@
} }
} }
body[data-layout="vertical"] {
.fixed-header + .app-main {
padding-top: 85px;
body[layout="vertical"] {
$sideBarWidth: 210px;
@include merge-style($sideBarWidth);
.sidebar-logo-container {
background: $sidebarLogo;
} }
.hideSidebar { .hideSidebar {
.fixed-header { .fixed-header {
width: calc(100% - 54px) !important;
width: calc(100% - 54px);
transition: width 0.28s; transition: width 0.28s;
} }
@ -484,7 +573,7 @@ body[data-layout="vertical"] {
} }
.main-container { .main-container {
margin-left: 54px !important;
margin-left: 54px;
} }
.submenu-title-noDropdown { .submenu-title-noDropdown {
@ -506,8 +595,10 @@ body[data-layout="vertical"] {
} }
} }
// 菜单折叠
.el-menu--collapse { .el-menu--collapse {
margin-left: -5px; //需优化的地方
margin-left: -5px;
.el-sub-menu { .el-sub-menu {
& > .el-sub-menu__title { & > .el-sub-menu__title {
& > span { & > span {
@ -519,101 +610,20 @@ body[data-layout="vertical"] {
} }
} }
} }
.submenu-title-noDropdown {
background: transparent !important;
}
} }
} }
} }
body[data-layout="horizontal"] {
body[layout="horizontal"] {
$sideBarWidth: 0;
@include merge-style($sideBarWidth);
.fixed-header { .fixed-header {
width: 100% !important;
width: 100%;
transition: none !important; transition: none !important;
} }
.fixed-header + .app-main {
padding-top: 98px;
}
}
// vertical模式下暗色主题
body[data-layout="vertical"][data-theme="dark"] {
$subMenuActiveText: #f4f4f5;
$menuBg: #1b2a47;
$menuHover: #2a395b;
$subMenuBg: #1f2d3d;
$subMenuHover: #001528;
$sideBarWidth: 210px;
$navTextColor: #fff;
@include merge-style(
$subMenuActiveText,
$menuBg,
$menuHover,
$subMenuBg,
$subMenuHover,
$sideBarWidth,
$navTextColor
);
}
// vertical模式下亮色主题
body[data-layout="vertical"][data-theme="light"] {
$subMenuActiveText: #409eff;
$menuBg: #fff;
$menuHover: #e0ebf6;
$subMenuBg: #fff;
$subMenuHover: #e0ebf6;
$sideBarWidth: 210px;
$navTextColor: #7a80b4;
@include merge-style(
$subMenuActiveText,
$menuBg,
$menuHover,
$subMenuBg,
$subMenuHover,
$sideBarWidth,
$navTextColor
);
}
// horizontal模式下暗色主题
body[data-layout="horizontal"][data-theme="dark"] {
$subMenuActiveText: #f4f4f5;
$menuBg: #1b2a47;
$menuHover: #2a395b;
$subMenuBg: #1f2d3d;
$subMenuHover: #001528;
$sideBarWidth: 0;
$navTextColor: #fff;
@include merge-style(
$subMenuActiveText,
$menuBg,
$menuHover,
$subMenuBg,
$subMenuHover,
$sideBarWidth,
$navTextColor
);
}
// horizontal模式下亮色主题
body[data-layout="horizontal"][data-theme="light"] {
$subMenuActiveText: #409eff;
$menuBg: #fff;
$menuHover: #e0ebf6;
$subMenuBg: #fff;
$subMenuHover: #e0ebf6;
$sideBarWidth: 0;
$navTextColor: #7a80b4;
@include merge-style(
$subMenuActiveText,
$menuBg,
$menuHover,
$subMenuBg,
$subMenuHover,
$sideBarWidth,
$navTextColor
);
} }

28
src/utils/http/core.ts

@ -41,12 +41,6 @@ class EnclosureHttp {
// 记录当前这一次cancelToken的key // 记录当前这一次cancelToken的key
private currentCancelTokenKey = ""; private currentCancelTokenKey = "";
private beforeRequestCallback: EnclosureHttpRequestConfig["beforeRequestCallback"] =
undefined;
private beforeResponseCallback: EnclosureHttpRequestConfig["beforeResponseCallback"] =
undefined;
public get cancelTokenList(): Array<cancelTokenType> { public get cancelTokenList(): Array<cancelTokenType> {
return this.sourceTokenList; return this.sourceTokenList;
} }
@ -126,9 +120,8 @@ class EnclosureHttp {
this.cancelRepeatRequest(); this.cancelRepeatRequest();
this.currentCancelTokenKey = cancelKey; this.currentCancelTokenKey = cancelKey;
// 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉 // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
if (typeof this.beforeRequestCallback === "function") {
this.beforeRequestCallback($config);
this.beforeRequestCallback = undefined;
if (typeof config.beforeRequestCallback === "function") {
config.beforeRequestCallback($config);
return $config; return $config;
} }
if (EnclosureHttp.initConfig.beforeRequestCallback) { if (EnclosureHttp.initConfig.beforeRequestCallback) {
@ -159,20 +152,21 @@ class EnclosureHttp {
const instance = EnclosureHttp.axiosInstance; const instance = EnclosureHttp.axiosInstance;
instance.interceptors.response.use( instance.interceptors.response.use(
(response: EnclosureHttpResoponse) => { (response: EnclosureHttpResoponse) => {
const $config = response.config;
// 请求每次成功一次就删除当前canceltoken标记 // 请求每次成功一次就删除当前canceltoken标记
const cancelKey = EnclosureHttp.genUniqueKey(response.config);
const cancelKey = EnclosureHttp.genUniqueKey($config);
this.deleteCancelTokenByCancelKey(cancelKey); this.deleteCancelTokenByCancelKey(cancelKey);
NProgress.done();
// 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉 // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
if (typeof this.beforeResponseCallback === "function") {
this.beforeResponseCallback(response);
this.beforeResponseCallback = undefined;
if (typeof $config.beforeResponseCallback === "function") {
$config.beforeResponseCallback(response);
return response.data; return response.data;
} }
if (EnclosureHttp.initConfig.beforeResponseCallback) { if (EnclosureHttp.initConfig.beforeResponseCallback) {
EnclosureHttp.initConfig.beforeResponseCallback(response); EnclosureHttp.initConfig.beforeResponseCallback(response);
return response.data; return response.data;
} }
NProgress.done();
return response.data; return response.data;
}, },
(error: EnclosureHttpError) => { (error: EnclosureHttpError) => {
@ -210,12 +204,6 @@ class EnclosureHttp {
...axiosConfig ...axiosConfig
} as EnclosureHttpRequestConfig); } as EnclosureHttpRequestConfig);
// 单独处理自定义请求/响应回掉 // 单独处理自定义请求/响应回掉
if (axiosConfig?.beforeRequestCallback) {
this.beforeRequestCallback = axiosConfig.beforeRequestCallback;
}
if (axiosConfig?.beforeResponseCallback) {
this.beforeResponseCallback = axiosConfig.beforeResponseCallback;
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
EnclosureHttp.axiosInstance EnclosureHttp.axiosInstance
.request(config) .request(config)

13
src/utils/storage/responsive.ts

@ -23,14 +23,23 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
locale: { locale: {
type: Object, type: Object,
default: Storage.getData(undefined, "locale") ?? { default: Storage.getData(undefined, "locale") ?? {
locale: config.Locale
locale: config.Locale ?? "zh"
} }
}, },
// layout模式以及主题 // layout模式以及主题
layout: { layout: {
type: Object, type: Object,
default: Storage.getData(undefined, "layout") ?? { default: Storage.getData(undefined, "layout") ?? {
layout: config.Layout
layout: config.Layout ?? "vertical",
theme: config.Theme ?? "default"
}
},
sets: {
type: Object,
default: Storage.getData(undefined, "sets") ?? {
grey: config.Grey ?? false,
weak: config.Weak ?? false,
hideTabs: config.HideTabs ?? false
} }
} }
}); });

184
src/views/login.vue

@ -1,20 +1,180 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from "vue";
import info, { ContextProps } from "../components/ReInfo/index.vue";
const contextInfo: ContextProps = reactive({
userName: "",
passWord: "",
verify: null,
svg: null
import { ref, computed } from "vue";
import { useRouter } from "vue-router";
import { initRouter } from "/@/router";
import { storageSession } from "/@/utils/storage";
import { addClass, removeClass } from "/@/utils/operate";
import bg from "/@/assets/login/bg.png";
import avatar from "/@/assets/login/avatar.svg";
import illustration0 from "/@/assets/login/illustration0.svg";
import illustration1 from "/@/assets/login/illustration1.svg";
import illustration2 from "/@/assets/login/illustration2.svg";
import illustration3 from "/@/assets/login/illustration3.svg";
import illustration4 from "/@/assets/login/illustration4.svg";
import illustration5 from "/@/assets/login/illustration5.svg";
import illustration6 from "/@/assets/login/illustration6.svg";
const router = useRouter();
// eslint-disable-next-line vue/return-in-computed-property
const currentWeek = computed(() => {
switch (String(new Date().getDay())) {
case "0":
return illustration0;
case "1":
return illustration1;
case "2":
return illustration2;
case "3":
return illustration3;
case "4":
return illustration4;
case "5":
return illustration5;
case "6":
return illustration6;
default:
return illustration4;
}
}); });
//
const onLogin = () => {};
let user = ref("admin");
let pwd = ref("123456");
const onLogin = (): void => {
storageSession.setItem("info", {
username: "admin",
accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
});
initRouter("admin").then(() => {});
router.push("/");
};
function onUserFocus() {
addClass(document.querySelector(".user"), "focus");
}
function onUserBlur() {
if (user.value.length === 0)
removeClass(document.querySelector(".user"), "focus");
}
function onPwdFocus() {
addClass(document.querySelector(".pwd"), "focus");
}
function onPwdBlur() {
if (pwd.value.length === 0)
removeClass(document.querySelector(".pwd"), "focus");
}
</script> </script>
<template> <template>
<div class="login">
<info :ruleForm="contextInfo" @on-behavior="onLogin" />
<img :src="bg" class="wave" />
<div class="container">
<div class="img">
<component :is="currentWeek"></component>
</div>
<div class="login-box">
<div class="login-form">
<avatar class="avatar" />
<h2
v-motion
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 100
}
}"
>
Pure Admin
</h2>
<div
class="input-group user focus"
v-motion
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 200
}
}"
>
<div class="icon">
<i class="fa fa-user"></i>
</div>
<div>
<h5>用户名</h5>
<input
type="text"
class="input"
v-model="user"
@focus="onUserFocus"
@blur="onUserBlur"
/>
</div>
</div>
<div
class="input-group pwd focus"
v-motion
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 300
}
}"
>
<div class="icon">
<i class="fa fa-lock"></i>
</div>
<div>
<h5>密码</h5>
<input
type="password"
class="input"
v-model="pwd"
@focus="onPwdFocus"
@blur="onPwdBlur"
/>
</div>
</div>
<button
class="btn"
v-motion
:initial="{
opacity: 0,
y: 10
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 400
}
}"
@click="onLogin"
>
登录
</button>
</div>
</div>
</div> </div>
</template> </template>
<style scoped>
@import url("/@/style/login.css");
</style>

2
src/views/permission/button/index.vue

@ -14,7 +14,7 @@ function changRole(value) {
</script> </script>
<template> <template>
<div class="app-container">
<div>
<el-radio-group v-model="auth" @change="changRole"> <el-radio-group v-model="auth" @change="changRole">
<el-radio-button label="admin"></el-radio-button> <el-radio-button label="admin"></el-radio-button>
<el-radio-button label="test"></el-radio-button> <el-radio-button label="test"></el-radio-button>

4
src/views/permission/page/index.vue

@ -1,7 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, unref } from "vue"; import { ref, unref } from "vue";
import { storageSession } from "/@/utils/storage"; import { storageSession } from "/@/utils/storage";
let purview = ref<string>(storageSession.getItem("info").username); let purview = ref<string>(storageSession.getItem("info").username);
function changRole() { function changRole() {
if (unref(purview) === "admin") { if (unref(purview) === "admin") {
storageSession.setItem("info", { storageSession.setItem("info", {
@ -20,7 +22,7 @@ function changRole() {
</script> </script>
<template> <template>
<div class="app-container">
<div>
<h4> <h4>
当前角色 当前角色
<span style="font-size: 26px">{{ purview }}</span> <span style="font-size: 26px">{{ purview }}</span>

17
src/views/register.vue

@ -1,17 +0,0 @@
<script setup lang="ts">
import { reactive } from "vue";
import info, { ContextProps } from "../components/ReInfo/index.vue";
const contextInfo: ContextProps = reactive({
userName: "",
passWord: "",
verify: null,
svg: null
});
</script>
<template>
<div class="register">
<info :ruleForm="contextInfo" />
</div>
</template>

4
types/global.d.ts

@ -90,6 +90,10 @@ declare global {
KeepAlive?: boolean; KeepAlive?: boolean;
Locale?: string; Locale?: string;
Layout?: string; Layout?: string;
Theme?: string;
Grey?: boolean;
Weak?: boolean;
HideTabs?: boolean;
MapConfigure?: { MapConfigure?: {
amapKey?: string; amapKey?: string;
baiduKey?: string; baiduKey?: string;

80
vite.config.ts

@ -6,8 +6,8 @@ import { warpperEnv } from "./build/utils";
import { createProxy } from "./build/proxy"; import { createProxy } from "./build/proxy";
import { viteMockServe } from "vite-plugin-mock"; import { viteMockServe } from "vite-plugin-mock";
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
import styleImport from "vite-plugin-style-import";
import ElementPlus from "unplugin-element-plus/vite"; import ElementPlus from "unplugin-element-plus/vite";
import themePreprocessorPlugin from "@zougt/vite-plugin-theme-preprocessor";
const pathResolve = (dir: string): string => { const pathResolve = (dir: string): string => {
return resolve(__dirname, ".", dir); return resolve(__dirname, ".", dir);
@ -55,19 +55,63 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
plugins: [ plugins: [
vue(), vue(),
vueJsx(), vueJsx(),
svgLoader(),
styleImport({
libs: [
// 按需加载vxe-table
{
libraryName: "vxe-table",
esModule: true,
ensureStyleFile: true,
resolveComponent: name => `vxe-table/es/${name}`,
resolveStyle: name => `vxe-table/es/${name}/style.css`
}
]
themePreprocessorPlugin({
scss: {
multipleScopeVars: [
{
scopeName: "layout-theme-default",
path: pathResolve("src/layout/theme/default-vars.scss")
},
{
scopeName: "layout-theme-light",
path: pathResolve("src/layout/theme/light-vars.scss")
},
{
scopeName: "layout-theme-dusk",
path: pathResolve("src/layout/theme/dusk-vars.scss")
},
{
scopeName: "layout-theme-volcano",
path: pathResolve("src/layout/theme/volcano-vars.scss")
},
{
scopeName: "layout-theme-yellow",
path: pathResolve("src/layout/theme/yellow-vars.scss")
},
{
scopeName: "layout-theme-mingQing",
path: pathResolve("src/layout/theme/mingQing-vars.scss")
},
{
scopeName: "layout-theme-auroraGreen",
path: pathResolve("src/layout/theme/auroraGreen-vars.scss")
},
{
scopeName: "layout-theme-pink",
path: pathResolve("src/layout/theme/pink-vars.scss")
},
{
scopeName: "layout-theme-saucePurple",
path: pathResolve("src/layout/theme/saucePurple-vars.scss")
}
],
// 默认取 multipleScopeVars[0].scopeName
defaultScopeName: "",
// 在生产模式是否抽取独立的主题css文件,extract为true以下属性有效
extract: true,
// 独立主题css文件的输出路径,默认取 viteConfig.build.assetsDir 相对于 (viteConfig.build.outDir)
outputDir: "",
// 会选取defaultScopeName对应的主题css文件在html添加link
themeLinkTagId: "head",
// "head"||"head-prepend" || "body" ||"body-prepend"
themeLinkTagInjectTo: "head",
// 是否对抽取的css文件内对应scopeName的权重类名移除
removeCssScopeName: false,
// 可以自定义css文件名称的函数
customThemeCssFileName: scopeName => scopeName
}
}), }),
svgLoader(),
ElementPlus({}), ElementPlus({}),
viteMockServe({ viteMockServe({
mockPath: "mock", mockPath: "mock",
@ -83,17 +127,17 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
optimizeDeps: { optimizeDeps: {
include: [ include: [
"element-plus/lib/locale/lang/zh-cn", "element-plus/lib/locale/lang/zh-cn",
"element-plus/lib/locale/lang/en",
"vxe-table/lib/locale/lang/zh-CN",
"vxe-table/lib/locale/lang/en-US"
]
"element-plus/lib/locale/lang/en"
],
exclude: ["@zougt/vite-plugin-theme-preprocessor/dist/browser-utils"]
}, },
build: { build: {
// @ts-ignore // @ts-ignore
sourcemap: false, sourcemap: false,
brotliSize: false, brotliSize: false,
// 消除打包大小超过500kb警告 // 消除打包大小超过500kb警告
chunkSizeWarningLimit: 2000
chunkSizeWarningLimit: 2000,
minify: false
}, },
define: { define: {
__INTLIFY_PROD_DEVTOOLS__: false __INTLIFY_PROD_DEVTOOLS__: false

4583
yarn.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save