Compare commits

..

131 Commits

Author SHA1 Message Date
xiaoxian521
46a48a5650 release: update 2.8.5 2022-01-21 16:46:48 +08:00
xiaoxian521
8fa3448af2 chore: update typescript 2022-01-21 16:33:45 +08:00
xiaoxian521
f236829b0f refactor: use @iconify-icons/ep 2022-01-21 16:10:10 +08:00
xiaoxian521
47dea87275 fix: update lintstagedrc 2022-01-18 17:06:15 +08:00
xiaoxian521
5e9198c2e0 fix: vscode setting 2022-01-18 17:05:42 +08:00
xiaoxian521
c9c8f20cc5 docs: update 2022-01-09 18:44:23 +08:00
xiaoxian521
a19c97f152 fix: add vite-plugin-live-reload 2022-01-09 12:11:50 +08:00
xiaoxian521
5e1e9d3c5f feat: 添加线上环境删console插件vite-plugin-remove-console 2022-01-07 17:57:55 +08:00
xiaoxian521
96153f8a14 feat: 添加WindiCSS支持 2022-01-07 17:20:25 +08:00
xiaoxian521
14adf692ab perf: theme 2022-01-07 14:45:21 +08:00
xiaoxian521
aa845fc3f5 release: update 2.8.0 2022-01-04 19:52:05 +08:00
xiaoxian521
079a455181 perf: router 2022-01-04 17:28:36 +08:00
xiaoxian521
07794d000c chore: update element-plus@1.3.0-beta.1 2021-12-31 14:09:59 +08:00
啝裳
e787f46414 Merge pull request #166 from xiaoxian521/perf/storage
perf: storage
2021-12-30 13:57:30 +08:00
lrl
e61d6109d7 perf: modify showLogo type: from string to Boolean 2021-12-29 22:45:18 +08:00
lrl
34eda14473 perf: storage 2021-12-29 21:51:05 +08:00
xiaoxian521
0eb0b7395e perf: layout style 2021-12-29 13:26:56 +08:00
xiaoxian521
73705eb0e4 perf: 优化国际化 2021-12-29 11:21:59 +08:00
xiaoxian521
d94fb0ef06 docs: update 2021-12-28 15:09:17 +08:00
xiaoxian521
17470ecce2 docs: update 2021-12-28 13:21:16 +08:00
一万
12db6966fb feat: guide page (#164)
* feat: add guide page

* perf: guide page
2021-12-27 10:51:25 +08:00
xiaoxian521
5070568b89 perf: theme 2021-12-24 10:46:39 +08:00
lrl
ef91f113ee fix: 动态标签页标记改为refreshRedirect 2021-12-22 22:49:34 +08:00
xiaoxian521
c875d20e83 perf: layout set 2021-12-22 13:40:59 +08:00
xiaoxian521
c86aae7b8b fix: 首页同一图表组件引入多次,只显示一个 2021-12-22 09:53:59 +08:00
一万
ee30cba471 perf: theme (#157) 2021-12-21 22:06:09 +08:00
xiaoxian521
7bcd8a800a perf: theme 2021-12-21 16:57:35 +08:00
xiaoxian521
903e298951 fix: delete realPath 2021-12-21 10:34:28 +08:00
啝裳
955b76f30a feat: ep theme (#156)
* feat: ep-theme

* perf: ep-theme

Co-authored-by: lrl <742798240@qq.com>
2021-12-20 22:27:47 +08:00
lrl
1b052023b6 perf: multiTags route delete realPath 2021-12-20 22:25:45 +08:00
xiaoxian521
3af52cf6c4 docs: update 2021-12-20 16:10:36 +08:00
xiaoxian521
660b6f4be8 feat: 暗黑模式 2021-12-20 14:19:38 +08:00
xiaoxian521
836c9e7cab release: update 2.7.0 2021-12-18 13:56:21 +08:00
xiaoxian521
4ded2a7a0c chore: update 2021-12-18 13:38:53 +08:00
xiaoxian521
dc1caecf1c fix: 修复动态路由子集设置rank为0报错 2021-12-17 18:27:06 +08:00
xiaoxian521
8de3e8b37f docs: update 2021-12-17 16:45:39 +08:00
xiaoxian521
ab93216dbe perf: breadCrumb 2021-12-17 14:46:30 +08:00
lrl
86177e430e fix: breadcrumb 2021-12-17 12:53:41 +08:00
lrl
93ac4fa813 perf: breadcrumb 2021-12-17 12:53:41 +08:00
xiaoxian521
0903008ced perf: menu tree 2021-12-16 13:47:13 +08:00
xiaoxian521
10fa0ee8c8 feat: 添加菜单树结构事例 2021-12-16 11:03:20 +08:00
xiaoxian521
eb0771e7ec feat: 打包后的文件提供传统浏览器兼容性支持 2021-12-15 13:25:18 +08:00
xiaoxian521
39159d5e7b docs: update 2021-12-15 11:41:40 +08:00
xiaoxian521
501891a21c docs: update 2021-12-15 11:36:18 +08:00
啝裳
cbffe31c70 Merge pull request #149 from xiaoxian521/fix/menuModel
fix: tag
2021-12-15 11:08:44 +08:00
xiaoxian521
3ef9444bcb docs: update 2021-12-15 11:05:50 +08:00
xiaoxian521
c81227bb4c docs: update 2021-12-15 11:04:12 +08:00
lrl
05ed941638 fix: menuModel 2021-12-14 22:04:52 +08:00
lrl
77c798eaed fix: menuModel 2021-12-14 21:59:54 +08:00
xiaoxian521
6ab9997a56 fix: router 2021-12-14 13:40:11 +08:00
lrl
b961659c2f fix: router 2021-12-14 13:23:02 +08:00
xiaoxian521
81bf66eca9 fix: router 2021-12-13 23:25:27 +08:00
啝裳
b251f8ff79 Merge pull request #148 from xiaoxian521/fix/router
fix: router
2021-12-13 21:34:29 +08:00
lrl
bae16008db fix: router 2021-12-13 21:13:54 +08:00
xiaoxian521
a0c54a6ac9 fix: types 2021-12-13 17:39:13 +08:00
xiaoxian521
438aab9bfc fix: router 2021-12-13 17:30:08 +08:00
xiaoxian521
e97bd9c8c4 feat: add hasPermissions util 2021-12-13 14:08:12 +08:00
xiaoxian521
653bafaa2b fix: permission directive 2021-12-13 13:54:28 +08:00
xiaoxian521
b82a3d3a2e docs: update 2021-12-13 11:23:12 +08:00
xiaoxian521
b8c8251c64 feat: 路由历史模式从env读取并支持base参数 2021-12-13 11:20:50 +08:00
xiaoxian521
00cc5a88e0 perf: router 2021-12-12 19:46:58 +08:00
xiaoxian521
5d6ed8da33 fix: nest menu 2021-12-12 18:25:04 +08:00
啝裳
d57e0e379e refactor: router (#145)
* refactor: router

* perf: router

* perf: router

* perf: router

Co-authored-by: lrl <742798240@qq.com>
2021-12-12 17:49:19 +08:00
xiaoxian521
7811f6bdeb style: layout 2021-12-11 11:04:00 +08:00
xiaoxian521
113e5f9db2 fix: layout style 2021-12-08 20:02:44 +08:00
xiaoxian521
1758711174 fix: vite build 2021-12-08 14:27:39 +08:00
啝裳
12879f9553 perf(router): refresh (#142)
* feat: set add multiTagsCache

* perf(router): refresh

* fix(multiTags): clear storage when close other tag

Co-authored-by: lrl <742798240@qq.com>
2021-12-07 15:40:10 +08:00
xiaoxian521
d6a358e851 fix: color name 2021-12-07 14:27:08 +08:00
xiaoxian521
9e7d78fd80 fix: layout style 2021-12-06 11:40:11 +08:00
xiaoxian521
570154a4f1 fix: delete useless pictures 2021-12-06 11:05:50 +08:00
xiaoxian521
5564250e7d fix: layout icon 2021-12-06 11:00:58 +08:00
GeeHon
8d65f8ee92 style: 解决eslint换行符告警 (#139) 2021-12-06 08:31:34 +08:00
xiaoxian521
11bf711838 refactor: axios methods and env 2021-12-04 01:16:50 +08:00
xiaoxian521
a845d4f237 fix: types 2021-12-02 10:17:05 +08:00
xiaoxian521
02c3a88ed6 perf: tag 2021-12-01 20:18:00 +08:00
xiaoxian521
a8a3e5b303 perf: tags 2021-12-01 17:20:25 +08:00
一万
cec5af55d9 fix(router): refresh (#137) 2021-11-30 22:57:28 +08:00
一万
d04ba7563a fix: router refresh (#136) 2021-11-30 21:42:30 +08:00
xiaoxian521
0450f004d3 docs: update 2021-11-28 23:43:52 +08:00
xiaoxian521
24a899bba0 chore: update wangeditor 2021-11-28 16:08:09 +08:00
啝裳
6c75296a02 Merge pull request #135 from xiaoxian521/feat/expandTag
feat: expand tag
2021-11-28 15:30:14 +08:00
lrl
6cca0d3ab2 Merge branch 'feat/expandTag' of https://github.com/xiaoxian521/vue-pure-admin into feat/expandTag 2021-11-28 15:08:02 +08:00
lrl
3d34663eda feat: tabs operation view 2021-11-28 15:06:22 +08:00
xiaoxian521
4bbf4c8548 fix: style 2021-11-28 12:07:04 +08:00
xiaoxian521
8685092260 feat: 菜单支持fontawesome、iconfont、ep/icons、自定义svg 2021-11-28 01:07:16 +08:00
啝裳
622464a8a4 Merge pull request #134 from xiaoxian521/refactor/tabs
refactor: tabs
2021-11-27 19:51:21 +08:00
lrl
be3a8a6949 perf: storage tags 2021-11-27 19:04:14 +08:00
一万
3acb65d42c perf: router (#133) 2021-11-27 16:38:18 +08:00
lrl
6d3a8c5a88 fix: update 2021-11-27 15:16:50 +08:00
xiaoxian521
b65b972353 fix: update 2021-11-27 10:05:36 +08:00
xiaoxian521
8c31ca1bad fix: update name 2021-11-26 22:54:29 +08:00
一万
8cb21b6321 Merge pull request #131 from xiaoxian521/perf/progress
perf: not show spinner
2021-11-25 21:45:30 +08:00
lrl
6d6eb98562 perf: not show Spinner 2021-11-25 21:41:33 +08:00
xiaoxian521
aca6a667f3 refactor: tabs 2021-11-25 21:30:44 +08:00
啝裳
d79e63f673 Merge pull request #130 from xiaoxian521/perf/layout
perf: layout
2021-11-25 21:11:37 +08:00
lrl
e67d2df677 perf: layout 2021-11-25 20:52:23 +08:00
xiaoxian521
d44da67dc4 perf: layout 2021-11-25 12:30:02 +08:00
啝裳
9d45e80856 Merge pull request #126 from xiaoxian521/fix/tag
fix(tag): the route is not normally closed and the close icon is repeated
2021-11-22 20:24:52 +08:00
lrl
be66c8bfb9 fix(tag): the route is not normally closed and the close icon is repeated 2021-11-22 20:15:37 +08:00
xiaoxian521
e26a0f949d perf: tag 2021-11-21 11:00:13 +08:00
啝裳
3e991e6e43 Merge pull request #125 from xiaoxian521/fix/sidebar
fix(sidebarItem): span focus and enter the border that appears
2021-11-20 22:41:05 +08:00
lrl
0337a0300c fix(sidebarItem): span focus and enter the border that appears 2021-11-20 22:29:14 +08:00
xiaoxian521
ee8e0eb733 docs: update 2021-11-20 19:47:28 +08:00
啝裳
39cca9ac25 Merge pull request #124 from xiaoxian521/refactor/tag
Refactor/tag
2021-11-20 19:19:22 +08:00
lrl
95140986b9 perf: tag 2021-11-20 19:06:23 +08:00
lrl
034f1577c2 perf: tag 2021-11-20 16:41:30 +08:00
xiaoxian521
c3645fd760 fix: 删除滚动支持 2021-11-20 11:09:52 +08:00
xiaoxian521
b1b236f736 style: fix 2021-11-20 11:00:40 +08:00
xiaoxian521
0b79b65575 refactor: tag 2021-11-19 15:13:28 +08:00
啝裳
067ed96de4 Merge pull request #120 from xiaoxian521/feat/headerNotice
feat: add header notice
2021-11-17 22:48:18 +08:00
xiaoxian521
f25e5d19a4 revert: will roll to the upper level 2021-11-17 22:45:54 +08:00
xiaoxian521
0380d4a17a style: notices style 2021-11-17 22:31:10 +08:00
lrl
89dc4e5052 perf: notice add scrollbar 2021-11-17 18:10:01 +08:00
lrl
12492a522f Merge branch 'main' into feat/headerNotice 2021-11-16 23:53:26 +08:00
lrl
5d9638758b perf: headerNotice 2021-11-16 23:46:34 +08:00
xiaoxian521
6b064bdef9 fix: icon 2021-11-16 22:21:05 +08:00
paobai
7aa895a2b7 perf: storage hooks 2021-11-16 15:52:58 +08:00
xiaoxian521
35f2f9e93f fix: i18n 2021-11-16 13:49:24 +08:00
xiaoxian521
f0a5f02588 fix: i18n 2021-11-16 13:16:04 +08:00
lrl
c4a6a337a3 Merge branch 'main' into feat/headerNotice 2021-11-16 10:04:36 +08:00
hb0730
44a4c9346b fix: i18n (#110) 2021-11-16 09:53:46 +08:00
lrl
bcf533af62 feat: add headerNotice 2021-11-15 23:19:00 +08:00
hb0730
aa8005a982 feat(i18n): 菜单动态支持i18n (#109)
* feat(i18n): 菜单动态支持i18n
2021-11-15 16:45:09 +08:00
hb0730
2d6ad99f6f feat(icon): findIcon function (#107)
* feat(icon): findIcon function

支持ElementPlus icon组件和 FontAwesomeIcon

* fix(menu): 支持第三方icon组件
2021-11-15 16:41:51 +08:00
xiaoxian521
1e1747a355 docs: update 2021-11-14 09:22:15 +08:00
xiaoxian521
6ba0bd7739 docs: update 2021-11-14 09:15:20 +08:00
xiaoxian521
b4088f4612 docs: update 2021-11-14 08:46:43 +08:00
xiaoxian521
3c4619d071 feat: 兼容fontawesome4和5版本 2021-11-13 14:39:38 +08:00
hb0730
10e8b296e3 fix: #100 (#103) 2021-11-12 12:39:08 +08:00
xiaoxian521
b702703472 docs: update 2021-11-10 20:40:06 +08:00
xiaoxian521
7590dc308c chore: update vite-plugin-theme-preprocessor 2021-11-10 18:08:40 +08:00
150 changed files with 6313 additions and 3114 deletions

14
.env
View File

@@ -1,14 +1,2 @@
# port
# 项目本地运行端口号
VITE_PORT = 8848
# title
VITE_TITLE = vue-pure-admin
# version
VITE_VERSION = 2.6.0
# open
VITE_OPEN = false
# public path
VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
VITE_PROXY = [ ["/api", "http://127.0.0.1:3000" ] ]

View File

@@ -1,14 +1,14 @@
# port
# 项目本地运行端口号
VITE_PORT = 8848
# title
VITE_TITLE = vue-pure-admin
# version
VITE_VERSION = 2.6.0
# open
VITE_OPEN = false
# public path
# 开发环境读取配置文件路径
VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
VITE_PROXY = [ ["/api", "http://127.0.0.1:3000" ] ]
# 开发环境代理
VITE_PROXY_DOMAIN = /api
# 开发环境路由历史模式
VITE_ROUTER_HISTORY = "hash"
# 开发环境后端地址
VITE_PROXY_DOMAIN_REAL = "http://127.0.0.1:3000"

View File

@@ -1,2 +1,11 @@
# public path
VITE_PUBLIC_PATH = /manages/
# 线上环境项目打包路径
VITE_PUBLIC_PATH = /
# 线上环境路由历史模式
VITE_ROUTER_HISTORY = "hash"
# 线上环境后端地址
VITE_PROXY_DOMAIN_REAL = ""
# 是否为打包后的文件提供传统浏览器兼容性支持 支持 true 不支持 false
VITE_LEGACY = false

View File

@@ -70,6 +70,12 @@ module.exports = {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"prettier/prettier": [
"error",
{
endOfLine: "auto"
}
]
}
};

View File

@@ -1,8 +1,6 @@
module.exports = {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"{!(package)*.json,.!(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"],

16
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"recommendations": [
"johnsoncodehk.vscode-typescript-vue-plugin",
"voorjaar.windicss-intellisense",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"johnsoncodehk.volar",
"lokalise.i18n-ally",
"mikestead.dotenv",
"eamodio.gitlens",
"antfu.iconify"
]
}

24
.vscode/settings.json vendored
View File

@@ -1,12 +1,4 @@
{
// 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",
@@ -24,26 +16,28 @@
"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"]
"i18n-ally.localesPaths": ["src/plugins/i18n"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"]
}

17
.vscode/vue3.2.code-snippets vendored Normal file
View File

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

View File

@@ -1,20 +0,0 @@
{
"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"
}
}

View File

@@ -1,3 +1,48 @@
# 2.8.5 (2022-1-21)
### 🎫 Feat
- Added `WindiCSS` support
- Add online environment remove console plugin `vite-plugin-remove-console`
### ✔️ refactor
- Replace `@element-plus/icons-vue` with `@iconify-icons/ep`
# 2.8.0(2022-1-4)
### 🎫 Feat
-Added dark theme
-Add element-plus custom theme
-Add guide page
### 🍏 Perf
-Optimize internationalization, compatible with the vscode plug-in i18n Ally smart reminder
-Optimize the back-end return routing structure
-Optimize local storage, with four built-in buttons `responsive-configure`, `responsive-locale`, `responsive-layout`, `responsive-tags`, which are basic configuration, international configuration, layout configuration, and tab persistent configuration
# 2.7.0(2021-12-18)
### 🎫 Feat
- New tab reuse
- New message reminder template
- Added front-end menu tree structure example
- Refactor routing, optimize permissions modules, and bring a more convenient experience
- Refactor the env environment and http request to bring a more convenient experience
- Currently, the tabs of the platform are forced to associate with local storage. The next step is to put the tabs in the memory by default and support configurable persistent tabs
- Navigation menu icons support fontawesome, iconfont, remixicon, element-plus/icons, custom svg
- Update font-awesome to version 5.0, because versions below 5.0 are no longer officially maintained, but the platform will still be compatible with font-awesome4 version
### 🍏 Perf
- Optimize the tab page to bring a better interactive experience
- Routing title supports direct writing in Chinese, which can be separated from internationalization
- Route history mode is read from env and supports base parameter
- Packaged files provide traditional browser compatibility support, configure VITE_LEGACY to true
# 2.6.0(2021-11-10)
### 🎫 Feat

View File

@@ -1,3 +1,48 @@
# 2.8.5 (2022-1-21)
### 🎫 Feat
- Added `WindiCSS` support
- Add online environment remove console plugin `vite-plugin-remove-console`
### ✔️ refactor
- Replace `@element-plus/icons-vue` with `@iconify-icons/ep`
# 2.8.0(2022-1-4)
### 🎫 Feat
-Added dark theme
-Add element-plus custom theme
-Add guide page
### 🍏 Perf
-Optimize internationalization, compatible with the vscode plug-in i18n Ally smart reminder
-Optimize the back-end return routing structure
-Optimize local storage, with four built-in buttons `responsive-configure`, `responsive-locale`, `responsive-layout`, `responsive-tags`, which are basic configuration, international configuration, layout configuration, and tab persistent configuration
# 2.7.0(2021-12-18)
### 🎫 Feat
- New tab reuse
- New message reminder template
- Added front-end menu tree structure example
- Refactor routing, optimize permissions modules, and bring a more convenient experience
- Refactor the env environment and http request to bring a more convenient experience
- Currently, the tabs of the platform are forced to associate with local storage. The next step is to put the tabs in the memory by default and support configurable persistent tabs
- Navigation menu icons support fontawesome, iconfont, remixicon, element-plus/icons, custom svg
- Update font-awesome to version 5.0, because versions below 5.0 are no longer officially maintained, but the platform will still be compatible with font-awesome4 version
### 🍏 Perf
- Optimize the tab page to bring a better interactive experience
- Routing title supports direct writing in Chinese, which can be separated from internationalization
- Route history mode is read from env and supports base parameter
- Packaged files provide traditional browser compatibility support, configure VITE_LEGACY to true
# 2.6.0(2021-11-10)
### 🎫 Feat

View File

@@ -1,3 +1,48 @@
# 2.8.5(2022-1-21)
### 🎫 Feat
- 添加 `WindiCSS` 支持
- 添加线上环境删 console 插件`vite-plugin-remove-console`
### ✔️ refactor
- 使用`@iconify-icons/ep`替换`@element-plus/icons-vue`
# 2.8.0(2022-1-4)
### 🎫 Feat
- 添加暗黑主题
- 添加 element-plus 自定义主题
- 添加引导页
### 🍏 Perf
- 优化国际化,兼容 vscode 插件 i18n Ally 智能提醒
- 优化后端返回路由结构
- 优化本地存储,内置四个键`responsive-configure``responsive-locale``responsive-layout``responsive-tags`,分别为基本配置、国际化配置、布局配置、标签页持久化配置
# 2.7.0(2021-12-18)
### 🎫 Feat
- 新增标签页复用
- 新增消息提醒模版
- 新增前端菜单树结构例子
- 重构路由,优化权限模块,带来更方便的体验
- 重构 env 环境和 http 请求,带来更方便的体验
- 目前平台的标签页强制关联了本地存储,下一步标签页默认放到内存中并支持可配置持久化标签页
- 导航菜单图标支持 fontawesome、iconfont、remixicon、element-plus/icons、自定义 svg
- 更新 font-awesome 到 5.0 版本,因为 5.0 以下的版本官方不再维护,但平台依旧会兼容 font-awesome4 版本
### 🍏 Perf
- 优化标签页,带来更好的交互体验
- 路由 title 支持直接写中文,可脱离国际化
- 路由历史模式从 env 读取并支持 base 参数
- 打包后的文件提供传统浏览器兼容性支持,配置 VITE_LEGACY 为 true
# 2.6.0(2021-11-10)
### 🎫 Feat

View File

@@ -1,6 +1,8 @@
<h1>vue-pure-admin</h1>
[![license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin.svg)](LICENSE)
![GitHub license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin?style=flat)
![GitHub stars](https://img.shields.io/github/stars/xiaoxian521/vue-pure-admin?color=fa6470&style=flat)
![GitHub forks](https://img.shields.io/github/forks/xiaoxian521/vue-pure-admin?style=flat)
**English** | [中文](./README.md)
@@ -8,34 +10,27 @@
vue-pure-admin is a free and open source middle and back-end template. Using the latest `vue3` `vite2` `Element-Plus` `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
## Supporting video tutorial
## Supporting Video
bilibili<https://www.bilibili.com/video/BV1534y1S7HV/>
- [Click Watch Tutorial](https://www.bilibili.com/video/BV1534y1S7HV)
- [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
## Docs
- [Click Watch Docs](https://pure-admin-doc.vercel.app)
## Thin
- [Click Watch Thin](https://github.com/xiaoxian521/pure-admin-thin)
## Preview
- [vue-pure-admin](http://yiming_chang.gitee.io/manages)
Click to log in without password
<p align="center">
<img alt="PureAdmin Logo" width="100%" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f5ee80eee1014fb4a53c5bb37574a5f5~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dec0672a62e141f3b7f626c22ff6c7ef~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f586f1353de74b1b88cc9f89fce2146e~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a28fc0af7ac44e3b8f30469cba4a9993~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a17e54329cda4d76aa9c1c4f2a4715d3~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d884fb611da74ee0bdc17c29014d0260~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/abed44ac1f2744e897c28d1689bcb517~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf2f068650f44a0787c699f5a20c75a6~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73c575dd06474731ad8ab9d853f1ddfd~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/99389b90f5ac4db9b0d61d99dd9a1454~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1546f1c6014446db6c9983934aedc86~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cfb0093b77c34e87b094daaa4304bc2d~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe133cc6db3245f9b1b37f231d040550~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3110a12d63e4f6fb314e60bf18bdb66~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45b93ec453e3406a939affe65ddcc803~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cdc2dc88c1ef4aafbaaade820442c986~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4c91b162206485b88cc58a72ff54a01~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/549c3184697f4d268a78c9833e5ec2ea~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/381fc957fac143db9f06efdd389d88a3~tplv-k3u1fbpfcp-watermark.awebp?">
</p>
### Use Gitpod
@@ -125,15 +120,15 @@ Support modern browsers, not IE
## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support
<img src="http://yiming_chang.gitee.io/manages/pay.png" width="360px" height="480px" />
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## Exchange Group
## WeChat Exchange Group
[WeChat exchange group, click to scan the code to enter the group](https://juejin.cn/post/6948419379566477342/)
For the better development of the project, you can choose to donate 10 yuan and add the following WeChat to pull you into the group. After adding, please consciously send a screenshot of the donation
My WeChat: 18237613535, pull you into the group
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
## License

View File

@@ -1,6 +1,8 @@
<h1>vue-pure-admin</h1>
[![license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin.svg)](LICENSE)
![GitHub license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin?style=flat)
![GitHub stars](https://img.shields.io/github/stars/xiaoxian521/vue-pure-admin?color=fa6470&style=flat)
![GitHub forks](https://img.shields.io/github/forks/xiaoxian521/vue-pure-admin?style=flat)
**中文** | [English](./README.en-US.md)
@@ -8,34 +10,27 @@
vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3` `vite2` `Element-Plus` `TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
## 配套视频教程
## 配套视频
bilibili<https://www.bilibili.com/video/BV1534y1S7HV/>
- [点我查看教程](https://www.bilibili.com/video/BV1534y1S7HV)
- [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
## 配套文档
- [点我查看文档](https://pure-admin-doc.vercel.app)
## 精简版
- [点我查看精简版](https://github.com/xiaoxian521/pure-admin-thin)
## 预览
- [vue-pure-admin](http://yiming_chang.gitee.io/manages)
点击免密登录
<p align="center">
<img alt="PureAdmin Logo" width="100%" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f5ee80eee1014fb4a53c5bb37574a5f5~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dec0672a62e141f3b7f626c22ff6c7ef~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f586f1353de74b1b88cc9f89fce2146e~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a28fc0af7ac44e3b8f30469cba4a9993~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a17e54329cda4d76aa9c1c4f2a4715d3~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d884fb611da74ee0bdc17c29014d0260~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/abed44ac1f2744e897c28d1689bcb517~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf2f068650f44a0787c699f5a20c75a6~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73c575dd06474731ad8ab9d853f1ddfd~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/99389b90f5ac4db9b0d61d99dd9a1454~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f1546f1c6014446db6c9983934aedc86~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cfb0093b77c34e87b094daaa4304bc2d~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fe133cc6db3245f9b1b37f231d040550~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3110a12d63e4f6fb314e60bf18bdb66~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/45b93ec453e3406a939affe65ddcc803~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cdc2dc88c1ef4aafbaaade820442c986~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f4c91b162206485b88cc58a72ff54a01~tplv-k3u1fbpfcp-watermark.image">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/549c3184697f4d268a78c9833e5ec2ea~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/381fc957fac143db9f06efdd389d88a3~tplv-k3u1fbpfcp-watermark.awebp?">
</p>
### 使用 Gitpod
@@ -125,15 +120,15 @@ pnpm build
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持
<img src="http://yiming_chang.gitee.io/manages/pay.png" width="360px" height="480px" />
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## 交流群
## 微信交流群
[微信交流群,点击扫码进群](https://juejin.cn/post/6948419379566477342/)
为了项目更好的发展,你可选择捐赠 10 元后添加下图微信拉你进群,添加后请自觉发捐赠截图
本人微信18237613535拉你进群
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
## 许可证

View File

@@ -1,6 +0,0 @@
const productPlugins = [];
process.env.NODE_ENV === "production" &&
productPlugins.push("transform-remove-console");
module.exports = {
plugins: [...productPlugins]
};

View File

@@ -1,5 +1,14 @@
// 处理环境变量
const warpperEnv = (envConf: Recordable): ViteEnv => {
const ret: any = {};
// 此处为默认值,无需修改
const ret: ViteEnv = {
VITE_PORT: 8848,
VITE_PUBLIC_PATH: "",
VITE_PROXY_DOMAIN: "",
VITE_PROXY_DOMAIN_REAL: "",
VITE_ROUTER_HISTORY: "",
VITE_LEGACY: false
};
for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, "\n");
@@ -9,13 +18,6 @@ const warpperEnv = (envConf: Recordable): ViteEnv => {
if (envName === "VITE_PORT") {
realName = Number(realName);
}
if (envName === "VITE_PROXY" && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = "";
}
}
ret[envName] = realName;
if (typeof realName === "string") {
process.env[envName] = realName;
@@ -25,8 +27,15 @@ const warpperEnv = (envConf: Recordable): ViteEnv => {
}
return ret;
};
// 跨域代理重写
const regExps = (value: string, reg: string): string => {
return value.replace(new RegExp(reg, "g"), "");
};
// 环境变量
const loadEnv = (): ViteEnv => {
return import.meta.env;
};
export { loadEnv, warpperEnv };
export { warpperEnv, regExps, loadEnv };

View File

@@ -1,19 +0,0 @@
type ProxyItem = [string, string];
type ProxyList = ProxyItem[];
const regExps = (value: string, reg: string): string => {
return value.replace(new RegExp(reg, "g"), "");
};
export function createProxy(list: ProxyList = []) {
const ret: any = {};
for (const [prefix, target] of list) {
ret[prefix] = {
target: target,
changeOrigin: true,
rewrite: (path: string) => regExps(path, prefix)
};
}
return ret;
}

View File

@@ -27,94 +27,78 @@
display: flex;
justify-content: center;
align-items: center;
background: #000;
overflow: hidden;
font-family: "Reggae One", cursive;
}
p {
font-size: 8vw;
overflow: hidden;
-webkit-text-stroke: 3px #7272a5;
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation: loadAnimation 1.8s infinite ease-in-out;
animation: loadAnimation 1.8s infinite ease-in-out;
}
span {
display: block;
font-size: 20px;
overflow: hidden;
color: green;
text-align: center;
.loader {
color: #406eeb;
font-size: 10px;
margin: 80px auto;
position: relative;
text-indent: -9999em;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
p::before {
content: " ";
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-image: linear-gradient(45deg, #ff269b, #2ab5f5, #ffbf00);
mix-blend-mode: multiply;
}
p::after {
.loader:before,
.loader:after {
content: "";
background: radial-gradient(circle, #fff, #000 50%);
background-size: 25% 25%;
position: absolute;
top: -100%;
left: -100%;
right: 0;
bottom: 0;
mix-blend-mode: color-dodge;
animation: mix 2s linear infinite;
top: 0;
}
@keyframes mix {
to {
transform: translate(50%, 50%);
.loader:before {
left: -3.5em;
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@-webkit-keyframes loadAnimation {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
@keyframes loadAnimation {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
</style>
<div class="g-container">
<p>Pure-Admin</p>
<span class="_develop"></span>
</div>
<div class="loader">Loading...</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>
</body>
</html>

View File

@@ -5,28 +5,32 @@ import { MockMethod } from "vite-plugin-mock";
const systemRouter = {
path: "/system",
name: "system",
redirect: "/system/user",
redirect: "/system/user/index",
meta: {
icon: "Setting",
title: "message.hssysManagement",
icon: "setting",
title: "menus.hssysManagement",
i18n: true,
showLink: true,
rank: 6
},
children: [
{
path: "/system/user",
path: "/system/user/index",
name: "user",
meta: {
title: "message.hsBaseinfo",
title: "menus.hsBaseinfo",
i18n: true,
showLink: true
}
},
{
path: "/system/dict",
path: "/system/dict/index",
name: "dict",
meta: {
title: "message.hsDict",
showLink: true
title: "menus.hsDict",
i18n: true,
showLink: true,
keepAlive: true
}
}
]
@@ -35,27 +39,30 @@ const systemRouter = {
const permissionRouter = {
path: "/permission",
name: "permission",
redirect: "/permission/page",
redirect: "/permission/page/index",
meta: {
title: "message.permission",
icon: "Lollipop",
title: "menus.permission",
icon: "lollipop",
i18n: true,
showLink: true,
rank: 3
},
children: [
{
path: "/permission/page",
path: "/permission/page/index",
name: "permissionPage",
meta: {
title: "message.permissionPage",
title: "menus.permissionPage",
i18n: true,
showLink: true
}
},
{
path: "/permission/button",
path: "/permission/button/index",
name: "permissionButton",
meta: {
title: "message.permissionButton",
title: "menus.permissionButton",
i18n: true,
showLink: true,
authority: []
}
@@ -63,6 +70,41 @@ const permissionRouter = {
]
};
const tabsRouter = {
path: "/tabs",
name: "reTabs",
redirect: "/tabs/index",
meta: {
icon: "IF-team-icontabs",
title: "menus.hstabs",
i18n: true,
showLink: true,
rank: 8
},
children: [
{
path: "/tabs/index",
name: "reTabs",
meta: {
title: "menus.hstabs",
showLink: true,
i18n: true
}
},
{
path: "/tabs/detail",
name: "tabDetail",
meta: {
title: "",
showLink: false,
i18n: false,
dynamicLevel: 3,
refreshRedirect: "/tabs/index"
}
}
]
};
// 添加不同按钮权限到/permission/button页面中
function setDifAuthority(authority, routes) {
routes.children[1].meta.authority = [authority];
@@ -77,12 +119,16 @@ export default [
if (query.name === "admin") {
return {
code: 0,
info: [systemRouter, setDifAuthority("v-admin", permissionRouter)]
info: [
tabsRouter,
systemRouter,
setDifAuthority("v-admin", permissionRouter)
]
};
} else {
return {
code: 0,
info: [setDifAuthority("v-test", permissionRouter)]
info: [tabsRouter, setDifAuthority("v-test", permissionRouter)]
};
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vue-pure-admin",
"version": "2.6.0",
"version": "2.8.5",
"private": true,
"engines": {
"node": ">= 16",
@@ -18,7 +18,7 @@
"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:pretty": "pretty-quick --staged",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint && pnpm lint:pretty",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky install",
"preinstall": "npx only-allow pnpm"
},
@@ -29,63 +29,69 @@
],
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@element-plus/icons": "^0.0.11",
"@ctrl/tinycolor": "^3.4.0",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-5",
"@logicflow/core": "0.7.1",
"@logicflow/extension": "0.7.1",
"@vueuse/core": "^6.7.1",
"@vueuse/motion": "^2.0.0-beta.4",
"@vueuse/core": "^7.5.3",
"@vueuse/motion": "^2.0.0-beta.9",
"@vueuse/shared": "^7.5.3",
"animate.css": "^4.1.1",
"await-to-js": "^3.0.0",
"axios": "^0.21.1",
"axios": "^0.25.0",
"cropperjs": "^1.5.11",
"css-color-function": "^1.3.3",
"dayjs": "^1.10.7",
"driver.js": "^0.9.8",
"echarts": "^5.2.1",
"element-plus": "1.2.0-beta.3",
"element-plus": "1.3.0-beta.1",
"element-resize-detector": "^1.2.3",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1",
"lodash-es": "^4.17.21",
"lowdb": "^3.0.0",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.0-rc.14",
"pinia": "^2.0.9",
"qs": "^6.10.1",
"remixicon": "^2.5.0",
"resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^1.0.11",
"sortablejs": "1.13.0",
"typescript-cookie": "^1.0.0",
"rgb-hex": "^4.0.0",
"v-contextmenu": "3.0.0",
"vue": "^3.2.21",
"vue-i18n": "^9.2.0-beta.3",
"vue": "^3.2.27",
"vue-i18n": "^9.2.0-beta.26",
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.11",
"vue-types": "^4.1.0",
"vue-router": "^4.0.12",
"vue-types": "^4.1.1",
"vuedraggable": "4.1.0",
"vxe-table": "4.0.30",
"wangeditor": "4.7.7",
"xe-ajax": "4.0.5",
"xe-utils": "3.4.0",
"vxe-table": "^4.1.18",
"wangeditor": "^4.7.9",
"xe-utils": "^3.5.2",
"xgplayer": "2.28.0"
},
"devDependencies": {
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@iconify-icons/ep": "^1.1.3",
"@iconify/vue": "^3.1.2",
"@types/element-resize-detector": "1.1.3",
"@types/js-cookie": "^3.0.1",
"@types/mockjs": "1.0.3",
"@types/node": "14.14.14",
"@types/nprogress": "0.2.0",
"@types/qs": "^6.9.7",
"@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",
"@vitejs/plugin-legacy": "^1.6.4",
"@vitejs/plugin-vue": "^2.0.1",
"@vitejs/plugin-vue-jsx": "^1.3.3",
"@vue/eslint-config-prettier": "6.0.0",
"@vue/eslint-config-typescript": "7.0.0",
"@zougt/vite-plugin-theme-preprocessor": "^1.3.10",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.4",
"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",
@@ -97,19 +103,23 @@
"prettier": "2.3.2",
"pretty-quick": "3.1.1",
"rimraf": "3.0.2",
"sass": "^1.43.4",
"sass": "^1.45.0",
"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",
"vite": "latest",
"typescript": "^4.5.5",
"unplugin-element-plus": "^0.2.0",
"vite": "^2.7.13",
"vite-plugin-live-reload": "^2.1.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-style-import": "^1.2.1",
"vite-svg-loader": "^2.2.0",
"vue-eslint-parser": "7.10.0"
"vite-plugin-remove-console": "^0.0.6",
"vite-plugin-style-import": "^1.4.1",
"vite-plugin-windicss": "^1.6.1",
"vite-svg-loader": "2.2.0",
"vue-eslint-parser": "7.10.0",
"windicss": "^3.4.3"
},
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
"author": "xiaoxian521",

2565
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,23 @@
{
"Version": "2.6.0",
"Version": "2.8.5",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,
"MultiTagsCache": false,
"KeepAlive": true,
"Locale": "zh",
"Layout": "vertical",
"Theme": "default",
"DarkMode": false,
"Grey": false,
"Weak": false,
"HideTabs": false,
"SidebarStatus": true,
"EpThemeColor": "#409EFF",
"ShowLogo": true,
"ShowModel": "smart",
"MapConfigure": {
"amapKey": "97b3248d1553172e81f168cf94ea667e",
"baiduKey": "wTHbkkEweiFqZLKunMIjcrb2RcqNXkhc",
"options": {
"resizeEnable": true,
"center": [113.6401, 34.72468],

View File

@@ -5,10 +5,11 @@
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import en from "element-plus/lib/locale/lang/en";
export default {
export default defineComponent({
name: "app",
components: {
[ElConfigProvider.name]: ElConfigProvider
@@ -18,5 +19,5 @@ export default {
return this.$storage.locale?.locale === "zh" ? zhCn : en;
}
}
};
});
</script>

View File

@@ -1,11 +1,11 @@
import { http } from "../utils/http";
// 地图数据
export const mapJson = (data?: object) => {
return http.request("get", "/getMapInfo", data);
export const mapJson = (params?: object) => {
return http.request("get", "/getMapInfo", { params });
};
// echarts数据
export const echartsJson = (data?: object) => {
return http.request("get", "/getEchartsInfo", data);
export const echartsJson = (params?: object) => {
return http.request("get", "/getEchartsInfo", { params });
};

View File

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

View File

@@ -13,5 +13,14 @@ export const getVerify = (): userType => {
// 登录
export const getLogin = (data: object) => {
return http.request("post", "/login", data);
return http.request("post", "/login", { data });
};
// 刷新token
export const refreshToken = (data: object) => {
return http.request("post", "/refreshToken", { data });
};
// export const searchVague = (data: object) => {
// return http.request("post", "/searchVague", { data });
// };

BIN
src/assets/avatars.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 2208059 */
src: url("iconfont.woff2?t=1636197082361") format("woff2"),
url("iconfont.woff?t=1636197082361") format("woff"),
url("iconfont.ttf?t=1636197082361") format("truetype");
src: url("iconfont.woff2?t=1638023560828") format("woff2"),
url("iconfont.woff?t=1638023560828") format("woff"),
url("iconfont.ttf?t=1638023560828") format("truetype");
}
.iconfont {
@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}
.team-icontabs::before {
content: "\e63e";
}
.team-iconlogo::before {
content: "\e620";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,13 @@
"css_prefix_text": "team-icon",
"description": "pure-admin",
"glyphs": [
{
"icon_id": "20594647",
"name": "标签页",
"font_class": "tabs",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "22129506",
"name": "水能",

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
src/assets/svg/dark.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981z"/></svg>

After

Width:  |  Height:  |  Size: 263 B

1
src/assets/svg/day.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" class="re-screen" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><g fill="currentColor"><path d="M3.5 4H1V3h2V1h1v2.5l-.5.5zM13 3V1h-1v2.5l.5.5H15V3h-2zm-1 9.5V15h1v-2h2v-1h-2.5l-.5.5zM1 12v1h2v2h1v-2.5l-.5-.5H1zm11-1.5l-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5v5zM10 7H6v2h4V7z"></path></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" class="re-screen" color="#00000073" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><g fill="currentColor"><path d="M3.5 4H1V3h2V1h1v2.5l-.5.5zM13 3V1h-1v2.5l.5.5H15V3h-2zm-1 9.5V15h1v-2h2v-1h-2.5l-.5.5zM1 12v1h2v2h1v-2.5l-.5-.5H1zm11-1.5l-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5v5zM10 7H6v2h4V7z"></path></g></svg>

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 452 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" class="re-screen" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><g fill="currentColor"><path d="M3 12h10V4H3v8zm2-6h6v4H5V6zM2 6H1V2.5l.5-.5H5v1H2v3zm13-3.5V6h-1V3h-3V2h3.5l.5.5zM14 10h1v3.5l-.5.5H11v-1h3v-3zM2 13h3v1H1.5l-.5-.5V10h1v3z"></path></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="1em" height="1em" class="re-screen" color="#00000073" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16"><g fill="currentColor"><path d="M3 12h10V4H3v8zm2-6h6v4H5V6zM2 6H1V2.5l.5-.5H5v1H2v3zm13-3.5V6h-1V3h-3V2h3.5l.5.5zM14 10h1v3.5l-.5.5H11v-1h3v-3zM2 13h3v1H1.5l-.5-.5V10h1v3z"></path></g></svg>

Before

Width:  |  Height:  |  Size: 403 B

After

Width:  |  Height:  |  Size: 421 B

View File

@@ -12,8 +12,15 @@ import { useEventListener, tryOnUnmounted, useTimeoutFn } from "@vueuse/core";
let echartInstance: ECharts;
const props = defineProps({
index: {
type: Number,
default: 0
}
});
function initechartInstance() {
const echartDom = document.querySelector(".bar");
const echartDom = document.querySelector(".bar" + props.index);
if (!echartDom) return;
// @ts-ignore
echartInstance = echarts.init(echartDom);
@@ -85,12 +92,5 @@ tryOnUnmounted(() => {
</script>
<template>
<div class="bar"></div>
<div :class="'bar' + props.index" style="width: 100%; height: 35vh"></div>
</template>
<style scoped>
.bar {
width: 100%;
height: 35vh;
}
</style>

View File

@@ -10,52 +10,48 @@ const lists = ref([
</script>
<template>
<el-descriptions
class="margin-top"
direction="vertical"
:column="3"
size="medium"
border
>
<el-descriptions class="margin-top" direction="vertical" :column="3" border>
<el-descriptions-item>
<template #label>
<i class="el-icon-user"></i>
<el-icon>
<IconifyIconOffline icon="user" />
</el-icon>
用户名
</template>
xiaoxian
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<i class="el-icon-mobile-phone"></i>
<el-icon>
<IconifyIconOffline icon="iphone" />
</el-icon>
手机号
</template>
123456789
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<i class="el-icon-location-outline"></i>
<el-icon>
<IconifyIconOffline icon="location" />
</el-icon>
居住地
</template>
上海
</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
direction="vertical"
:column="2"
size="medium"
border
>
<el-descriptions class="margin-top" direction="vertical" :column="2" border>
<el-descriptions-item>
<template #label>
<i class="el-icon-tickets"></i>
<el-icon>
<IconifyIconOffline icon="tickets" />
</el-icon>
标签
</template>
<el-tag
v-for="item in lists"
:key="item.label"
:type="item.type"
size="mini"
size="small"
effect="dark"
>
{{ item.label }}
@@ -63,22 +59,20 @@ const lists = ref([
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<i class="el-icon-office-building"></i>
<el-icon>
<IconifyIconOffline icon="office-building" />
</el-icon>
联系地址
</template>
上海市徐汇区
</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
direction="vertical"
:column="1"
size="medium"
border
>
<el-descriptions class="margin-top" direction="vertical" :column="1" border>
<el-descriptions-item>
<template #label>
<i class="el-icon-notebook-1"></i>
<el-icon>
<IconifyIconOffline icon="notebook" />
</el-icon>
留言
</template>
好好学习天天向上
@@ -87,7 +81,7 @@ const lists = ref([
</template>
<style scoped>
.el-tag--mini {
.el-tag {
margin-right: 10px !important;
}
</style>

View File

@@ -12,8 +12,15 @@ import { useEventListener, tryOnUnmounted, useTimeoutFn } from "@vueuse/core";
let echartInstance: ECharts;
const props = defineProps({
index: {
type: Number,
default: 0
}
});
function initechartInstance() {
const echartDom = document.querySelector(".line");
const echartDom = document.querySelector(".line" + props.index);
if (!echartDom) return;
// @ts-ignore
echartInstance = echarts.init(echartDom);
@@ -73,12 +80,5 @@ tryOnUnmounted(() => {
</script>
<template>
<div class="line"></div>
<div :class="'line' + props.index" style="width: 100%; height: 35vh"></div>
</template>
<style scoped>
.line {
width: 100%;
height: 35vh;
}
</style>

View File

@@ -12,8 +12,15 @@ import { useEventListener, tryOnUnmounted, useTimeoutFn } from "@vueuse/core";
let echartInstance: ECharts;
const props = defineProps({
index: {
type: Number,
default: 0
}
});
function initechartInstance() {
const echartDom = document.querySelector(".pie");
const echartDom = document.querySelector(".pie" + props.index);
if (!echartDom) return;
// @ts-ignore
echartInstance = echarts.init(echartDom);
@@ -76,12 +83,5 @@ tryOnUnmounted(() => {
</script>
<template>
<div class="pie"></div>
<div :class="'pie' + props.index" style="width: 100%; height: 35vh"></div>
</template>
<style scoped>
.pie {
width: 100%;
height: 35vh;
}
</style>

View File

@@ -1,5 +1,91 @@
import { App } from "vue";
import { App, defineComponent } from "vue";
import icon from "./src/Icon.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import iconifyIconOffline from "./src/iconifyIconOffline";
import iconifyIconOnline from "./src/iconifyIconOnline";
/**
* find icon component
* @param icon icon图标
* @returns component
*/
export function findIconReg(icon: string) {
// fontawesome4
const fa4Reg = /^fa-/;
// fontawesome5+
const fa5Reg = /^FA-/;
// iconfont
const iFReg = /^IF-/;
// remixicon
const riReg = /^RI-/;
// typeof icon === "function" 属于SVG
if (fa5Reg.test(icon)) {
const text = icon.split(fa5Reg)[1];
return findIcon(
text.slice(0, text.indexOf(" ") == -1 ? text.length : text.indexOf(" ")),
"FA",
text.slice(text.indexOf(" ") + 1, text.length)
);
} else if (fa4Reg.test(icon)) {
return findIcon(icon.split(fa4Reg)[1], "fa");
} else if (iFReg.test(icon)) {
return findIcon(icon.split(iFReg)[1], "IF");
} else if (typeof icon === "function") {
return findIcon(icon, "SVG");
} else if (riReg.test(icon)) {
return findIcon(icon.split(riReg)[1], "RI");
} else {
return findIcon(icon, "EL");
}
}
// 支持fontawesome、iconfont、remixicon、element-plus/icons、自定义svg
export function findIcon(icon: String, type = "EL", property?: string) {
if (type === "FA") {
return defineComponent({
name: "FaIcon",
setup() {
return { icon, property };
},
components: { FontAwesomeIcon },
template: `<font-awesome-icon :icon="icon" v-bind:[property]="true" />`
});
} else if (type === "fa") {
return defineComponent({
name: "faIcon",
data() {
return { icon: `fa ${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "IF") {
return defineComponent({
name: "IfIcon",
data() {
return { icon: `iconfont ${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "RI") {
return defineComponent({
name: "RiIcon",
data() {
return { icon: `ri-${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "EL") {
return defineComponent({
name: "ElIcon",
data() {
return { icon };
},
template: `<IconifyIconOffline :icon="icon" />`
});
} else if (type === "SVG") {
return icon;
}
}
export const Icon = Object.assign(icon, {
install(app: App) {
@@ -7,6 +93,11 @@ export const Icon = Object.assign(icon, {
}
});
export const IconifyIconOffline = iconifyIconOffline;
export const IconifyIconOnline = iconifyIconOnline;
export default {
Icon
Icon,
IconifyIconOffline,
IconifyIconOnline
};

View File

@@ -0,0 +1,70 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
import Check from "@iconify-icons/ep/check";
import Menu from "@iconify-icons/ep/menu";
import HomeFilled from "@iconify-icons/ep/home-filled";
import SetUp from "@iconify-icons/ep/set-up";
import Edit from "@iconify-icons/ep/edit";
import Setting from "@iconify-icons/ep/setting";
import Lollipop from "@iconify-icons/ep/lollipop";
import Link from "@iconify-icons/ep/link";
import Position from "@iconify-icons/ep/position";
import Histogram from "@iconify-icons/ep/histogram";
import RefreshRight from "@iconify-icons/ep/refresh-right";
import ArrowDown from "@iconify-icons/ep/arrow-down";
import Close from "@iconify-icons/ep/close";
import CloseBold from "@iconify-icons/ep/close-bold";
import Bell from "@iconify-icons/ep/bell";
import Guide from "@iconify-icons/ep/guide";
import User from "@iconify-icons/ep/user";
import Iphone from "@iconify-icons/ep/iphone";
import Location from "@iconify-icons/ep/location";
import Tickets from "@iconify-icons/ep/tickets";
import OfficeBuilding from "@iconify-icons/ep/office-building";
import Notebook from "@iconify-icons/ep/notebook";
addIcon("check", Check);
addIcon("menu", Menu);
addIcon("home-filled", HomeFilled);
addIcon("set-up", SetUp);
addIcon("edit", Edit);
addIcon("setting", Setting);
addIcon("lollipop", Lollipop);
addIcon("link", Link);
addIcon("position", Position);
addIcon("histogram", Histogram);
addIcon("refresh-right", RefreshRight);
addIcon("arrow-down", ArrowDown);
addIcon("close", Close);
addIcon("close-bold", CloseBold);
addIcon("bell", Bell);
addIcon("guide", Guide);
addIcon("user", User);
addIcon("iphone", Iphone);
addIcon("location", Location);
addIcon("tickets", Tickets);
addIcon("office-building", OfficeBuilding);
addIcon("notebook", Notebook);
// Iconify Icon在Vue里离线使用用于内网环境
// https://docs.iconify.design/icon-components/vue/offline.html
export default defineComponent({
name: "IconifyIcon",
components: { IconifyIcon },
props: {
icon: {
type: String,
default: ""
}
},
render() {
return h(
IconifyIcon,
{
icon: `${this.icon}`
},
{
default: () => []
}
);
}
});

View File

@@ -0,0 +1,30 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon } from "@iconify/vue";
// Iconify Icon在Vue里在线使用用于外网环境
// https://docs.iconify.design/icon-components/vue/offline.html
export default defineComponent({
name: "IconifyIcon",
components: { IconifyIcon },
props: {
icon: {
type: String,
default: ""
},
type: {
type: String,
default: "ep:"
}
},
render() {
return h(
IconifyIcon,
{
icon: `${this.type}${this.icon}`
},
{
default: () => []
}
);
}
});

View File

@@ -1,6 +1,9 @@
import { App } from "vue";
import axios from "axios";
import { loadEnv } from "@build/index";
let config: object = {};
const { VITE_PUBLIC_PATH } = loadEnv();
const setConfig = (cfg?: unknown) => {
config = Object.assign(config, cfg);
@@ -30,10 +33,7 @@ export const getServerConfig = async (app: App): Promise<undefined> => {
return axios({
baseURL: "",
method: "get",
url:
process.env.NODE_ENV === "production"
? "/manages/serverConfig.json"
: "/serverConfig.json"
url: `${VITE_PUBLIC_PATH}serverConfig.json`
})
.then(({ data: config }) => {
let $config = app.config.globalProperties.$config;

View File

@@ -9,7 +9,7 @@ export const auth: Directive = {
const authRoles = value;
const hasAuth = usePermissionStoreHook().buttonAuth.includes(authRoles);
if (!hasAuth) {
el.style.display = "none";
el.parentNode.removeChild(el);
}
} else {
throw new Error("need roles! Like v-auth=\"['admin','test']\"");

View File

@@ -8,7 +8,7 @@ import {
getCurrentInstance
} from "vue";
import { RouterView } from "vue-router";
import backTop from "/@/assets/svg/back_top.svg";
import backTop from "/@/assets/svg/back_top.svg?component";
import { usePermissionStoreHook } from "/@/store/modules/permission";
const props = defineProps({
@@ -27,12 +27,23 @@ const transitions = computed(() => {
});
const hideTabs = computed(() => {
return instance?.sets.hideTabs;
return instance?.configure.hideTabs;
});
const layout = computed(() => {
return instance?.layout.layout === "vertical";
});
const getSectionStyle = computed(() => {
return [
hideTabs.value && layout ? "padding-top: 48px;" : "",
!hideTabs.value && layout ? "padding-top: 85px;" : "",
hideTabs.value && !layout.value ? "padding-top: 48px" : "",
!hideTabs.value && !layout.value ? "padding-top: 85px;" : "",
props.fixedHeader ? "" : "padding-top: 0;"
];
});
const transitionMain = defineComponent({
render() {
return h(
@@ -71,12 +82,7 @@ const transitionMain = defineComponent({
<template>
<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;' : ''
]"
:style="getSectionStyle"
>
<router-view>
<template #default="{ Component, route }">

View File

@@ -1,6 +1,10 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import Notice from "./notice/index.vue";
import avatars from "/@/assets/avatars.jpg";
import { transformI18n } from "/@/plugins/i18n";
import Hamburger from "./sidebar/hamBurger.vue";
import { useRouter, useRoute } from "vue-router";
import { storageSession } from "/@/utils/storage";
@@ -9,7 +13,8 @@ import { useAppStoreHook } from "/@/store/modules/app";
import { unref, watch, getCurrentInstance } from "vue";
import { deviceDetection } from "/@/utils/deviceDetection";
import screenfull from "../components/screenfull/index.vue";
import globalization from "/@/assets/svg/globalization.svg";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import globalization from "/@/assets/svg/globalization.svg?component";
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
@@ -17,13 +22,26 @@ const pureApp = useAppStoreHook();
const router = useRouter();
const route = useRoute();
let usename = storageSession.getItem("info")?.username;
const { locale, t } = useI18n();
const { locale } = useI18n();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? useEpThemeStoreHook().epThemeColor : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
});
watch(
() => locale.value,
() => {
//@ts-ignore
document.title = t(unref(route.meta.title)); // 动态title
document.title = transformI18n(
//@ts-ignore
unref(route.meta.title),
unref(route.meta.i18n)
); // 动态title
}
);
@@ -65,31 +83,29 @@ function translationEn() {
<Breadcrumb class="breadcrumb-container" />
<div class="vertical-header-right">
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 全屏 -->
<screenfull v-show="!deviceDetection()" />
<screenfull id="header-screenfull" v-show="!deviceDetection()" />
<!-- 国际化 -->
<el-dropdown trigger="click">
<el-dropdown id="header-translation" trigger="click">
<globalization />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="{
background: locale === 'zh' ? '#1b2a47' : '',
color: locale === 'zh' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
>简体中文</el-dropdown-item
><IconifyIconOffline
class="check-zh"
v-show="locale === 'zh'"
icon="check"
/>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="{
background: locale === 'en' ? '#1b2a47' : '',
color: locale === 'en' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
><IconifyIconOffline icon="check" /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
@@ -98,25 +114,24 @@ function translationEn() {
<!-- 退出登陆 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<img
src="https://avatars.githubusercontent.com/u/44761321?s=400&u=30907819abd29bb3779bc247910873e7c7f7c12f&v=4"
/>
<img :src="avatars" />
<p>{{ usename }}</p>
</span>
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item icon="el-icon-switch-button" @click="logout">{{
$t("message.hsLoginOut")
}}</el-dropdown-item>
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-icon
class="el-icon-setting"
:title="$t('message.hssystemSet')"
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<Setting />
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
@@ -137,10 +152,6 @@ function translationEn() {
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.vertical-header-right {
@@ -151,6 +162,12 @@ function translationEn() {
color: #000000d9;
justify-content: flex-end;
:deep(.dropdown-badge) {
&:hover {
background: #f6f6f6;
}
}
.screen-full {
cursor: pointer;
@@ -216,7 +233,7 @@ function translationEn() {
.translation {
.el-dropdown-menu__item {
padding: 0 40px !important;
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
@@ -228,19 +245,21 @@ function translationEn() {
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
}
.logout {
max-width: 120px;
.el-dropdown-menu__item {
padding: 0 18px !important;
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
.el-dropdown-menu__item:focus,

View File

@@ -0,0 +1,146 @@
export interface ListItem {
avatar: string;
title: string;
datetime: string;
type: string;
description: string;
status?: "" | "success" | "warning" | "info" | "danger";
extra?: string;
}
export interface TabItem {
key: string;
name: string;
list: ListItem[];
}
export const noticesData: TabItem[] = [
{
key: "1",
name: "通知",
list: [
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "你收到了 12 份新周报",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
title: "你推荐的 前端高手 已通过第三轮面试",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
title: "这种模板可以区分多种通知类型",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title:
"展示标题内容超过一行后的处理方式如果内容超过1行将自动截断并支持tooltip显示完整标题。",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title: "左侧图标用于区分不同的类型",
datetime: "一年前",
description: "",
type: "1"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
title: "左侧图标用于区分不同的类型",
datetime: "一年前",
description: "",
type: "1"
}
]
},
{
key: "2",
name: "消息",
list: [
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "李白 评论了你",
description: "长风破浪会有时,直挂云帆济沧海",
datetime: "一年前",
type: "2"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "李白 回复了你",
description: "行路难,行路难,多歧路,今安在。",
datetime: "一年前",
type: "2"
},
{
avatar:
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
title: "标题",
description:
"请将鼠标移动到此处以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
datetime: "一年前",
type: "2"
}
]
},
{
key: "3",
name: "代办",
list: [
{
avatar: "",
title: "任务名称",
description: "任务需要在 2021-11-16 20:00 前启动",
datetime: "",
extra: "未开始",
status: "info",
type: "3"
},
{
avatar: "",
title: "第三方紧急代码变更",
description:
"一拳提交于 2021-11-16需在 2021-11-18 前完成代码变更任务",
datetime: "",
extra: "马上到期",
status: "danger",
type: "3"
},
{
avatar: "",
title: "信息安全考试",
description: "指派小仙于 2021-12-12 前完成更新并发布",
datetime: "",
extra: "已耗时 8 天",
status: "warning",
type: "3"
},
{
avatar: "",
title: "vue-pure-admin 版本发布",
description: "vue-pure-admin 版本发布",
datetime: "",
extra: "进行中",
type: "3"
}
]
}
];

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { ref } from "vue";
import NoticeList from "./noticeList.vue";
import { noticesData } from "./data";
const activeName = ref(noticesData[0].name);
const notices = ref(noticesData);
let noticesNum = ref(0);
notices.value.forEach(notice => {
noticesNum.value += notice.list.length;
});
</script>
<template>
<el-dropdown trigger="click" placement="bottom-end">
<span class="dropdown-badge">
<el-badge :value="noticesNum" :max="99">
<el-icon class="header-notice-icon"
><IconifyIconOffline icon="bell"
/></el-icon>
</el-badge>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-tabs v-model="activeName" class="dropdown-tabs">
<template v-for="item in notices" :key="item.key">
<el-tab-pane
:label="`${item.name}(${item.list.length})`"
:name="item.name"
>
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList :list="item.list" />
</div>
</el-scrollbar>
</el-tab-pane>
</template>
</el-tabs>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<style lang="scss" scoped>
.dropdown-badge {
display: flex;
align-items: center;
justify-content: center;
height: 48px;
width: 60px;
cursor: pointer;
.header-notice-icon {
font-size: 18px;
}
}
.dropdown-tabs {
width: 336px;
background-color: #fff;
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
border-radius: 4px;
:deep(.el-tabs__header) {
margin: 0;
}
:deep(.el-tabs__nav-scroll) {
display: flex;
justify-content: center;
}
:deep(.el-tabs__nav-wrap)::after {
height: 1px;
}
:deep(.noticeList-container) {
padding: 15px 24px 0 24px;
}
}
</style>

View File

@@ -0,0 +1,168 @@
<script setup lang="ts">
import { ListItem } from "./data";
import { ref, PropType, nextTick } from "vue";
const props = defineProps({
noticeItem: {
type: Object as PropType<ListItem>,
default: () => {}
}
});
const titleRef = ref(null);
const descriptionRef = ref(null);
const titleTooltip = ref(false);
const descriptionTooltip = ref(false);
function hoverTitle() {
nextTick(() => {
titleRef.value?.scrollWidth > titleRef.value?.clientWidth
? (titleTooltip.value = true)
: (titleTooltip.value = false);
});
}
function hoverDescription(event, description) {
// currentWidth 为文本在页面中所占的宽度创建标签加入到页面获取currentWidth ,最后在移除
let tempTag = document.createElement("span");
tempTag.innerText = description;
tempTag.className = "getDescriptionWidth";
document.querySelector("body").appendChild(tempTag);
let currentWidth = (
document.querySelector(".getDescriptionWidth") as HTMLSpanElement
).offsetWidth;
document.querySelector(".getDescriptionWidth").remove();
// cellWidth为容器的宽度
const cellWidth = event.target.offsetWidth;
// 当文本宽度大于容器宽度两倍时,代表文本显示超过两行
currentWidth > 2 * cellWidth
? (descriptionTooltip.value = true)
: (descriptionTooltip.value = false);
}
</script>
<template>
<div class="notice-container">
<el-avatar
v-if="props.noticeItem.avatar"
:size="30"
:src="props.noticeItem.avatar"
class="notice-container-avatar"
></el-avatar>
<div class="notice-container-text">
<div class="notice-text-title">
<el-tooltip
popper-class="notice-title-popper"
:disabled="!titleTooltip"
:content="props.noticeItem.title"
placement="top-start"
>
<div
ref="titleRef"
class="notice-title-content"
@mouseover="hoverTitle"
>
{{ props.noticeItem.title }}
</div>
</el-tooltip>
<el-tag
v-if="props.noticeItem?.extra"
:type="props.noticeItem?.status"
size="small"
class="notice-title-extra"
>{{ props.noticeItem?.extra }}
</el-tag>
</div>
<el-tooltip
popper-class="notice-title-popper"
:disabled="!descriptionTooltip"
:content="props.noticeItem.description"
placement="top-start"
>
<div
ref="descriptionRef"
class="notice-text-description"
@mouseover="hoverDescription($event, props.noticeItem.description)"
>
{{ props.noticeItem.description }}
</div>
</el-tooltip>
<div class="notice-text-datetime">
{{ props.noticeItem.datetime }}
</div>
</div>
</div>
</template>
<style>
.notice-title-popper {
max-width: 238px;
}
</style>
<style scoped lang="scss">
.notice-container {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
.notice-container-avatar {
margin-right: 16px;
background: #fff;
}
.notice-container-text {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.notice-text-title {
display: flex;
margin-bottom: 8px;
font-weight: 400;
font-size: 14px;
line-height: 1.5715;
color: rgba(0, 0, 0, 0.85);
cursor: pointer;
.notice-title-content {
flex: 1;
width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.notice-title-extra {
float: right;
margin-top: -1.5px;
font-weight: 400;
}
}
.notice-text-description,
.notice-text-datetime {
font-size: 12px;
line-height: 1.5715;
color: rgba(0, 0, 0, 0.45);
}
.notice-text-description {
display: -webkit-box;
text-overflow: ellipsis;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.notice-text-datetime {
margin-top: 4px;
}
}
}
</style>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import { PropType } from "vue";
import NoticeItem from "./noticeItem.vue";
import { ListItem } from "./data";
const props = defineProps({
list: {
type: Array as PropType<Array<ListItem>>,
default: () => []
}
});
</script>
<template>
<div v-if="props.list.length">
<NoticeItem
v-for="(item, index) in props.list"
:noticeItem="item"
:key="index"
></NoticeItem>
</div>
<el-empty v-else description="暂无数据"></el-empty>
</template>

View File

@@ -23,7 +23,7 @@ emitter.on("openPanel", () => {
<div class="project-configuration">
<h3>项目配置</h3>
<el-icon title="关闭配置" class="el-icon-close" @click="show = !show">
<Close />
<IconifyIconOffline icon="close" />
</el-icon>
</div>
<div style="border-bottom: 1px solid #dcdfe6"></div>

View File

@@ -8,8 +8,8 @@ const { isFullscreen, toggle } = useFullscreen();
<i
:title="
isFullscreen
? $t('message.hsexitfullscreen')
: $t('message.hsfullscreen')
? $t('buttons.hsexitfullscreen')
: $t('buttons.hsfullscreen')
"
:class="
isFullscreen
@@ -23,7 +23,7 @@ const { isFullscreen, toggle } = useFullscreen();
<style lang="scss" scoped>
.screen-full {
width: 36px;
height: 62px;
height: 48px;
display: flex;
align-items: center;
justify-content: space-around;

View File

@@ -9,18 +9,29 @@ import {
useCssModule,
getCurrentInstance
} from "vue";
import panel from "../panel/index.vue";
import rgbHex from "rgb-hex";
import { find } from "lodash-es";
import { getConfig } from "/@/config";
import { useRouter } from "vue-router";
import panel from "../panel/index.vue";
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 { shadeBgColor } from "../../theme/element-plus";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import { storageLocal, storageSession } from "/@/utils/storage";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { createNewStyle, writeNewStyle } from "../../theme/element-plus";
import { toggleTheme } from "@zougt/vite-plugin-theme-preprocessor/dist/browser-utils";
import dayIcon from "/@/assets/svg/day.svg?component";
import darkIcon from "/@/assets/svg/dark.svg?component";
const router = useRouter();
const { isSelect } = useCssModule();
const body = document.documentElement as HTMLElement;
const instance =
getCurrentInstance().appContext.app.config.globalProperties.$storage;
@@ -28,23 +39,23 @@ 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" }
]);
@@ -69,16 +80,33 @@ if (unref(layoutTheme)) {
}
// 默认灵动模式
const markValue = ref(storageLocal.getItem("showModel") || "smart");
const markValue = ref(instance.configure?.showModel ?? "smart");
const logoVal = ref(storageLocal.getItem("logoVal") || "1");
const logoVal = ref(instance.configure?.showLogo ?? true);
const epThemeColor = ref(useEpThemeStoreHook().getEpThemeColor);
const settings = reactive({
greyVal: instance.sets.grey,
weakVal: instance.sets.weak,
tabsVal: instance.sets.hideTabs
greyVal: instance.configure.grey,
weakVal: instance.configure.weak,
tabsVal: instance.configure.hideTabs,
showLogo: instance.configure.showLogo,
showModel: instance.configure.showModel,
multiTagsCache: instance.configure.multiTagsCache
});
const getThemeColorStyle = computed(() => {
return rgb => {
return { background: `rgb(${rgb})` };
};
});
function changeStorageConfigure(key, val) {
const storageConfigure = instance.configure;
storageConfigure[key] = val;
instance.configure = storageConfigure;
}
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
const targetEl = target || document.body;
let { className } = targetEl;
@@ -89,11 +117,7 @@ function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
// 灰色模式设置
const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
instance.sets = {
grey: value,
weak: instance.sets.weak,
hideTabs: instance.sets.hideTabs
};
changeStorageConfigure("grey", value);
};
// 色弱模式设置
@@ -103,51 +127,58 @@ const weekChange = (value): void => {
"html-weakness",
document.querySelector("html")
);
instance.sets = {
grey: instance.sets.grey,
weak: value,
hideTabs: instance.sets.hideTabs
};
changeStorageConfigure("weak", value);
};
const tagsChange = () => {
let showVal = settings.tabsVal;
instance.sets = {
grey: instance.sets.grey,
weak: instance.sets.weak,
hideTabs: showVal
};
changeStorageConfigure("hideTabs", 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();
});
const multiTagsCacheChange = () => {
let multiTagsCache = settings.multiTagsCache;
changeStorageConfigure("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
};
// 清空缓存并返回登录页
function onReset() {
toggleClass(getConfig().Grey, "html-grey", document.querySelector("html"));
toggleClass(
getConfig().Weak,
"html-weakness",
document.querySelector("html")
);
useMultiTagsStoreHook().handleTags("equal", [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "menus.hshome",
icon: "home-filled",
i18n: true,
showLink: true
}
}
]);
useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
useEpThemeStoreHook().setEpThemeColor(getConfig().EpThemeColor);
storageLocal.clear();
storageSession.clear();
toggleClass(false, "html-grey", document.querySelector("html"));
toggleClass(false, "html-weakness", document.querySelector("html"));
router.push("/login");
}
function onChange(label) {
storageLocal.setItem("showModel", label);
changeStorageConfigure("showModel", label);
emitter.emit("tagViewsShowModel", label);
}
// 侧边栏Logo
function logoChange() {
unref(logoVal) === "1"
? storageLocal.setItem("logoVal", "1")
: storageLocal.setItem("logoVal", "-1");
unref(logoVal)
? changeStorageConfigure("showLogo", true)
: changeStorageConfigure("showLogo", false);
emitter.emit("logoChange", unref(logoVal));
}
@@ -193,23 +224,97 @@ const getThemeColor = computed(() => {
function setLayoutModel(layout: string) {
layoutTheme.value.layout = layout;
window.document.body.setAttribute("layout", layout);
instance.layout = { layout, theme: layoutTheme.value.theme };
instance.layout = {
layout,
theme: layoutTheme.value.theme,
darkMode: instance.layout.darkMode,
sidebarStatus: instance.layout.sidebarStatus,
epThemeColor: instance.layout.epThemeColor
};
useAppStoreHook().setLayout(layout);
}
// 存放夜间主题切换前的导航主题色
let tempLayoutThemeColor;
// 设置导航主题色
function setLayoutThemeColor(theme: string) {
tempLayoutThemeColor = instance.layout.theme;
layoutTheme.value.theme = theme;
toggleTheme({
scopeName: `layout-theme-${theme}`
});
instance.layout = { layout: useAppStoreHook().layout, theme };
instance.layout = {
layout: useAppStoreHook().layout,
theme,
darkMode: dataTheme.value,
sidebarStatus: instance.layout.sidebarStatus,
epThemeColor: instance.layout.epThemeColor
};
if (theme === "default" || theme === "light") {
setEpThemeColor(getConfig().EpThemeColor);
} else {
const colors = find(themeColors.value, { themeColor: theme });
const color = "#" + rgbHex(colors.rgb);
setEpThemeColor(color);
}
}
// 设置ep主题色
const setEpThemeColor = (color: string) => {
writeNewStyle(createNewStyle(color));
useEpThemeStoreHook().setEpThemeColor(color);
body.style.setProperty("--el-color-primary-active", shadeBgColor(color));
};
let dataTheme = ref<boolean>(instance.layout.darkMode);
// 日间、夜间主题切换
function dataThemeChange() {
if (dataTheme.value) {
body.setAttribute("data-theme", "dark");
setLayoutThemeColor("light");
} else {
body.setAttribute("data-theme", "");
tempLayoutThemeColor && setLayoutThemeColor(tempLayoutThemeColor);
instance.layout = {
layout: useAppStoreHook().layout,
theme: instance.layout.theme,
darkMode: dataTheme.value,
sidebarStatus: instance.layout.sidebarStatus,
epThemeColor: instance.layout.epThemeColor
};
}
}
//初始化项目配置
nextTick(() => {
settings.greyVal &&
document.querySelector("html")?.setAttribute("class", "html-grey");
settings.weakVal &&
document.querySelector("html")?.setAttribute("class", "html-weakness");
settings.tabsVal && tagsChange();
writeNewStyle(createNewStyle(epThemeColor.value));
dataThemeChange();
});
</script>
<template>
<panel>
<el-divider>主题风格</el-divider>
<el-divider>主题</el-divider>
<el-switch
v-model="dataTheme"
inline-prompt
class="pure-datatheme"
:active-icon="dayIcon"
:inactive-icon="darkIcon"
@change="dataThemeChange"
>
</el-switch>
<el-divider>导航栏模式</el-divider>
<ul class="pure-theme">
<el-tooltip class="item" content="左侧菜单模式" placement="bottom">
<li
@@ -234,12 +339,12 @@ function setLayoutThemeColor(theme: string) {
</el-tooltip>
</ul>
<el-divider>主题色</el-divider>
<ul class="theme-color">
<el-divider v-show="!dataTheme">主题色</el-divider>
<ul class="theme-color" v-show="!dataTheme">
<li
v-for="(item, index) in themeColors"
:key="index"
:style="{ background: `rgb(${item.rgb})` }"
:style="getThemeColorStyle(item.rgb)"
@click="setLayoutThemeColor(item.themeColor)"
>
<el-icon
@@ -247,14 +352,14 @@ function setLayoutThemeColor(theme: string) {
:size="17"
:color="getThemeColor(item.themeColor)"
>
<Check />
<IconifyIconOffline icon="check" />
</el-icon>
</li>
</ul>
<el-divider>界面显示</el-divider>
<ul class="setting">
<li>
<li v-show="!dataTheme">
<span>灰色模式</span>
<el-switch
v-model="settings.greyVal"
@@ -266,7 +371,7 @@ function setLayoutThemeColor(theme: string) {
>
</el-switch>
</li>
<li>
<li v-show="!dataTheme">
<span>色弱模式</span>
<el-switch
v-model="settings.weakVal"
@@ -295,8 +400,8 @@ function setLayoutThemeColor(theme: string) {
<el-switch
v-model="logoVal"
inline-prompt
active-value="1"
inactive-value="-1"
:active-value="true"
:inactive-value="false"
inactive-color="#a6a6a6"
active-text=""
inactive-text=""
@@ -304,6 +409,18 @@ function setLayoutThemeColor(theme: string) {
>
</el-switch>
</li>
<li>
<span>标签页持久化</span>
<el-switch
v-model="settings.multiTagsCache"
inline-prompt
inactive-color="#a6a6a6"
active-text=""
inactive-text=""
@change="multiTagsCacheChange"
>
</el-switch>
</li>
<li>
<span>标签风格</span>
@@ -328,7 +445,7 @@ function setLayoutThemeColor(theme: string) {
<style scoped module>
.isSelect {
border: 2px solid #0960bd;
border: 2px solid var(--el-color-primary);
}
</style>
@@ -349,6 +466,14 @@ function setLayoutThemeColor(theme: string) {
font-weight: 700;
}
.pure-datatheme {
width: 100%;
height: 50px;
text-align: center;
display: block;
padding-top: 25px;
}
.pure-theme {
margin-top: 25px;
width: 100%;

View File

@@ -1,10 +1,16 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { isEqual } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import { getParentPaths, findRouteByPath } from "/@/router/utils";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { useRoute, useRouter, RouteLocationMatched } from "vue-router";
const levelList = ref([]);
const route = useRoute();
const levelList = ref([]);
const router = useRouter();
const routes = router.options.routes;
const multiTags = useMultiTagsStoreHook().multiTags;
const isDashboard = (route: RouteLocationMatched): boolean | string => {
const name = route && (route.name as string);
@@ -15,19 +21,57 @@ const isDashboard = (route: RouteLocationMatched): boolean | string => {
};
const getBreadcrumb = (): void => {
let matched = route.matched.filter(item => item.meta && item.meta.title);
// 当前路由信息
let currentRoute;
if (Object.keys(route.query).length > 0) {
multiTags.forEach(item => {
if (isEqual(route.query, item?.query)) {
currentRoute = item;
}
});
} else {
currentRoute = findRouteByPath(router.currentRoute.value.path, multiTags);
}
// 当前路由的父级路径组成的数组
const parentRoutes = getParentPaths(router.currentRoute.value.path, routes);
// 存放组成面包屑的数组
let matched = [];
// 获取每个父级路径对应的路由信息
parentRoutes.forEach(path => {
if (path !== "/") {
matched.push(findRouteByPath(path, routes));
}
});
if (router.currentRoute.value.meta?.refreshRedirect) {
matched.unshift(
findRouteByPath(
router.currentRoute.value.meta.refreshRedirect as string,
routes
)
);
} else {
// 过滤与子级相同标题的父级路由
matched = matched.filter(item => {
return !item.redirect || (item.redirect && item.children.length !== 1);
});
}
if (currentRoute?.path !== "/welcome") {
matched.push(currentRoute);
}
const first = matched[0];
if (!isDashboard(first)) {
matched = [
{
path: "/welcome",
parentPath: "/",
meta: { title: "message.hshome" }
meta: { title: "menus.hshome", i18n: true }
} as unknown as RouteLocationMatched
].concat(matched);
}
levelList.value = matched.filter(
item => item.meta && item.meta.title && item.meta.breadcrumb !== false
item => item?.meta && item?.meta.title !== false
);
};
@@ -38,6 +82,11 @@ watch(
() => getBreadcrumb()
);
watch(
() => route.query,
() => getBreadcrumb()
);
const handleLink = (item: RouteLocationMatched): any => {
const { redirect, path } = item;
if (redirect) {
@@ -55,10 +104,10 @@ const handleLink = (item: RouteLocationMatched): any => {
<span
v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
class="no-redirect"
>{{ $t(item.meta.title) }}</span
>{{ transformI18n(item.meta.title, item.meta.i18n) }}</span
>
<a v-else @click.prevent="handleLink(item)">
{{ $t(item.meta.title) }}
{{ transformI18n(item.meta.title, item.meta.i18n) }}
</a>
</el-breadcrumb-item>
</transition-group>

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
export interface Props {
isActive: boolean;
}
@@ -7,6 +9,8 @@ const props = withDefaults(defineProps<Props>(), {
isActive: false
});
const fillColor = ref<string>("");
const emit = defineEmits<{
(e: "toggleClick"): void;
}>();
@@ -21,8 +25,11 @@ const toggleClick = () => {
:class="classes.container"
:title="props.isActive ? '点击折叠' : '点击展开'"
@click="toggleClick"
@mouseenter="fillColor = useEpThemeStoreHook().epThemeColor"
@mouseleave="fillColor = ''"
>
<svg
:fill="fillColor"
:class="['hamburger', props.isActive ? 'is-active' : '']"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -9,16 +9,18 @@ import {
} from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import Notice from "../notice/index.vue";
import { templateRef } from "@vueuse/core";
import SidebarItem from "./sidebarItem.vue";
import avatars from "/@/assets/avatars.jpg";
import { algorithm } from "/@/utils/algorithm";
import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router";
import { storageSession } from "/@/utils/storage";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { deviceDetection } from "/@/utils/deviceDetection";
import globalization from "/@/assets/svg/globalization.svg";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component";
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
@@ -27,13 +29,21 @@ const title =
getCurrentInstance().appContext.config.globalProperties.$config?.Title;
const menuRef = templateRef<ElRef | null>("menu", null);
const routeStore = usePermissionStoreHook();
const route = useRoute();
const router = useRouter();
const routers = useRouter().options.routes;
let usename = storageSession.getItem("info")?.username;
const { locale, t } = useI18n();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? "#1b2a47" : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
});
watch(
() => locale.value,
() => {
@@ -131,38 +141,34 @@ onMounted(() => {
@select="menuSelect"
>
<sidebar-item
v-for="route in routeStore.wholeRoutes"
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:item="route"
:base-path="route.path"
/>
</el-menu>
<div class="horizontal-header-right">
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 全屏 -->
<screenfull v-show="!deviceDetection()" />
<screenfull id="header-screenfull" v-show="!deviceDetection()" />
<!-- 国际化 -->
<el-dropdown trigger="click">
<el-dropdown id="header-translation" trigger="click">
<globalization />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="{
background: locale === 'zh' ? '#1b2a47' : '',
color: locale === 'zh' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
><IconifyIconOffline icon="check" /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="{
background: locale === 'en' ? '#1b2a47' : '',
color: locale === 'en' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
><IconifyIconOffline icon="check" /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
@@ -171,25 +177,24 @@ onMounted(() => {
<!-- 退出登陆 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<img
src="https://avatars.githubusercontent.com/u/44761321?s=400&u=30907819abd29bb3779bc247910873e7c7f7c12f&v=4"
/>
<img :src="avatars" />
<p>{{ usename }}</p>
</span>
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item icon="el-icon-switch-button" @click="logout">{{
$t("message.hsLoginOut")
}}</el-dropdown-item>
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-icon
class="el-icon-setting"
:title="$t('message.hssystemSet')"
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<Setting />
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
@@ -198,7 +203,7 @@ onMounted(() => {
<style lang="scss" scoped>
.translation {
.el-dropdown-menu__item {
padding: 0 40px !important;
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
@@ -210,19 +215,21 @@ onMounted(() => {
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
}
.logout {
max-width: 120px;
.el-dropdown-menu__item {
padding: 0 18px !important;
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
.el-dropdown-menu__item:focus,

View File

@@ -1,9 +1,18 @@
<script setup lang="ts">
import {
ref,
PropType,
nextTick,
computed,
CSSProperties,
getCurrentInstance
} from "vue";
import path from "path";
import { PropType, ref, nextTick, getCurrentInstance } from "vue";
import { childrenType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import { transformI18n } from "/@/plugins/i18n";
import { findIconReg } from "/@/components/ReIcon";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { useAppStoreHook } from "/@/store/modules/app";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const menuMode = instance.$storage.layout?.layout === "vertical";
@@ -23,6 +32,48 @@ const props = defineProps({
}
});
const getNoDropdownStyle = computed((): CSSProperties => {
return {
display: "flex",
alignItems: "center"
};
});
const getDivStyle = computed((): CSSProperties => {
return {
width: pureApp.sidebar.opened ? "" : "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
overflow: "hidden"
};
});
const getMenuTextStyle = computed((): CSSProperties => {
return {
width: pureApp.sidebar.opened ? "125px" : "",
overflow: "hidden",
textOverflow: "ellipsis",
outline: "none"
};
});
const getSubTextStyle = computed((): CSSProperties => {
return {
width: pureApp.sidebar.opened ? "125px" : "",
display: "inline-block",
overflow: "hidden",
textOverflow: "ellipsis"
};
});
const getSpanStyle = computed((): CSSProperties => {
return {
overflow: "hidden",
textOverflow: "ellipsis"
};
});
const onlyOneChild: childrenType = ref(null);
// 存放菜单是否存在showTooltip属性标识
const hoverMenuMap = new WeakMap();
@@ -68,7 +119,12 @@ function hasOneShowingChild(
}
function resolvePath(routePath) {
return path.resolve(props.basePath, routePath);
const httpReg = /^http(s?):\/\//;
if (httpReg.test(routePath)) {
return props.basePath + "/" + routePath;
} else {
return path.resolve(props.basePath, routePath);
}
}
</script>
@@ -82,43 +138,42 @@ function resolvePath(routePath) {
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
style="display: flex; align-items: center"
:style="getNoDropdownStyle"
>
<el-icon v-show="props.item.meta.icon">
<component
:is="
onlyOneChild.meta.icon || (props.item.meta && props.item.meta.icon)
findIconReg(
onlyOneChild.meta.icon ||
(props.item.meta && props.item.meta.icon)
)
"
></component>
</el-icon>
<template #title>
<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>
<div :style="getDivStyle">
<span v-if="!menuMode">{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}</span>
<el-tooltip
v-else
placement="top"
:offset="-10"
:disabled="!onlyOneChild.showTooltip"
>
<template #content> {{ $t(onlyOneChild.meta.title) }} </template>
<template #content>
{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}
</template>
<span
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
overflow: 'hidden',
textOverflow: 'ellipsis'
}"
:style="getMenuTextStyle"
@mouseover="hoverMenu(onlyOneChild)"
>
{{ $t(onlyOneChild.meta.title) }}
{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}
</span>
</el-tooltip>
<Icon
@@ -139,28 +194,29 @@ function resolvePath(routePath) {
>
<template #title>
<el-icon v-show="props.item.meta.icon" :class="props.item.meta.icon">
<component :is="props.item.meta && props.item.meta.icon"></component>
<component
:is="findIconReg(props.item.meta && props.item.meta.icon)"
></component>
</el-icon>
<span v-if="!menuMode">{{ $t(props.item.meta.title) }}</span>
<span v-if="!menuMode">{{
transformI18n(props.item.meta.title, props.item.meta.i18n)
}}</span>
<el-tooltip
v-else
placement="top"
:offset="-10"
:disabled="!pureApp.sidebar.opened || !props.item.showTooltip"
>
<template #content> {{ $t(props.item.meta.title) }} </template>
<template #content>
{{ transformI18n(props.item.meta.title, props.item.meta.i18n) }}
</template>
<div
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis'
}"
:style="getSubTextStyle"
@mouseover="hoverMenu(props.item)"
>
<span style="overflow: hidden; text-overflow: ellipsis">
{{ $t(props.item.meta.title) }}
<span :style="getSpanStyle">
{{ transformI18n(props.item.meta.title, props.item.meta.i18n) }}
</span>
</div>
</el-tooltip>

View File

@@ -12,8 +12,9 @@ import { usePermissionStoreHook } from "/@/store/modules/permission";
const route = useRoute();
const pureApp = useAppStoreHook();
const router = useRouter().options.routes;
const routeStore = usePermissionStoreHook();
const showLogo = ref(storageLocal.getItem("logoVal") || "1");
const showLogo = ref(
storageLocal.getItem("responsive-configure")?.showLogo ?? true
);
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
@@ -59,7 +60,7 @@ onBeforeMount(() => {
<template>
<div :class="['sidebar-container', showLogo ? 'has-logo' : '']">
<Logo v-if="showLogo === '1'" :collapse="isCollapse" />
<Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
@@ -72,7 +73,7 @@ onBeforeMount(() => {
@select="menuSelect"
>
<sidebar-item
v-for="route in routeStore.wholeRoutes"
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:item="route"
class="outer-most"

View File

@@ -0,0 +1,342 @@
@keyframes scheduleInWidth {
from {
width: 0;
}
to {
width: 100%;
}
}
@keyframes scheduleOutWidth {
from {
width: 100%;
}
to {
width: 0;
}
}
@-webkit-keyframes rotate {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes rotate {
from {
-moz-transform: rotate(0deg);
}
to {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes rotate {
from {
-o-transform: rotate(0deg);
}
to {
-o-transform: rotate(360deg);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes close {
from {
transform: translate(-50%, -50%);
}
to {
transform: translate(0, -50%);
}
}
.tags-view {
width: 100%;
font-size: 14px;
display: flex;
align-items: center;
color: var(--el-text-color-regular);
background: #fff;
position: relative;
box-shadow: 0 0 1px #888;
.scroll-item {
border-radius: 3px 3px 0 0;
padding: 0 6px 0 6px;
box-shadow: 0 0 1px #888;
position: relative;
margin-right: 4px;
height: 28px;
display: inline-block;
line-height: 28px;
transition: all 0.4s;
cursor: pointer;
.el-icon-close {
font-size: 10px;
color: #1890ff;
cursor: pointer;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
transition: font-size 0.2s;
&:hover {
border-radius: 50%;
color: #fff;
background: #b4bccc;
font-size: 13px;
}
}
&.is-closable:not(:first-child) {
&:hover {
padding-right: 18px;
&:not(.is-active) {
.el-icon-close {
animation: close 200ms ease-in forwards;
}
}
}
}
}
a {
text-decoration: none;
color: #666;
padding: 0 4px 0 4px;
}
.scroll-container {
flex: 1;
overflow: hidden;
padding: 5px 0;
white-space: nowrap;
position: relative;
.tab {
position: relative;
float: left;
list-style: none;
overflow: visible;
white-space: nowrap;
transition: transform 0.5s ease-in-out;
.scroll-item {
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
&:nth-child(1) {
margin-left: 5px;
}
}
}
}
.right-button {
display: flex;
font-size: 16px;
li {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 38px;
border-right: 1px solid #ccc;
cursor: pointer;
}
}
/* 右键菜单 */
.contextmenu {
margin: 0;
background: #fff;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
color: #000000d9;
font-weight: normal;
font-size: 13px;
white-space: nowrap;
outline: 0;
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
li {
width: 100%;
margin: 0;
padding: 7px 12px;
cursor: pointer;
display: flex;
align-items: center;
&:hover {
background: #eee;
}
svg {
display: block;
margin-right: 0.5em;
}
}
}
}
.el-dropdown-menu {
padding: 0;
li {
width: 100%;
margin: 0;
cursor: pointer;
display: flex;
align-items: center;
svg {
display: block;
margin-right: 0.5em;
}
}
}
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
:deep(.el-dropdown-menu__item) i {
margin-right: 10px;
}
.el-dropdown-menu__item--divided::before {
margin: 0;
}
.el-dropdown-menu__item.is-disabled {
cursor: not-allowed;
}
.scroll-item.is-active {
background-color: #eaf4fe;
position: relative;
color: #fff;
&:not(:first-child) {
padding-right: 18px;
}
.el-icon-close {
transform: translate(0, -50%);
}
a {
color: #1890ff;
}
}
.ri-arrow-left-s-line {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
font-size: 20px;
color: #00000073;
box-shadow: 5px 0 5px -6px #ccc;
&:hover {
cursor: w-resize;
}
}
.ri-arrow-right-s-line {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
font-size: 20px;
border-right: 1px solid #ccc;
color: #00000073;
box-shadow: -5px 0 5px -6px #ccc;
&:hover {
cursor: e-resize;
}
}
/* 卡片模式下鼠标移入显示蓝色边框 */
.card-in {
color: #1890ff;
a {
color: #1890ff;
}
}
/* 卡片模式下鼠标移出隐藏蓝色边框 */
.card-out {
border: none;
color: #666;
a {
color: #666;
}
}
/* 灵动模式 */
.schedule-active {
width: 100%;
height: 2px;
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
}
/* 灵动模式下鼠标移入显示蓝色进度条 */
.schedule-in {
width: 100%;
height: 2px;
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
animation: scheduleInWidth 400ms ease-in;
}
/* 灵动模式下鼠标移出隐藏蓝色进度条 */
.schedule-out {
width: 0;
height: 2px;
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
animation: scheduleOutWidth 400ms ease-in;
}
/* 刷新按钮动画效果 */
.refresh-button {
-webkit-animation: rotate 600ms linear infinite;
-moz-animation: rotate 600ms linear infinite;
-o-animation: rotate 600ms linear infinite;
animation: rotate 600ms linear infinite;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,9 @@
<script setup lang="ts">
import {
h,
ref,
unref,
reactive,
computed,
onMounted,
watchEffect,
onBeforeMount,
defineComponent,
getCurrentInstance
} from "vue";
@@ -15,13 +11,15 @@ import { setType } from "./types";
import { useI18n } from "vue-i18n";
import { routerArrays } from "./types";
import { emitter } from "/@/utils/mitt";
import { useEventListener } from "@vueuse/core";
import backTop from "/@/assets/svg/back_top.svg";
import { useAppStoreHook } from "/@/store/modules/app";
import fullScreen from "/@/assets/svg/full_screen.svg";
import exitScreen from "/@/assets/svg/exit_screen.svg";
import { deviceDetection } from "/@/utils/deviceDetection";
import { useMultiTagsStore } from "/@/store/modules/multiTags";
import { useSettingStoreHook } from "/@/store/modules/settings";
import backTop from "/@/assets/svg/back_top.svg?component";
import fullScreen from "/@/assets/svg/full_screen.svg?component";
import exitScreen from "/@/assets/svg/exit_screen.svg?component";
import navbar from "./components/navbar.vue";
import tag from "./components/tag/index.vue";
import appMain from "./components/appMain.vue";
@@ -29,19 +27,19 @@ import setting from "./components/setting/index.vue";
import Vertical from "./components/sidebar/vertical.vue";
import Horizontal from "./components/sidebar/horizontal.vue";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const hiddenSideBar = ref(instance.$config?.HiddenSideBar);
const isMobile = deviceDetection();
const pureSetting = useSettingStoreHook();
const instance = getCurrentInstance().appContext.app.config.globalProperties;
// 清空缓存后从serverConfig.json读取默认配置并赋值到storage中
const layout = computed(() => {
// 路由
if (
!instance.$storage.routesInStorage ||
instance.$storage.routesInStorage.length === 0
useMultiTagsStore().multiTagsCache &&
(!instance.$storage.tags || instance.$storage.tags.length === 0)
) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.routesInStorage = routerArrays;
instance.$storage.tags = routerArrays;
}
// 国际化
if (!instance.$storage.locale) {
@@ -54,16 +52,22 @@ const layout = computed(() => {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.layout = {
layout: instance.$config?.Layout ?? "vertical",
theme: instance.$config?.Theme ?? "default"
theme: instance.$config?.Theme ?? "default",
darkMode: instance.$config?.DarkMode ?? false,
sidebarStatus: instance.$config?.SidebarStatus ?? true,
epThemeColor: instance.$config?.EpThemeColor ?? "#409EFF"
};
}
// 灰色模式、色弱模式、隐藏标签页
if (!instance.$storage.sets) {
if (!instance.$storage.configure) {
// eslint-disable-next-line
instance.$storage.sets = {
instance.$storage.configure = {
grey: instance.$config?.Grey ?? false,
weak: instance.$config?.Weak ?? false,
hideTabs: instance.$config?.HideTabs ?? false
hideTabs: instance.$config?.HideTabs ?? false,
showLogo: instance.$config?.ShowLogo ?? true,
showModel: instance.$config?.ShowModel ?? "smart",
multiTagsCache: instance.$config?.MultiTagsCache ?? false
};
}
return instance.$storage?.layout.layout;
@@ -92,67 +96,67 @@ const set: setType = reactive({
}),
hideTabs: computed(() => {
return instance.$storage?.sets.hideTabs;
return instance.$storage?.configure.hideTabs;
})
});
const handleClickOutside = (params: boolean) => {
useAppStoreHook().closeSideBar({ withoutAnimation: params });
};
function setTheme(layoutModel: string) {
window.document.body.setAttribute("layout", layoutModel);
instance.$storage.layout = {
layout: `${layoutModel}`,
theme: instance.$storage.layout?.theme
theme: instance.$storage.layout?.theme,
darkMode: instance.$storage.layout?.darkMode,
sidebarStatus: instance.$storage.layout?.sidebarStatus,
epThemeColor: instance.$storage.layout?.epThemeColor
};
}
// 监听容器
emitter.on("resize", ({ detail }) => {
let { width } = detail;
width <= 670 ? setTheme("vertical") : setTheme(useAppStoreHook().layout);
});
watchEffect(() => {
if (set.device === "mobile" && !set.sidebar.opened) {
handleClickOutside(false);
}
});
const $_isMobile = () => {
const rect = document.body.getBoundingClientRect();
return rect.width - 1 < 992;
};
const $_resizeHandler = () => {
if (!document.hidden) {
const isMobile = $_isMobile();
useAppStoreHook().toggleDevice(isMobile ? "mobile" : "desktop");
if (isMobile) {
handleClickOutside(true);
}
}
};
function onFullScreen() {
unref(hiddenSideBar)
? (hiddenSideBar.value = false)
: (hiddenSideBar.value = true);
function toggle(device: string, bool: boolean) {
useAppStoreHook().toggleDevice(device);
useAppStoreHook().toggleSideBar(bool, "resize");
}
onMounted(() => {
const isMobile = $_isMobile();
if (isMobile) {
useAppStoreHook().toggleDevice("mobile");
handleClickOutside(true);
// 判断是否可自动关闭菜单栏
let isAutoCloseSidebar = true;
// 监听容器
emitter.on("resize", ({ detail }) => {
if (isMobile) return;
let { width } = detail;
width <= 670 ? setTheme("vertical") : setTheme(useAppStoreHook().layout);
/** width app-wrapper类容器宽度
* 0 < width <= 760 隐藏侧边栏
* 760 < width <= 990 折叠侧边栏
* width > 990 展开侧边栏
*/
if (width > 0 && width <= 760) {
toggle("mobile", false);
isAutoCloseSidebar = true;
} else if (width > 760 && width <= 990) {
if (isAutoCloseSidebar) {
toggle("desktop", false);
isAutoCloseSidebar = false;
}
} else if (width > 990) {
if (!set.sidebar.isClickHamburger) {
toggle("desktop", true);
isAutoCloseSidebar = true;
}
}
});
onBeforeMount(() => {
useEventListener("resize", $_resizeHandler);
onMounted(() => {
if (isMobile) {
toggle("mobile", false);
}
});
function onFullScreen() {
pureSetting.hiddenSideBar
? pureSetting.changeSetting({ key: "hiddenSideBar", value: false })
: pureSetting.changeSetting({ key: "hiddenSideBar", value: true });
}
const layoutHeader = defineComponent({
render() {
return h(
@@ -167,10 +171,10 @@ const layoutHeader = defineComponent({
},
{
default: () => [
!hiddenSideBar.value && layout.value.includes("vertical")
!pureSetting.hiddenSideBar && layout.value.includes("vertical")
? h(navbar)
: h("div"),
!hiddenSideBar.value && layout.value.includes("horizontal")
!pureSetting.hiddenSideBar && layout.value.includes("horizontal")
? h(Horizontal)
: h("div"),
h(
@@ -183,7 +187,7 @@ const layoutHeader = defineComponent({
{ onClick: onFullScreen },
{
default: () => [
!hiddenSideBar.value ? h(fullScreen) : h(exitScreen)
!pureSetting.hiddenSideBar ? h(fullScreen) : h(exitScreen)
]
}
)
@@ -205,11 +209,18 @@ const layoutHeader = defineComponent({
set.sidebar.opened &&
layout.includes('vertical')
"
class="drawer-bg"
@click="handleClickOutside(false)"
class="app-mask"
@click="useAppStoreHook().toggleSideBar()"
/>
<Vertical v-show="!hiddenSideBar && layout.includes('vertical')" />
<div :class="['main-container', hiddenSideBar ? 'main-hidden' : '']">
<Vertical
v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')"
/>
<div
:class="[
'main-container',
pureSetting.hiddenSideBar ? 'main-hidden' : ''
]"
>
<div v-if="set.fixedHeader">
<layout-header />
<!-- 主体内容 -->
@@ -257,7 +268,7 @@ const layoutHeader = defineComponent({
margin-left: 0 !important;
}
.drawer-bg {
.app-mask {
background: #000;
opacity: 0.3;
width: 100%;

View File

@@ -0,0 +1,15 @@
<template>
<router-view>
<template #default="{ Component, route }">
<transition appear name="fade-transform" mode="out-in">
<component :is="Component" :key="route.fullPath" />
</transition>
</template>
</router-view>
</template>
<script lang="ts">
export default {
name: "layoutParentView"
};
</script>

View File

@@ -1,5 +1,4 @@
// 极光绿
/* 酸橙绿 */
$subMenuActiveText: #fff;
$menuBg: #0b1e15;
$menuHover: #60ac80;

View File

@@ -1,24 +1,29 @@
/**
*此scss变量文件作为multipleScopeVars去编译时会自动移除!default以达到变量提升
*同时此scss变量文件作为默认主题变量文件,被其他.scss通过 @import 时,必需 !default
* 道奇蓝(默认)
* 此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背景颜色
/* logo背景颜色 */
$sidebarLogo: #002140 !default;
// 鼠标覆盖到菜单时的字体颜色
/* 鼠标覆盖到菜单时的字体颜色 */
$menuTitleHover: #fff !default;
$menuActiveBefore: #4091f7 !default;

View File

@@ -1,5 +1,4 @@
// 薄暮
/* 猩红色 */
$subMenuActiveText: #fff;
$menuBg: #2a0608;
$menuHover: #e13c39;

View File

@@ -0,0 +1,80 @@
/* 动态改变element-plus主题色 */
import rgbHex from "rgb-hex";
import color from "css-color-function";
import { TinyColor } from "@ctrl/tinycolor";
import epCss from "element-plus/dist/index.css";
// 色值表
const formula = {
"shade-1": "color(primary shade(10%))",
"light-1": "color(primary tint(10%))",
"light-2": "color(primary tint(20%))",
"light-3": "color(primary tint(30%))",
"light-4": "color(primary tint(40%))",
"light-5": "color(primary tint(50%))",
"light-6": "color(primary tint(60%))",
"light-7": "color(primary tint(70%))",
"light-8": "color(primary tint(80%))",
"light-9": "color(primary tint(90%))"
};
// 把生成的样式表写入到style中
export const writeNewStyle = (newStyle: string): void => {
const style = window.document.createElement("style");
style.innerText = newStyle;
window.document.head.appendChild(style);
};
// 根据主题色,生成最新的样式表
export const createNewStyle = (primaryStyle: string): string => {
// 根据主色生成色值表
const colors = createColors(primaryStyle);
// 在当前ep的默认样式表中标记需要替换的色值
let cssText = getStyleTemplate(epCss);
// 遍历生成的色值表,在 默认样式表 进行全局替换
Object.keys(colors).forEach(key => {
cssText = cssText.replace(
new RegExp("(:|\\s+)" + key, "g"),
"$1" + colors[key]
);
});
return cssText;
};
export const createColors = (primary: string) => {
if (!primary) return;
const colors = {
primary
};
Object.keys(formula).forEach(key => {
const value = formula[key].replace(/primary/, primary);
colors[key] = "#" + rgbHex(color.convert(value));
});
return colors;
};
const getStyleTemplate = (data: string): string => {
const colorMap = {
"#3a8ee6": "shade-1",
"#409eff": "primary",
"#53a8ff": "light-1",
"#66b1ff": "light-2",
"#79bbff": "light-3",
"#8cc5ff": "light-4",
"#a0cfff": "light-5",
"#b3d8ff": "light-6",
"#c6e2ff": "light-7",
"#d9ecff": "light-8",
"#ecf5ff": "light-9"
};
Object.keys(colorMap).forEach(key => {
const value = colorMap[key];
data = data.replace(new RegExp(key, "ig"), value);
});
return data;
};
// 自动计算hover和active颜色 https://element-plus.gitee.io/zh-CN/component/button.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A2%9C%E8%89%B2-%E6%B5%8B%E8%AF%95%E7%89%88
export const shadeBgColor = (color: string): string => {
return new TinyColor(color).shade(10).toString();
};

View File

@@ -1,4 +1,4 @@
// 明亮
/* 亮白色 */
$subMenuActiveText: #409eff;
$menuBg: #fff;
$menuHover: #e0ebf6;

View File

@@ -1,5 +1,4 @@
// 明青
/* 绿宝石 */
$subMenuActiveText: #fff;
$menuBg: #032121;
$menuHover: #59bfc1;

View File

@@ -1,5 +1,4 @@
// 粉红
/* 深粉色 */
$subMenuActiveText: #fff;
$menuBg: #28081a;
$menuHover: #d84493;

View File

@@ -1,5 +1,4 @@
// 酱紫
/* 深紫罗兰色 */
$subMenuActiveText: #fff;
$menuBg: #130824;
$menuHover: #693ac9;

View File

@@ -1,5 +1,4 @@
// 火山
/* 橙红色 */
$subMenuActiveText: #fff;
$menuBg: #2b0e05;
$menuHover: #e85f33;

View File

@@ -1,5 +1,4 @@
// 黄色
/* 金色 */
$subMenuActiveText: #d25f00;
$menuBg: #2b2503;
$menuHover: #f6da4d;

View File

@@ -1,10 +1,12 @@
import { Component } from "vue";
export const routerArrays: Array<RouteConfigs> = [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
icon: "el-icon-s-home",
title: "menus.hshome",
i18n: true,
icon: "home-filled",
showLink: true
}
}
@@ -13,21 +15,25 @@ export const routerArrays: Array<RouteConfigs> = [
export type RouteConfigs = {
path?: string;
parentPath?: string;
query?: object;
meta?: {
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
children?: RouteConfigs[];
name?: string;
};
export type relativeStorageType = {
routesInStorage: Array<RouteConfigs>;
export type multiTagsType = {
tags: Array<RouteConfigs>;
};
export type tagsViewsType = {
icon: string;
icon: Component;
text: string;
divided: boolean;
disabled: boolean;
@@ -38,6 +44,7 @@ export interface setType {
sidebar: {
opened: boolean;
withoutAnimation: boolean;
isClickHamburger: boolean;
};
device: string;
fixedHeader: boolean;
@@ -58,6 +65,7 @@ export type childrenType = {
meta?: {
icon?: string;
title?: string;
i18n?: boolean;
extraIcon?: {
svg?: boolean;
name?: string;
@@ -70,3 +78,9 @@ export type themeColorsType = {
rgb: string;
themeColor: string;
};
export interface scrollbarDomType extends HTMLElement {
wrap?: {
offsetWidth: number;
};
}

View File

@@ -6,10 +6,12 @@ import { createApp, Directive } from "vue";
import { usI18n } from "../src/plugins/i18n";
import { MotionPlugin } from "@vueuse/motion";
import { useTable } from "../src/plugins/vxe-table";
import { useFontawesome } from "../src/plugins/fontawesome";
import { useElementPlus } from "../src/plugins/element-plus";
import { injectResponsiveStorage } from "/@/utils/storage/responsive";
import "animate.css";
import "virtual:windi.css";
// 导入公共样式
import "./style/index.scss";
// 导入字体图标
@@ -25,6 +27,11 @@ Object.keys(directives).forEach(key => {
app.directive(key, (directives as { [key: string]: Directive })[key]);
});
// 全局注册`@iconify/vue`图标库
import { IconifyIconOffline, IconifyIconOnline } from "./components/ReIcon";
app.component("IconifyIconOffline", IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline);
getServerConfig(app).then(async config => {
injectResponsiveStorage(app, config);
setupStore(app);
@@ -33,7 +40,8 @@ getServerConfig(app).then(async config => {
.use(MotionPlugin)
.use(useElementPlus)
.use(useTable)
.use(usI18n);
.use(usI18n)
.use(useFontawesome);
await router.isReady();
app.mount("#app");
});

View File

@@ -23,7 +23,6 @@ import {
ElInput,
ElForm,
ElFormItem,
ElLoading,
ElPopover,
ElPopper,
ElTooltip,
@@ -36,26 +35,22 @@ import {
ElDescriptions,
ElDescriptionsItem,
ElBacktop,
ElSwitch
ElSwitch,
ElBadge,
ElTabs,
ElTabPane,
ElAvatar,
ElEmpty,
ElCollapse,
ElCollapseItem,
ElTreeV2,
// 指令
ElLoading,
ElInfiniteScroll
} 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";
// Directives
const plugins = [ElLoading, ElInfiniteScroll];
const components = [
ElTag,
@@ -94,31 +89,24 @@ const components = [
ElDescriptionsItem,
ElBacktop,
ElSwitch,
// icon
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold
ElBadge,
ElTabs,
ElTabPane,
ElAvatar,
ElEmpty,
ElCollapse,
ElCollapseItem,
ElTreeV2
];
const plugins = [ElLoading];
export function useElementPlus(app: App) {
// 注册组件
components.forEach((component: Component) => {
app.component(component.name, component);
});
// 注册指令
plugins.forEach(plugin => {
// @ts-ignore
app.use(plugin);
});
}

View File

@@ -0,0 +1,21 @@
/** 兼容fontawesome4和5版本
* 4版本: www.fontawesome.com.cn/faicons/
* 5版本https://fontawesome.com/v5.15/icons?d=gallery&p=2&m=free
* https://github.com/FortAwesome/vue-fontawesome
*/
import { App } from "vue";
import "font-awesome/css/font-awesome.css";
import { library } from "@fortawesome/fontawesome-svg-core";
import {
faUserSecret,
faCoffee,
faSpinner
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
// github.com/Remix-Design/RemixIcon/blob/master/README_CN.md#%E5%AE%89%E8%A3%85%E5%BC%95%E5%85%A5
import "remixicon/fonts/remixicon.css";
export function useFontawesome(app: App) {
library.add(faUserSecret, faCoffee, faSpinner);
app.component("font-awesome-icon", FontAwesomeIcon);
}

View File

@@ -1,4 +1,4 @@
// 菜单国际化配置
import { siphonI18n } from "./index";
// vxe-table组件国际化
import zhVxeTable from "vxe-table/lib/locale/lang/zh-CN";
import enVxeTable from "vxe-table/lib/locale/lang/en-US";
@@ -7,152 +7,18 @@ import enVxeTable from "vxe-table/lib/locale/lang/en-US";
import enLocale from "element-plus/lib/locale/lang/en";
import zhLocale from "element-plus/lib/locale/lang/zh-cn";
// 导航菜单配置
export const menusConfig = {
zh: {
message: {
hshome: "首页",
hssysManagement: "系统管理",
hsBaseinfo: "基础信息",
hsDict: "字典管理",
hseditor: "编辑器",
hserror: "错误页面",
hsfourZeroFour: "404",
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: "权限管理",
permissionPage: "页面权限",
permissionButton: "按钮权限",
externalLink: "外链"
}
},
en: {
message: {
hshome: "Home",
hssysManagement: "System Manage",
hsBaseinfo: "Base Info",
hsDict: "Dict Manage",
hseditor: "Editor",
hserror: "Error Page",
hsfourZeroFour: "404",
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",
permissionPage: "Page Permission",
permissionButton: "Button Permission",
externalLink: "External Link"
}
}
};
// 按钮配置
export const buttonConfig = {
zh: {
message: {
hsLoginOut: "退出系统",
hsfullscreen: "全屏",
hsexitfullscreen: "退出全屏",
hsrefreshRoute: "刷新路由",
hslogin: "登陆",
hsadd: "新增",
hsmark: "标记/取消",
hssave: "保存",
hssearch: "搜索",
hsexpendAll: "全部展开",
hscollapseAll: "全部折叠",
hssystemSet: "打开项目配置",
hsdelete: "删除",
hsreload: "重新加载",
hscloseCurrentTab: "关闭当前标签页",
hscloseLeftTabs: "关闭左侧标签页",
hscloseRightTabs: "关闭右侧标签页",
hscloseOtherTabs: "关闭其他标签页",
hscloseAllTabs: "关闭全部标签页"
}
},
en: {
message: {
hsLoginOut: "loginOut",
hsfullscreen: "fullScreen",
hsexitfullscreen: "exitFullscreen",
hsrefreshRoute: "refreshRoute",
hslogin: "login",
hsadd: "Add",
hsmark: "Mark/Cancel",
hssave: "Save",
hssearch: "Search",
hsexpendAll: "Expand All",
hscollapseAll: "Collapse All",
hssystemSet: "Open ProjectConfig",
hsdelete: "Delete",
hsreload: "Reload",
hscloseCurrentTab: "Close CurrentTab",
hscloseLeftTabs: "Close LeftTabs",
hscloseRightTabs: "Close RightTabs",
hscloseOtherTabs: "Close OtherTabs",
hscloseAllTabs: "Close AllTabs"
}
}
};
// 配置
// export const xxxx = {
// zh: {
// message: {},
// },
// en: {
// message: {},
// },
// };
const localesList = [menusConfig, buttonConfig];
// 项目内自定义国际化
const zhModules = import.meta.globEager("./zh-CN/**/*.ts");
const enModules = import.meta.globEager("./en/**/*.ts");
export const localesConfigs = {
zh: {
message: Object.assign({}, ...localesList.map(v => v.zh.message)),
...siphonI18n(zhModules, "zh-CN"),
...zhVxeTable,
...zhLocale
},
en: {
message: Object.assign({}, ...localesList.map(v => v.en.message)),
...siphonI18n(enModules, "en"),
...enVxeTable,
...enLocale
}

View File

@@ -0,0 +1,21 @@
export default {
hsLoginOut: "LoginOut",
hsfullscreen: "FullScreen",
hsexitfullscreen: "ExitFullscreen",
hsrefreshRoute: "RefreshRoute",
hslogin: "Login",
hsadd: "Add",
hsmark: "Mark/Cancel",
hssave: "Save",
hssearch: "Search",
hsexpendAll: "Expand All",
hscollapseAll: "Collapse All",
hssystemSet: "Open ProjectConfig",
hsdelete: "Delete",
hsreload: "Reload",
hscloseCurrentTab: "Close CurrentTab",
hscloseLeftTabs: "Close LeftTabs",
hscloseRightTabs: "Close RightTabs",
hscloseOtherTabs: "Close OtherTabs",
hscloseAllTabs: "Close AllTabs"
};

View File

@@ -0,0 +1,38 @@
export default {
hshome: "Home",
hslogin: "Login",
hssysManagement: "System Manage",
hsBaseinfo: "Base Info",
hsDict: "Dict Manage",
hseditor: "Editor",
hserror: "Error Page",
hsfourZeroFour: "404",
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",
permissionPage: "Page Permission",
permissionButton: "Button Permission",
hstabs: "Tabs Operate",
hsMenuTree: "Menu Tree",
hsguide: "Guide",
externalLink: "External Link"
};

View File

@@ -1,9 +1,68 @@
// 多组件库的国际化和本地项目国际化兼容
import { App } from "vue";
import { set } from "lodash-es";
import { createI18n } from "vue-i18n";
import { localesConfigs } from "./config";
import { storageLocal } from "/@/utils/storage";
/**
* 国际化转换工具函数
* @param message message
* @param isI18n 如果true,获取对应的消息,否则返回本身
* @returns message
*/
export function transformI18n(message: string | object = "", isI18n = false) {
if (!message) {
return "";
}
// 处理存储动态路由的title,格式 {zh:"",en:""}
if (typeof message === "object") {
return message[i18n.global?.locale];
}
if (isI18n) {
//@ts-ignore
return i18n.global.tc.call(i18n.global, message);
} else {
return message;
}
}
/**
* 从模块中抽取国际化
* @param langs 存放国际化模块
* @param prefix 语言 默认 zh-CN
* @returns obj 格式:{模块名.**}
*/
export function siphonI18n(
langs: Record<string, Record<string, any>>,
prefix = "zh-CN"
) {
const langsObj: Recordable = {};
Object.keys(langs).forEach((key: string) => {
let fileName = key.replace(`./${prefix}/`, "").replace(/^\.\//, "");
fileName = fileName.substring(0, fileName.lastIndexOf("."));
const keyList = fileName.split("/");
const moduleName = keyList.shift();
const objKey = keyList.join(".");
const langFileModule = langs[key].default;
if (moduleName) {
if (objKey) {
set(langsObj, moduleName, langsObj[moduleName] || {});
set(langsObj[moduleName], objKey, langFileModule);
} else {
set(langsObj, moduleName, langFileModule || {});
}
}
});
return langsObj;
}
// 此函数只是配合i18n Ally插件来进行国际化智能提示并无实际意义只对提示起作用如果不需要国际化可删除
export const $t = (key: string) => key;
export const i18n = createI18n({
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh",
fallbackLocale: "en",

View File

@@ -0,0 +1,21 @@
export default {
hsLoginOut: "退出系统",
hsfullscreen: "全屏",
hsexitfullscreen: "退出全屏",
hsrefreshRoute: "刷新路由",
hslogin: "登陆",
hsadd: "新增",
hsmark: "标记/取消",
hssave: "保存",
hssearch: "搜索",
hsexpendAll: "全部展开",
hscollapseAll: "全部折叠",
hssystemSet: "打开项目配置",
hsdelete: "删除",
hsreload: "重新加载",
hscloseCurrentTab: "关闭当前标签页",
hscloseLeftTabs: "关闭左侧标签页",
hscloseRightTabs: "关闭右侧标签页",
hscloseOtherTabs: "关闭其他标签页",
hscloseAllTabs: "关闭全部标签页"
};

View File

@@ -0,0 +1,38 @@
export default {
hshome: "首页",
hslogin: "登陆",
hssysManagement: "系统管理",
hsBaseinfo: "基础信息",
hsDict: "字典管理",
hseditor: "编辑器",
hserror: "错误页面",
hsfourZeroFour: "404",
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: "权限管理",
permissionPage: "页面权限",
permissionButton: "按钮权限",
hstabs: "标签页操作",
hsMenuTree: "菜单树结构",
hsguide: "引导页",
externalLink: "外链"
};

View File

@@ -1,7 +1,6 @@
import "xe-utils";
import { App } from "vue";
import { i18n } from "../i18n/index";
import "font-awesome/css/font-awesome.css";
import {
// 核心
VXETable,
@@ -66,8 +65,8 @@ VXETable.setup({
i18n: (key, args) => i18n.global.t(key, args),
// 可选,对参数中的列头、校验提示..等进行自动翻译(只对支持国际化的有效)
translate(key, args) {
// 例如,只翻译 "message." 开头的键值
if (key && key.indexOf("message.") > -1) {
// 例如,只翻译 "buttons." 开头的键值
if (key && key.indexOf("buttons.") > -1) {
return i18n.global.t(key, args);
}
if (key && key.indexOf("el.") > -1) {

View File

@@ -1,140 +1,27 @@
import {
Router,
createRouter,
RouteComponent,
createWebHashHistory,
RouteRecordNormalized
} from "vue-router";
import { RouteConfigs } from "/@/layout/types";
import { split, uniqBy } from "lodash-es";
import { i18n } from "/@/plugins/i18n";
import { toRouteType } from "./types";
import { openLink } from "/@/utils/link";
import NProgress from "/@/utils/progress";
import { useTimeoutFn } from "@vueuse/core";
import { storageSession, storageLocal } from "/@/utils/storage";
import { usePermissionStoreHook } from "/@/store/modules/permission";
// 静态路由
import homeRouter from "./modules/home";
import Layout from "/@/layout/index.vue";
import errorRouter from "./modules/error";
import editorRouter from "./modules/editor";
import nestedRouter from "./modules/nested";
import externalLink from "./modules/externalLink";
import { constantRoutes } from "./modules";
import { split, findIndex } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import remainingRouter from "./modules/remaining";
import flowChartRouter from "./modules/flowchart";
import componentsRouter from "./modules/components";
// 动态路由
import { getAsyncRoutes } from "/@/api/routes";
// https://cn.vitejs.dev/guide/features.html#glob-import
const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue");
const constantRoutes: Array<RouteComponent> = [
homeRouter,
flowChartRouter,
editorRouter,
componentsRouter,
nestedRouter,
externalLink,
errorRouter
];
// 按照路由中meta下的rank等级升序来排序路由
export const ascending = arr => {
return arr.sort((a: any, b: any) => {
return a?.meta?.rank - b?.meta?.rank;
});
};
// 将所有静态路由导出
export const constantRoutesArr: Array<RouteComponent> = ascending(
constantRoutes
).concat(...remainingRouter);
// 过滤meta中showLink为false的路由
export const filterTree = data => {
const newTree = data.filter(v => v.meta.showLink);
newTree.forEach(v => v.children && (v.children = filterTree(v.children)));
return newTree;
};
// 从路由中提取keepAlive为true的name组成数组此处本项目中并没有用到只是暴露个方法
export const getAliveRoute = () => {
const alivePageList = [];
const recursiveSearch = treeLists => {
if (!treeLists || !treeLists.length) {
return;
}
for (let i = 0; i < treeLists.length; i++) {
if (treeLists[i]?.meta?.keepAlive) alivePageList.push(treeLists[i].name);
recursiveSearch(treeLists[i].children);
}
};
recursiveSearch(router.options.routes);
return alivePageList;
};
// 批量删除缓存路由
export const delAliveRoutes = (delAliveRouteList: Array<RouteConfigs>) => {
delAliveRouteList.forEach(route => {
usePermissionStoreHook().cacheOperate({
mode: "delete",
name: route?.name
});
});
};
// 处理缓存路由(添加、删除、刷新)
export const handleAliveRoute = (
matched: RouteRecordNormalized[],
mode?: string
) => {
switch (mode) {
case "add":
matched.forEach(v => {
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
});
break;
case "delete":
usePermissionStoreHook().cacheOperate({
mode: "delete",
name: matched[matched.length - 1].name
});
break;
default:
usePermissionStoreHook().cacheOperate({
mode: "delete",
name: matched[matched.length - 1].name
});
useTimeoutFn(() => {
matched.forEach(v => {
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
});
}, 100);
}
};
// 过滤后端传来的动态路由 重新生成规范路由
export const addAsyncRoutes = (arrRoutes: Array<RouteComponent>) => {
if (!arrRoutes || !arrRoutes.length) return;
arrRoutes.forEach((v: any) => {
if (v.redirect) {
v.component = Layout;
} else {
v.component = modulesRoutes[`/src/views${v.path}/index.vue`];
}
if (v.children) {
addAsyncRoutes(v.children);
}
});
return arrRoutes;
};
import { storageSession } from "/@/utils/storage";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { Router, RouteMeta, createRouter, RouteRecordName } from "vue-router";
import {
initRouter,
getHistoryMode,
getParentPaths,
findRouteByPath,
handleAliveRoute
} from "./utils";
// 创建路由实例
export const router: Router = createRouter({
history: createWebHashHistory(),
routes: filterTree(ascending(constantRoutes)).concat(...remainingRouter),
history: getHistoryMode(),
routes: constantRoutes.concat(...remainingRouter),
strict: true,
scrollBehavior(to, from, savedPosition) {
return new Promise(resolve => {
if (savedPosition) {
@@ -150,53 +37,10 @@ export const router: Router = createRouter({
}
});
// 初始化路由
export const initRouter = name => {
return new Promise(resolve => {
getAsyncRoutes({ name }).then(({ info }) => {
if (info.length === 0) {
usePermissionStoreHook().changeSetting(info);
} else {
addAsyncRoutes(info).map((v: any) => {
// 防止重复添加路由
if (
router.options.routes.findIndex(value => value.path === v.path) !==
-1
) {
return;
} else {
// 切记将路由push到routes后还需要使用addRoute这样路由才能正常跳转
router.options.routes.push(v);
// 最终路由进行升序
ascending(router.options.routes);
router.addRoute(v.name, v);
usePermissionStoreHook().changeSetting(info);
}
resolve(router);
});
}
router.addRoute({
path: "/:pathMatch(.*)",
redirect: "/error/404"
});
});
});
};
// 重置路由
export function resetRouter() {
router.getRoutes().forEach(route => {
const { name } = route;
if (name) {
router.hasRoute(name) && router.removeRoute(name);
}
});
}
// 路由白名单
const whiteList = ["/login"];
router.beforeEach((to, _from, next) => {
router.beforeEach((to: toRouteType, _from, next) => {
if (to.meta?.keepAlive) {
const newMatched = to.matched;
handleAliveRoute(newMatched, "add");
@@ -208,10 +52,15 @@ router.beforeEach((to, _from, next) => {
const name = storageSession.getItem("info");
NProgress.start();
const externalLink = to?.redirectedFrom?.fullPath;
// @ts-ignore
const { t } = i18n.global;
// @ts-ignore
if (!externalLink) to.meta.title ? (document.title = t(to.meta.title)) : "";
if (!externalLink)
to.matched.some(item => {
item.meta.title
? (document.title = transformI18n(
item.meta.title as string,
item.meta?.i18n as boolean
))
: "";
});
if (name) {
if (_from?.name) {
// 如果路由包含http 则是超链接 反之是普通路由
@@ -223,26 +72,75 @@ router.beforeEach((to, _from, next) => {
}
} else {
// 刷新
if (usePermissionStoreHook().wholeRoutes.length === 0)
if (usePermissionStoreHook().wholeMenus.length === 0)
initRouter(name.username).then((router: Router) => {
router.push(to.path);
// 刷新页面更新标签栏与页面路由匹配
const localRoutes = storageLocal.getItem(
"responsive-routesInStorage"
);
const optionsRoutes = router.options?.routes;
const newLocalRoutes = [];
optionsRoutes.forEach(ors => {
localRoutes.forEach(lrs => {
if (ors.path === lrs.parentPath) {
newLocalRoutes.push(lrs);
if (!useMultiTagsStoreHook().getMultiTagsCache) {
const handTag = (
path: string,
parentPath: string,
name: RouteRecordName,
meta: RouteMeta
): void => {
useMultiTagsStoreHook().handleTags("push", {
path,
parentPath,
name,
meta
});
};
// 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对静态路由)
if (to.meta?.refreshRedirect) {
const routes = router.options.routes;
const { refreshRedirect } = to.meta;
const { name, meta } = findRouteByPath(refreshRedirect, routes);
handTag(
refreshRedirect,
getParentPaths(refreshRedirect, routes)[1],
name,
meta
);
return router.push(refreshRedirect);
} else {
const { path } = to;
const index = findIndex(remainingRouter, v => {
return v.path == path;
});
const routes =
index === -1
? router.options.routes[0].children
: router.options.routes;
const route = findRouteByPath(path, routes);
const routePartent = getParentPaths(path, routes);
// 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对动态路由)
if (
path !== routes[0].path &&
route?.meta?.rank !== 0 &&
routePartent.length === 0
) {
if (!route?.meta?.refreshRedirect) return;
const { name, meta } = findRouteByPath(
route.meta.refreshRedirect,
routes
);
handTag(
route.meta?.refreshRedirect,
getParentPaths(route.meta?.refreshRedirect, routes)[0],
name,
meta
);
return router.push(route.meta?.refreshRedirect);
} else {
handTag(
route.path,
routePartent[routePartent.length - 1],
route.name,
route.meta
);
return router.push(path);
}
});
});
storageLocal.setItem(
"responsive-routesInStorage",
uniqBy(newLocalRoutes, "path")
);
}
}
router.push(to.fullPath);
});
next();
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const componentsRouter = {
@@ -6,8 +7,9 @@ const componentsRouter = {
component: Layout,
redirect: "/components/video",
meta: {
icon: "Menu",
title: "message.hscomponents",
icon: "menu",
title: $t("menus.hscomponents"),
i18n: true,
showLink: true,
rank: 4
},
@@ -17,8 +19,9 @@ const componentsRouter = {
name: "video",
component: () => import("/@/views/components/video/index.vue"),
meta: {
title: "message.hsvideo",
showLink: true
title: $t("menus.hsvideo"),
showLink: true,
i18n: true
}
},
{
@@ -26,9 +29,10 @@ const componentsRouter = {
name: "map",
component: () => import("/@/views/components/map/index.vue"),
meta: {
title: "message.hsmap",
title: $t("menus.hsmap"),
showLink: true,
keepAlive: true,
i18n: true,
transition: {
name: "fade"
}
@@ -39,8 +43,9 @@ const componentsRouter = {
name: "draggable",
component: () => import("/@/views/components/draggable/index.vue"),
meta: {
title: "message.hsdraggable",
title: $t("menus.hsdraggable"),
showLink: true,
i18n: true,
transition: {
enterTransition: "animate__zoomIn",
leaveTransition: "animate__zoomOut"
@@ -53,8 +58,9 @@ const componentsRouter = {
name: "splitPane",
component: () => import("/@/views/components/split-pane/index.vue"),
meta: {
title: "message.hssplitPane",
title: $t("menus.hssplitPane"),
showLink: true,
i18n: true,
extraIcon: {
svg: true,
name: "team-iconxinpinrenqiwang"
@@ -66,7 +72,8 @@ const componentsRouter = {
name: "button",
component: () => import("/@/views/components/button/index.vue"),
meta: {
title: "message.hsbutton",
title: $t("menus.hsbutton"),
i18n: true,
showLink: true
}
},
@@ -75,7 +82,8 @@ const componentsRouter = {
name: "cropping",
component: () => import("/@/views/components/cropping/index.vue"),
meta: {
title: "message.hscropping",
title: $t("menus.hscropping"),
i18n: true,
showLink: true
}
},
@@ -84,7 +92,8 @@ const componentsRouter = {
name: "countTo",
component: () => import("/@/views/components/count-to/index.vue"),
meta: {
title: "message.hscountTo",
title: $t("menus.hscountTo"),
i18n: true,
showLink: true
}
},
@@ -93,7 +102,8 @@ const componentsRouter = {
name: "selector",
component: () => import("/@/views/components/selector/index.vue"),
meta: {
title: "message.hsselector",
title: $t("menus.hsselector"),
i18n: true,
showLink: true
}
},
@@ -102,7 +112,8 @@ const componentsRouter = {
name: "seamlessScroll",
component: () => import("/@/views/components/seamless-scroll/index.vue"),
meta: {
title: "message.hsseamless",
title: $t("menus.hsseamless"),
i18n: true,
showLink: true
}
},
@@ -111,7 +122,8 @@ const componentsRouter = {
name: "contextmenu",
component: () => import("/@/views/components/contextmenu/index.vue"),
meta: {
title: "message.hscontextmenu",
title: $t("menus.hscontextmenu"),
i18n: true,
showLink: true
}
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const editorRouter = {
@@ -6,8 +7,9 @@ const editorRouter = {
component: Layout,
redirect: "/editor/index",
meta: {
icon: "Edit",
title: "message.hseditor",
icon: "edit",
title: $t("menus.hseditor"),
i18n: true,
showLink: true,
rank: 2
},
@@ -17,8 +19,9 @@ const editorRouter = {
name: "reEditor",
component: () => import("/@/views/editor/index.vue"),
meta: {
title: "message.hseditor",
title: $t("menus.hseditor"),
showLink: true,
i18n: true,
keepAlive: true,
extraIcon: {
svg: true,

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const errorRouter = {
@@ -6,9 +7,10 @@ const errorRouter = {
component: Layout,
redirect: "/error/401",
meta: {
icon: "Position",
title: "message.hserror",
icon: "position",
title: $t("menus.hserror"),
showLink: true,
i18n: true,
rank: 7
},
children: [
@@ -17,7 +19,8 @@ const errorRouter = {
name: "401",
component: () => import("/@/views/error/401.vue"),
meta: {
title: "message.hsfourZeroOne",
title: $t("menus.hsfourZeroOne"),
i18n: true,
showLink: true
}
},
@@ -26,7 +29,8 @@ const errorRouter = {
name: "404",
component: () => import("/@/views/error/404.vue"),
meta: {
title: "message.hsfourZeroFour",
title: $t("menus.hsfourZeroFour"),
i18n: true,
showLink: true
}
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const externalLink = {
@@ -5,17 +6,19 @@ const externalLink = {
name: "external",
component: Layout,
meta: {
icon: "Link",
title: "message.externalLink",
icon: "link",
title: $t("menus.externalLink"),
showLink: true,
i18n: true,
rank: 190
},
children: [
{
path: "https://github.com/xiaoxian521/vue-pure-admin",
meta: {
title: "message.externalLink",
title: $t("menus.externalLink"),
showLink: true,
i18n: true,
rank: 191
}
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const flowChartRouter = {
@@ -6,9 +7,10 @@ const flowChartRouter = {
component: Layout,
redirect: "/flowChart/index",
meta: {
icon: "SetUp",
title: "message.hsflowChart",
icon: "set-up",
title: $t("menus.hsflowChart"),
showLink: true,
i18n: true,
rank: 1
},
children: [
@@ -17,7 +19,8 @@ const flowChartRouter = {
name: "flowChart",
component: () => import("/@/views/flow-chart/index.vue"),
meta: {
title: "message.hsflowChart",
title: $t("menus.hsflowChart"),
i18n: true,
showLink: true
}
}

View File

@@ -0,0 +1,30 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const guideRouter = {
path: "/guide",
name: "reGuide",
component: Layout,
redirect: "/guide/index",
meta: {
icon: "guide",
title: $t("menus.hsguide"),
i18n: true,
showLink: true,
rank: 10
},
children: [
{
path: "/guide/index",
name: "reGuide",
component: () => import("/@/views/guide/index.vue"),
meta: {
title: $t("menus.hsguide"),
showLink: true,
i18n: true
}
}
]
};
export default guideRouter;

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const homeRouter = {
@@ -6,8 +7,10 @@ const homeRouter = {
component: Layout,
redirect: "/welcome",
meta: {
icon: "HomeFilled",
icon: "home-filled",
title: $t("menus.hshome"),
showLink: true,
i18n: true,
rank: 0
},
children: [
@@ -16,7 +19,8 @@ const homeRouter = {
name: "welcome",
component: () => import("/@/views/welcome.vue"),
meta: {
title: "message.hshome",
title: $t("menus.hshome"),
i18n: true,
showLink: true
}
}

View File

@@ -0,0 +1,41 @@
// 静态路由
import homeRouter from "./home";
import errorRouter from "./error";
import guideRouter from "./guide";
import editorRouter from "./editor";
import nestedRouter from "./nested";
import menuTreeRouter from "./menuTree";
import externalLink from "./externalLink";
import flowChartRouter from "./flowchart";
import remainingRouter from "./remaining";
import componentsRouter from "./components";
import { RouteRecordRaw, RouteComponent } from "vue-router";
import {
ascending,
formatTwoStageRoutes,
formatFlatteningRoutes
} from "../utils";
// 原始静态路由(未做任何处理)
const routes = [
homeRouter,
errorRouter,
guideRouter,
nestedRouter,
externalLink,
editorRouter,
menuTreeRouter,
flowChartRouter,
componentsRouter
];
// 导出处理后的静态路由(三级及以上的路由全部拍成二级)
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
formatFlatteningRoutes(ascending(routes))
);
// 用于渲染菜单,保持原始层级
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
...remainingRouter
);

View File

@@ -0,0 +1,30 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const menuTreeRouter = {
path: "/menuTree",
name: "reMenuTree",
component: Layout,
redirect: "/menuTree/index",
meta: {
icon: "RI-node-tree",
title: $t("menus.hsMenuTree"),
i18n: true,
showLink: true,
rank: 9
},
children: [
{
path: "/menuTree/index",
name: "reMenuTree",
component: () => import("/@/views/menu-tree/index.vue"),
meta: {
title: $t("menus.hsMenuTree"),
showLink: true,
i18n: true
}
}
]
};
export default menuTreeRouter;

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const nestedRouter = {
@@ -6,19 +7,21 @@ const nestedRouter = {
redirect: "/nested/menu1/menu1-1",
name: "Nested",
meta: {
title: "message.hsmenus",
icon: "Histogram",
title: $t("menus.hsmenus"),
icon: "histogram",
showLink: true,
i18n: true,
rank: 5
},
children: [
{
path: "/nested/menu1",
component: () => import("/@/views/nested/menu1/index.vue"),
component: () => import("/@/layout/routerView/parent.vue"),
name: "Menu1",
meta: {
title: "message.hsmenu1",
title: $t("menus.hsmenu1"),
showLink: true,
i18n: true,
keepAlive: true
},
redirect: "/nested/menu1/menu1-1",
@@ -28,19 +31,21 @@ const nestedRouter = {
component: () => import("/@/views/nested/menu1/menu1-1/index.vue"),
name: "Menu1-1",
meta: {
title: "message.hsmenu1-1",
title: $t("menus.hsmenu1-1"),
showLink: true,
i18n: true,
keepAlive: true
}
},
{
path: "/nested/menu1/menu1-2",
component: () => import("/@/views/nested/menu1/menu1-2/index.vue"),
component: () => import("/@/layout/routerView/parent.vue"),
name: "Menu1-2",
redirect: "/nested/menu1/menu1-2/menu1-2-1",
meta: {
title: "message.hsmenu1-2",
title: $t("menus.hsmenu1-2"),
showLink: true,
i18n: true,
keepAlive: true
},
children: [
@@ -50,8 +55,9 @@ const nestedRouter = {
import("/@/views/nested/menu1/menu1-2/menu1-2-1/index.vue"),
name: "Menu1-2-1",
meta: {
title: "message.hsmenu1-2-1",
title: $t("menus.hsmenu1-2-1"),
showLink: true,
i18n: true,
keepAlive: true
}
},
@@ -61,9 +67,10 @@ const nestedRouter = {
import("/@/views/nested/menu1/menu1-2/menu1-2-2/index.vue"),
name: "Menu1-2-2",
meta: {
title: "message.hsmenu1-2-2",
title: $t("menus.hsmenu1-2-2"),
showLink: true,
keepAlive: true,
i18n: true,
extraIcon: {
svg: true,
name: "team-iconxinpinrenqiwang"
@@ -77,8 +84,9 @@ const nestedRouter = {
component: () => import("/@/views/nested/menu1/menu1-3/index.vue"),
name: "Menu1-3",
meta: {
title: "message.hsmenu1-3",
title: $t("menus.hsmenu1-3"),
showLink: true,
i18n: true,
keepAlive: true
}
}
@@ -89,8 +97,9 @@ const nestedRouter = {
name: "Menu2",
component: () => import("/@/views/nested/menu2/index.vue"),
meta: {
title: "message.hsmenu2",
title: $t("menus.hsmenu2"),
showLink: true,
i18n: true,
keepAlive: true
}
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const remainingRouter = [
@@ -6,8 +7,9 @@ const remainingRouter = [
name: "login",
component: () => import("/@/views/login.vue"),
meta: {
title: "message.hslogin",
title: $t("menus.hslogin"),
showLink: false,
i18n: true,
rank: 101
}
},
@@ -16,8 +18,9 @@ const remainingRouter = [
name: "redirect",
component: Layout,
meta: {
icon: "HomeFilled",
title: "message.hshome",
icon: "home-filled",
title: $t("menus.hshome"),
i18n: true,
showLink: false,
rank: 104
},

Some files were not shown because too many files have changed in this diff Show More