Compare commits

...

224 Commits

Author SHA1 Message Date
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
xiaoxian521
2f457ac6b2 Merge branch 'refactor/theme' into main 2021-11-10 13:30:48 +08:00
xiaoxian521
cbc6743b5a fix: update 2021-11-10 12:37:25 +08:00
xiaoxian521
af01d80db2 fix: update 2021-11-10 11:50:29 +08:00
xiaoxian521
b45a10b98f fix: update 2021-11-10 09:55:01 +08:00
xiaoxian521
cd668c11d8 fix: update 2021-11-10 09:10:32 +08:00
xiaoxian521
753491a7eb fix: update 2021-11-10 09:00:39 +08:00
xiaoxian521
fd4706f2d3 fix: update 2021-11-10 08:39:47 +08:00
xiaoxian521
db580366fd fix: update 2021-11-10 07:59:33 +08:00
xiaoxian521
487920375b fix: update 2021-11-10 07:27:50 +08:00
xiaoxian521
42eb52bd88 fix: update 2021-11-10 07:04:19 +08:00
lrl
3943164ad6 style: sidebar icon position 2021-11-09 22:05:17 +08:00
lrl
e98dee9f0a refactor: setting 2021-11-09 19:24:55 +08:00
xiaoxian521
782cea9995 fix: update 2021-11-09 09:04:20 +08:00
xiaoxian521
41a0fde036 fix: update 2021-11-08 09:21:14 +08:00
xiaoxian521
7d453fdca7 fix: update 2021-11-07 21:11:23 +08:00
xiaoxian521
c935fce27b fix: update 2021-11-07 19:28:33 +08:00
xiaoxian521
ff4b18b73c fix: update 2021-11-07 19:02:42 +08:00
xiaoxian521
cf78f6aa6a fix: update 2021-11-07 17:45:24 +08:00
xiaoxian521
0ad3418239 fix: update vertical style 2021-11-07 16:56:15 +08:00
xiaoxian521
d501339274 chore: update 2021-11-07 13:51:18 +08:00
xiaoxian521
ad2f7358db Merge branch 'main' into refactor/theme 2021-11-07 13:32:38 +08:00
xiaoxian521
a7ca161271 fix: build svg 404 2021-11-06 20:39:29 +08:00
xiaoxian521
3a1f28667b perf: login page 2021-11-06 19:47:57 +08:00
xiaoxian521
ef1c47bac5 Merge branch 'main' of github.com:xiaoxian521/vue-pure-admin into main 2021-11-06 19:16:03 +08:00
xiaoxian521
aa7a073b4a refactor: login page 2021-11-06 19:15:10 +08:00
xiaoxian521
544e474e98 refactor: login page 2021-11-06 17:03:40 +08:00
xiaoxian521
4b16bac5dc feat: update 2021-11-06 16:15:25 +08:00
sedice
ea7266c04c fix: 修复首页柱状图在大屏下显示比例问题 (#97) 2021-11-05 00:07:49 +08:00
xiaoxian521
c18dbea3cd fix: update 2021-11-04 20:34:59 +08:00
xiaoxian521
f19f27510b chore: set vite build minify is false 2021-11-04 12:34:27 +08:00
lrl
57e32bab16 style: hideSidebar add marginLeft 2021-11-04 11:02:01 +08:00
xiaoxian521
e63fe92cd9 fix: update 2021-11-04 10:25:01 +08:00
xiaoxian521
c95126adf5 fix: update 2021-11-04 10:20:22 +08:00
xiaoxian521
54a0e457b4 feat: 剔除vxe强依赖 2021-11-03 23:55:18 +08:00
xiaoxian521
8436f964e4 Merge branch 'main' into refactor/theme 2021-11-03 23:24:53 +08:00
一万
709cb87c2b style: editor add margin (#96) 2021-11-03 22:28:15 +08:00
一万
e0551251d3 style: perf sidebar style (#95) 2021-11-03 20:51:16 +08:00
xiaoxian521
dae0b5dc08 workflow: update 2021-11-03 14:20:54 +08:00
xiaoxian521
077276463f workflow: update 2021-11-03 14:18:16 +08:00
xiaoxian521
43a8baee3d workflow: update 2021-11-03 14:16:34 +08:00
xiaoxian521
91fe227e8c chore: update 2021-11-03 14:04:13 +08:00
xiaoxian521
3f526873d8 chore: update 2021-11-03 14:01:48 +08:00
xiaoxian521
def4c60395 workflow: update 2021-11-03 14:00:50 +08:00
xiaoxian521
6c02e97157 chore: use pnpm replace yarn 2021-11-03 13:58:24 +08:00
一万
c25b130b7d perf: perf sidebar (#91) 2021-11-03 10:13:27 +08:00
xiaoxian521
c266a675a2 feat: update 2021-11-02 13:26:25 +08:00
xiaoxian521
6b151eae1b Merge branch 'main' into refactor/theme 2021-11-02 07:48:14 +08:00
xiaoxian521
0615902632 perf: home page 2021-11-01 13:31:49 +08:00
xiaoxian521
b773fb582d perf: style 2021-10-31 21:52:39 +08:00
一万
9ddf449ea7 perf: perf style (#89)
perf: perf style
2021-10-31 20:24:57 +08:00
xiaoxian521
8a14519467 feat: update 2021-10-31 15:42:46 +08:00
xiaoxian521
321b9da9a8 feat: update 2021-10-31 14:19:08 +08:00
xiaoxian521
594efc6204 feat: update 2021-10-31 13:42:51 +08:00
xiaoxian521
c819554e54 feat: update 2021-10-31 10:49:35 +08:00
xiaoxian521
3e12372d9c chore: update 2021-10-31 10:20:36 +08:00
艾阳
0fb7b5e983 fix: 修改面包屑重定向到第一个子级 (#87) 2021-10-31 00:40:01 +08:00
xiaoxian521
9d061c6406 feat: update 2021-10-30 22:11:18 +08:00
xiaoxian521
5f349775a8 feat: update 2021-10-30 21:14:25 +08:00
一万
4e04fdd657 fix(panel): fix setting tooltip (#85)
* fix(panel): fix setting tooltip
2021-10-30 19:50:47 +08:00
xiaoxian521
c418ab03a2 style: 优化样式 2021-10-30 18:56:37 +08:00
hb0730
8ffd341443 fix(axios): 修复封装的axios请求过快beforeRequestCallback,beforeResponseCallback只触发一次 (#84) 2021-10-30 13:56:42 +08:00
一万
72224717f9 style: 恢复标签页横向滚动条 (#83)
* style: 恢复标签页横向滚动条
2021-10-30 13:19:41 +08:00
一万
d4237aa596 fix: 修复菜单隐藏,内容显示问题 (#81) 2021-10-29 22:04:51 +08:00
一万
ae72cccbb9 fix: 菜单名称过长添加tooltip (#78)
* fix: 菜单名称过长添加tooltip
2021-10-29 18:41:57 +08:00
xiaoxian521
3389f3118a fix: 修复封装的axios请求过快beforeRequestCallback只触发一次 2021-10-29 15:37:48 +08:00
xiaoxian521
15ccd5db5b fix: update 2021-10-29 15:07:06 +08:00
xiaoxian521
a28eb4b203 feat: update 2021-10-29 14:50:10 +08:00
xiaoxian521
f921996563 feat: update 2021-10-29 14:09:05 +08:00
xiaoxian521
71bd7e89d4 feat: update 2021-10-29 14:09:05 +08:00
xiaoxian521
02f2cd9747 feat: update 2021-10-29 14:09:04 +08:00
xiaoxian521
af6a1b03cd fix: update 2021-10-29 14:09:04 +08:00
xiaoxian521
b248d9ea31 fix: delete toggleTheme.ts 2021-10-29 14:09:04 +08:00
xiaoxian521
3851d284bf feat: update 2021-10-29 14:09:04 +08:00
xiaoxian521
b520e234a1 feat: update 2021-10-29 14:08:00 +08:00
xiaoxian521
b40cd2db74 fix: xgplayer path error 2021-10-29 14:08:00 +08:00
xiaoxian521
fc4bfc35d7 refactor: theme 2021-10-29 14:08:00 +08:00
huohuoit
769e684c61 * feat:新增vscode中的vue3.2.setup-snippets代码片段文件
* feat:新增vscode中的vue3.2.setup-snippets代码片段文件
2021-10-28 11:43:34 +08:00
一万
25c37bb2fa fix: xgplayer path error (#76) 2021-10-27 18:27:27 +08:00
一万
ab06aa00ed fix: 修复取消固定头部问题 (#74) 2021-10-27 14:45:40 +08:00
啝裳
1fa5793ec4 fix: 修复取消固定头部问题 (#75)
* fix: 修复取消固定头部问题

* perf: simplify the layout code
2021-10-27 14:40:31 +08:00
xiaoxian521
5a78685ac9 docs: update README.md 2021-10-26 12:35:06 +08:00
一万
ae161e8b48 feat(backtop): add backtop (#71) 2021-10-25 08:55:17 +08:00
lrl
8d2824fe82 perf: 隐藏标签页功能优化 2021-10-24 15:39:01 +08:00
xiaoxian521
822261c922 docs: update README.md 2021-10-24 14:02:24 +08:00
xiaoxian521
3e4b49b4be style: 调整地图的高度 2021-10-24 13:54:36 +08:00
xiaoxian521
c5d6d2c76e refactor: login page 2021-10-24 13:48:33 +08:00
xiaoxian521
2d9c185b4e style: 修改layout样式 2021-10-24 11:53:43 +08:00
一万
70af35dbf5 fix: 修复隐藏标签页问题 (#69)
* fix: 修复隐藏标签页问题
2021-10-23 22:50:03 +08:00
xiaoxian521
7dcf98a178 style: 优化导航样式 2021-10-23 20:40:32 +08:00
xiaoxian521
089dea8441 Merge branches 'main' and 'main' of github.com:xiaoxian521/vue-pure-admin into main 2021-10-23 19:16:12 +08:00
xiaoxian521
c7b61febc3 docs: update README.md 2021-10-23 19:15:54 +08:00
一万
8b8cd52f64 style(flow-chart): 流程图数据弹窗样式优化 (#68) 2021-10-22 21:21:42 +08:00
xiaoxian521
eb8d3ca96e perf: icon 2021-10-22 20:38:19 +08:00
xiaoxian521
7e6768208d docs: update README.md 2021-10-22 17:52:38 +08:00
xiaoxian521
3980c9a876 docs: update readme.me 2021-10-22 17:49:48 +08:00
xiaoxian521
ca849c94e2 fix: 修复流程图打包后出错 2021-10-21 18:55:46 +08:00
xiaoxian521
430e5d75b0 docs: update readme.me 2021-10-21 13:00:11 +08:00
xiaoxian521
5f294356a9 docs: update readme.md 2021-10-21 10:39:21 +08:00
xiaoxian521
4a6fcdb8e5 docs: update readme.md 2021-10-21 10:35:16 +08:00
xiaoxian521
52394cc550 docs: update docs 2021-10-21 09:51:01 +08:00
xiaoxian521
f7036143a4 chore: update some packages version 2021-10-20 22:30:52 +08:00
xiaoxian521
7d0367dc58 Merge branch 'main' of github.com:xiaoxian521/vue-pure-admin into main 2021-10-20 21:26:54 +08:00
xiaoxian521
f872ea1a23 fix: 修复vxe-table打包找不到css问题 2021-10-20 21:26:39 +08:00
Ten-K
016948d852 fix(tag): 修复关闭左侧/右侧标签页时未删除缓存路由问题 (#66) 2021-10-20 15:45:35 +08:00
xiaoxian521
7724d6443e chore: update element-plus lastest 2021-10-20 11:40:22 +08:00
xiaoxian521
7013e57d1e fix: 右键菜单z-index问题 2021-10-19 18:58:47 +08:00
Ten-K
639a69d579 fix(tag): 删除非当前激活tag时不切换tag (#60)
* fix(tag): 删除非当前激活tag时不切换tag

* fix(tag): 修复在激活菜单时选择关闭左侧标签后当前激活的菜单非选择菜单

* perf: 自定义去重函数更改为lodash-es的uniqBy
2021-10-19 13:58:17 +08:00
xiaoxian521
b2c05e4397 docs: 更新文档 2021-10-19 12:36:13 +08:00
qingguizhang
f76f43ca0e style: 页面内全屏效果样式
* style: 页面内全屏效果样式
2021-10-19 11:59:17 +08:00
xiaoxian521
a322b8497e perf: 页面内全屏效果 2021-10-19 10:45:41 +08:00
xiaoxian521
b75eecabd2 Merge branch 'main' of github.com:xiaoxian521/vue-pure-admin into main 2021-10-19 10:07:44 +08:00
xiaoxian521
4aaffc53a8 style: use :deep() replace >>> 2021-10-19 10:07:24 +08:00
黄兵
a2a793f84e fix(icon): icon vary in size 2021-10-19 10:01:23 +08:00
黄兵
d551f372e4 style(element-ui.scss): move to component css 2021-10-19 10:01:23 +08:00
黄兵
04f4815f04 style(element-ui.scss): remove global .el-dialog
change .el-dialog to .flow-dialog
2021-10-19 10:01:23 +08:00
hb0730
cec2ffd3eb style(element-ui.scss): remove global .el-dialog
* style(element-ui.scss):  remove global  .el-dialog

change .el-dialog to .flow-dialog

* style(element-ui.scss): move to component css
2021-10-19 09:11:46 +08:00
xiaoxian521
1b56c86e07 docs: 添加配套视频教程地址 2021-10-18 22:04:57 +08:00
xiaoxian521
8b1b37b727 Merge branch 'main' of github.com:xiaoxian521/vue-pure-admin into main 2021-10-18 16:02:41 +08:00
xiaoxian521
f45588e811 fix: 主界面最外层正常情况下出现滚动条 2021-10-18 16:02:26 +08:00
zhuoyue03007
7f34b63569 perf: 主界面布局滚动条显示样式处理 (#59)
perf: 主界面布局滚动条采用el-scrollbar
2021-10-18 15:05:23 +08:00
xiaoxian521
db4855737b fix: 字典管理页面点击编辑弹出遮罩层没有覆盖到整个页面,菜单还能选择 2021-10-18 13:02:36 +08:00
xiaoxian521
ee7241da3f fix: 修复清空缓存并退出登陆后再次进入页面routerArrays未初始化问题 2021-10-18 10:34:28 +08:00
xiaoxian521
3cd4f340a1 types: ignore db.ts 2021-10-15 14:08:47 +08:00
xiaoxian521
a7c12d93d3 types: optimizate layout 2021-10-15 13:19:12 +08:00
xiaoxian521
e22e19552a perf: delete settings.ts use serverConfig.json 2021-10-15 11:38:43 +08:00
163 changed files with 12958 additions and 7692 deletions

14
.env
View File

@@ -1,14 +1,2 @@
# port
# 项目本地运行端口号
VITE_PORT = 8848
# title
VITE_TITLE = vue-pure-admin
# version
VITE_VERSION = 2.1.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.1.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

@@ -45,11 +45,21 @@ jobs:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: "16"
registry-url: https://registry.npmjs.com/
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Build
run: |
yarn install
yarn lint
yarn build
pnpm install
pnpm lint
env:
VALIDATE_ALL_CODEBASE: false
DEFAULT_BRANCH: main

7
.gitignore vendored
View File

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

View File

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

11
.markdownlint.json Normal file
View File

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

View File

@@ -9,7 +9,6 @@
"terminal.integrated.rendererType": "dom",
"editor.formatOnType": true,
"editor.formatOnSave": true,
"window.zoomLevel": 1,
"javascript.updateImportsOnFileMove.enabled": "always",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -43,5 +42,8 @@
"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"]
}

20
.vscode/vue3.2.setup-snippets vendored Normal file
View File

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

View File

@@ -1,3 +1,36 @@
# 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
- Refactored navigation theme color, supports multiple color schemes
- Refactored login page, illustration style
### 🍏 Perf
- Optimize the navigation style
- Eliminate strong navigation dependence on vxe-table
- Synchronously update element-plus, replace Font Icon with SVG Icon
# 2.1.0(2021-10-14)
### 🎫 Feat

View File

@@ -1,3 +1,36 @@
# 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
- Refactored navigation theme color, supports multiple color schemes
- Refactored login page, illustration style
### 🍏 Perf
- Optimize the navigation style
- Eliminate strong navigation dependence on vxe-table
- Synchronously update element-plus, replace Font Icon with SVG Icon
# 2.1.0(2021-10-14)
### 🎫 Feat

View File

@@ -1,3 +1,36 @@
# 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
- 重构导航主题色,支持多种配色
- 重构登录页,插画风格
### 🍏 Perf
- 优化导航样式
- 剔除导航强依赖 vxe-table
- 同步更新 element-plus使用 SVG Icon 替换 Font Icon
# 2.1.0(2021-10-14)
### 🎫 Feat

View File

@@ -6,32 +6,28 @@
## Introduction
vue-pure-admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite2`, `TypeScript`, `Element-Plus` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference.
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: <https://www.bilibili.com/video/BV1534y1S7HV/>
UI Design: <https://www.bilibili.com/video/BV17g411T7rq/>
## Docs
<https://pure-admin-doc.vercel.app/>
## Thin
Github Address: <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?">
</p>
### Use Gitpod
@@ -55,20 +51,20 @@ git clone https://github.com.cnpmjs.org/xiaoxian521/vue-pure-admin.git
```bash
cd vue-pure-admin
yarn install
pnpm install
```
- run
```bash
yarn serve
pnpm serve
```
- build
```bash
yarn build
pnpm build
```
## Change Log
@@ -123,14 +119,24 @@ Support modern browsers, not IE
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
![donate](http://yiming_chang.gitee.io/manages/pay.jpg)
## Exchange Group
Please scan the code to join the WeChat exchange group, if you have any questions, you can communicate in the group!
![group](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0c5f882422bd47b5af691906ea994ccf~tplv-k3u1fbpfcp-watermark.awebp?)
<img src="http://yiming_chang.gitee.io/manages/pay.png" width="360px" height="480px" />
## License
In principle, no fees and copyrights are charged, so you can use it with confidence
[MIT © xiaoxian521-2020](./LICENSE)
## Backers
Thank you very much for your support, I believe the project will get better and better! ! ! :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> |
## Contributors
This project exists thanks to all the people who contribute!!! :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

View File

@@ -6,32 +6,28 @@
## 简介
vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite2`,`TypeScript`,`Element-Plus`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3` `vite2` `Element-Plus` `TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
## 配套视频
教程:<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?">
</p>
### 使用 Gitpod
@@ -55,20 +51,20 @@ git clone https://github.com.cnpmjs.org/xiaoxian521/vue-pure-admin.git
```bash
cd vue-pure-admin
yarn install
pnpm install
```
- 运行
```bash
yarn serve
pnpm serve
```
- 打包
```bash
yarn build
pnpm build
```
## 更新日志
@@ -121,16 +117,32 @@ yarn build
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持
![donate](http://yiming_chang.gitee.io/manages/pay.jpg)
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## 交流群
## 付费咨询、需求定制
请扫码加微信交流群,有问题可以在群里沟通!
作者精力有限,需要提供技术服务的可扫下面的二维码加微信,添加请备注来意
![group](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0c5f882422bd47b5af691906ea994ccf~tplv-k3u1fbpfcp-watermark.awebp?)
<img src="http://yiming_chang.gitee.io/manages/wechat.jpg" width="150px" height="150px" />
## License
## 许可证
原则上不收取任何费用及版权,可以放心使用
[MIT © xiaoxian521-2020](./LICENSE)
## 捐赠者
非常感谢你们的支持,相信项目会越来越好!!!:heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> |
## 贡献者
这个项目的存在感谢所有做出贡献的人!!!:heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

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

@@ -5,7 +5,7 @@
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/iconfont.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理系统</title>
<title>vue-pure-admin</title>
<script src="/sortable.min.js"></script>
<script>
window.process = {};

View File

@@ -7,8 +7,9 @@ const systemRouter = {
name: "system",
redirect: "/system/user",
meta: {
icon: "el-icon-setting",
icon: "Setting",
title: "message.hssysManagement",
i18n: true,
showLink: true,
rank: 6
},
@@ -18,6 +19,7 @@ const systemRouter = {
name: "user",
meta: {
title: "message.hsBaseinfo",
i18n: true,
showLink: true
}
},
@@ -26,7 +28,9 @@ const systemRouter = {
name: "dict",
meta: {
title: "message.hsDict",
showLink: true
i18n: true,
showLink: true,
keepAlive: true
}
}
]
@@ -38,7 +42,8 @@ const permissionRouter = {
redirect: "/permission/page",
meta: {
title: "message.permission",
icon: "el-icon-lollipop",
icon: "Lollipop",
i18n: true,
showLink: true,
rank: 3
},
@@ -48,6 +53,7 @@ const permissionRouter = {
name: "permissionPage",
meta: {
title: "message.permissionPage",
i18n: true,
showLink: true
}
},
@@ -56,6 +62,7 @@ const permissionRouter = {
name: "permissionButton",
meta: {
title: "message.permissionButton",
i18n: true,
showLink: true,
authority: []
}
@@ -63,6 +70,42 @@ const permissionRouter = {
]
};
const tabsRouter = {
path: "/tabs",
name: "reTabs",
redirect: "/tabs/index",
meta: {
icon: "IF-team-icontabs",
title: "message.hstabs",
i18n: true,
showLink: true,
rank: 8
},
children: [
{
path: "/tabs/index",
name: "reTabs",
meta: {
title: "message.hstabs",
showLink: true,
i18n: true
}
},
{
path: "/tabs/detail",
name: "tabDetail",
meta: {
title: "",
showLink: false,
i18n: false,
dynamicLevel: 3,
realPath: "/tabs/detail",
refreshRedirect: "/tabs/index"
}
}
]
};
// 添加不同按钮权限到/permission/button页面中
function setDifAuthority(authority, routes) {
routes.children[1].meta.authority = [authority];
@@ -77,12 +120,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,8 +1,8 @@
import { MockMethod } from "vite-plugin-mock";
// http://mockjs.com/examples.html#Object
const echartsList = (): any => {
const result: any[] = [];
const echartsList = () => {
const result = [];
for (let index = 0; index < 200; index++) {
result.push(["@date", Math.floor(Math.random() * 300)]);
}

View File

@@ -1,8 +1,16 @@
import { MockMethod } from "vite-plugin-mock";
type mapType = {
plateNumber: string;
driver: string;
"orientation|1-360": number;
"lng|113-114.1-10": number;
"lat|34-35.1-10": number;
};
// http://mockjs.com/examples.html#Object
const mapList = (): any => {
const result: any[] = [];
const mapList = (): Array<mapType> => {
const result: Array<mapType> = [];
for (let index = 0; index < 200; index++) {
result.push({
plateNumber: "豫A@natural(11111, 99999)@character('upper')",

View File

@@ -1,36 +1,52 @@
{
"name": "vue-pure-admin",
"version": "2.1.0",
"version": "2.7.0",
"private": true,
"engines": {
"node": ">= 16",
"pnpm": ">= 6"
},
"scripts": {
"dev": "cross-env --max_old_space_size=4096 vite",
"serve": "yarn dev",
"serve": "pnpm dev",
"build": "rimraf dist && cross-env vite build",
"preview": "vite preview",
"preview:build": "yarn build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && yarn cache clean && yarn",
"preview:build": "pnpm build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged",
"lint": "yarn lint:eslint && yarn lint:prettier && yarn lint:stylelint && yarn lint:pretty",
"prepare": "husky install"
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint && pnpm lint:pretty",
"prepare": "husky install",
"preinstall": "npx only-allow pnpm"
},
"browserslist": [
"> 1%",
"not ie 11",
"not op_mini all"
],
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@logicflow/core": "^0.4.6",
"@logicflow/extension": "^0.4.6",
"@vueuse/core": "^6.5.3",
"@element-plus/icons-vue": "^0.2.4",
"@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",
"animate.css": "^4.1.1",
"await-to-js": "^3.0.0",
"axios": "^0.21.1",
"cropperjs": "^1.5.11",
"dayjs": "^1.10.7",
"echarts": "^5.2.1",
"element-plus": "1.1.0-beta.20",
"element-plus": "1.2.0-beta.6",
"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",
@@ -38,65 +54,71 @@
"nprogress": "^0.2.0",
"path": "^0.12.7",
"path-to-regexp": "^6.2.0",
"pinia": "2.0.0-rc.10",
"pinia": "^2.0.0-rc.14",
"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",
"v-contextmenu": "^3.0.0",
"vue": "^3.2.20",
"v-contextmenu": "3.0.0",
"vue": "^3.2.24",
"vue-i18n": "^9.2.0-beta.3",
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.11",
"vue-router": "^4.0.12",
"vue-types": "^4.1.0",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.0.27",
"wangeditor": "^4.7.7",
"xe-ajax": "^4.0.5",
"xe-utils": "^3.3.1",
"xgplayer": "^2.28.0"
"vuedraggable": "4.1.0",
"vxe-table": "4.0.30",
"wangeditor": "^4.7.9",
"xe-ajax": "4.0.5",
"xe-utils": "3.4.0",
"xgplayer": "2.28.0"
},
"devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@types/element-resize-detector": "^1.1.3",
"@types/mockjs": "^1.0.3",
"@types/node": "^14.14.14",
"@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"@vitejs/plugin-vue": "^1.6.0",
"@vitejs/plugin-vue-jsx": "^1.1.7",
"@vue/compiler-sfc": "^3.2.20",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"autoprefixer": "^10.2.4",
"babel-plugin-transform-remove-console": "^6.9.4",
"chalk": "^2.4.2",
"cross-env": "^7.0.3",
"eslint": "^7.30.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.17.0",
"husky": "^7.0.2",
"lint-staged": "^11.1.2",
"postcss": "^8.2.6",
"postcss-import": "^14.0.0",
"prettier": "^2.3.2",
"pretty-quick": "^3.1.1",
"rimraf": "^3.0.2",
"sass": "^1.38.0",
"sass-loader": "^12.1.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"typescript": "^4.4.2",
"unplugin-element-plus": "^0.1.0",
"vite": "^2.6.7",
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@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-legacy": "^1.6.4",
"@vitejs/plugin-vue": "^1.10.2",
"@vitejs/plugin-vue-jsx": "^1.3.1",
"@vue/compiler-sfc": "^3.2.24",
"@vue/eslint-config-prettier": "6.0.0",
"@vue/eslint-config-typescript": "7.0.0",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.0",
"autoprefixer": "10.2.4",
"babel-plugin-transform-remove-console": "6.9.4",
"chalk": "2.4.2",
"cross-env": "7.0.3",
"eslint": "7.30.0",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-vue": "7.17.0",
"husky": "7.0.2",
"lint-staged": "11.1.2",
"postcss": "8.2.6",
"postcss-import": "14.0.0",
"prettier": "2.3.2",
"pretty-quick": "3.1.1",
"rimraf": "3.0.2",
"sass": "^1.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.3",
"vite": "2.6.14",
"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"
"vue-eslint-parser": "7.10.0"
},
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
"author": "xiaoxian521",

7806
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,11 +1,18 @@
{
"Version": "2.0.0",
"Version": "2.7.0",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,
"MultiTagsCache": false,
"KeepAlive": true,
"Locale": "zh",
"Layout": "vertical-dark",
"Layout": "vertical",
"Theme": "default",
"Grey": false,
"Weak": false,
"HideTabs": false,
"MapConfigure": {
"amapKey": "97b3248d1553172e81f168cf94ea667e",
"baiduKey": "wTHbkkEweiFqZLKunMIjcrb2RcqNXkhc",
"options": {
"resizeEnable": true,
"center": [113.6401, 34.72468],

View File

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

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

@@ -1,16 +1,26 @@
import { http } from "../utils/http";
interface userType extends Promise<any> {
svg?: string;
code?: number;
info?: object;
}
// 获取验证码
export const getVerify = () => {
export const getVerify = (): userType => {
return http.request("get", "/captcha");
};
// 登录
export const getLogin = (data: object) => {
return http.request("post", "/login", data);
return http.request("post", "/login", { data });
};
// 注册
export const getRegist = (data: object) => {
return http.request("post", "/register", data);
// 刷新token
export const refreshToken = (data: object) => {
return http.request("post", "/refreshToken", { data });
};
// export const searchVague = (data: object) => {
// return http.request("post", "/searchVague", { data });
// };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 680 KiB

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: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

File diff suppressed because one or more lines are too long

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/assets/login/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

View File

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

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

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

After

Width:  |  Height:  |  Size: 608 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

@@ -111,7 +111,7 @@ let classOption = reactive({
.warp {
width: 95%;
height: 230px;
height: 215px;
margin: 0 auto;
overflow: hidden;

View File

@@ -2,9 +2,15 @@
import { ref, unref } from "vue";
import { LogicFlow } from "@logicflow/core";
type nodeListType = {
text: string;
class: string;
type: string;
};
interface Props {
lf: LogicFlow;
nodeList: ForDataType<undefined>;
nodeList: Array<nodeListType>;
}
const props = withDefaults(defineProps<Props>(), {

View File

@@ -1,5 +1,79 @@
import { App } from "vue";
import { App, defineComponent } from "vue";
import icon from "./src/Icon.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { iconComponents } from "/@/plugins/element-plus";
/**
* find icon component
* @param icon icon图标
* @returns component
*/
export function findIconReg(icon: string) {
// fontawesome
const faReg = /^FA-/;
// iconfont
const iFReg = /^IF-/;
// remixicon
const riReg = /^RI-/;
// typeof icon === "function" 属于SVG
if (faReg.test(icon)) {
const text = icon.split(faReg)[1];
return findIcon(
text.slice(0, text.indexOf(" ")),
"FA",
text.slice(text.indexOf(" ") + 1, text.length)
);
} 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 === "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") {
const components = iconComponents.filter(
component => component.name === icon
);
if (components.length > 0) {
return components[0];
} else {
return null;
}
} else if (type === "SVG") {
return icon;
}
}
export const Icon = Object.assign(icon, {
install(app: App) {

View File

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

View File

@@ -3,7 +3,7 @@ import AMapLoader from "@amap/amap-jsapi-loader";
import { reactive, getCurrentInstance, onBeforeMount, onUnmounted } from "vue";
import { mapJson } from "/@/api/mock";
import { deviceDetection } from "/@/utils/deviceDetection";
import greenCar from "/@/assets/green.png";
import car from "/@/assets/car.png";
export interface MapConfigureInter {
on: Fn;
@@ -15,6 +15,10 @@ export interface MapConfigureInter {
plugin?: Fn;
}
type resultType = {
info: Array<undefined>;
};
export interface mapInter {
loading: boolean;
}
@@ -24,7 +28,7 @@ let map: MapConfigureInter;
const instance = getCurrentInstance();
const mapSet: mapInter = reactive({
const mapSet = reactive({
loading: deviceDetection() ? false : true
});
@@ -72,7 +76,7 @@ onBeforeMount(() => {
var { driver, plateNumber, orientation } = data[0];
var content = `<img style="transform: scale(1) rotate(${
360 - Number(orientation)
}deg);" src='${greenCar}' />`;
}deg);" src='${car}' />`;
marker.setContent(content);
marker.setLabel({
direction: "bottom",
@@ -92,7 +96,7 @@ onBeforeMount(() => {
// 获取模拟车辆信息
mapJson()
.then(res => {
.then((res: resultType) => {
let points: object = res.info.map((v: any) => {
return {
lnglat: [v.lng, v.lat],
@@ -122,19 +126,12 @@ onUnmounted(() => {
</script>
<template>
<div
id="mapview"
ref="mapview"
v-loading="mapSet.loading"
element-loading-text="地图加载中"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
></div>
<div id="mapview" ref="mapview" v-loading="mapSet.loading"></div>
</template>
<style lang="scss" scoped>
#mapview {
height: 100%;
height: calc(100vh - 86px);
}
:deep(.amap-marker-label) {

View File

@@ -1,12 +1,15 @@
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);
};
const getConfig = (key?: string) => {
const getConfig = (key?: string): ServerConfigs => {
if (typeof key === "string") {
const arr = key.split(".");
if (arr && arr.length) {
@@ -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

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

View File

@@ -1,6 +1,9 @@
<script setup lang="ts">
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";
@@ -17,13 +20,17 @@ const pureApp = useAppStoreHook();
const router = useRouter();
const route = useRoute();
let usename = storageSession.getItem("info")?.username;
const { locale, t } = useI18n();
const { locale } = useI18n();
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,6 +72,8 @@ function translationEn() {
<Breadcrumb class="breadcrumb-container" />
<div class="vertical-header-right">
<!-- 通知 -->
<Notice />
<!-- 全屏 -->
<screenfull v-show="!deviceDetection()" />
<!-- 国际化 -->
@@ -78,6 +87,8 @@ function translationEn() {
color: locale === 'zh' ? '#f4f4f5' : '#000'
}"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
@@ -86,6 +97,8 @@ function translationEn() {
color: locale === 'en' ? '#f4f4f5' : '#000'
}"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
@@ -94,24 +107,25 @@ 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("message.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<i
<el-icon
class="el-icon-setting"
:title="$t('message.hssystemSet')"
@click="onPanel"
></i>
>
<Setting />
</el-icon>
</div>
</div>
</template>
@@ -145,6 +159,12 @@ function translationEn() {
color: #000000d9;
justify-content: flex-end;
:deep(.dropdown-badge) {
&:hover {
background: #f6f6f6;
}
}
.screen-full {
cursor: pointer;
@@ -191,8 +211,8 @@ function translationEn() {
.el-icon-setting {
height: 48px;
width: 40px;
padding: 11px;
width: 38px;
padding: 12px;
display: flex;
cursor: pointer;
align-items: center;
@@ -218,10 +238,27 @@ function translationEn() {
color: #606266;
background: #f0f0f0;
}
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
}
.logout {
max-width: 120px;
.el-dropdown-menu__item {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
padding: 0 18px !important;
}

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

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

View File

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

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

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: "message.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

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

View File

@@ -8,22 +8,27 @@ import {
getCurrentInstance
} from "vue";
import { useI18n } from "vue-i18n";
import settings from "/@/settings";
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";
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
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;
@@ -49,9 +54,10 @@ function onPanel() {
emitter.emit("openPanel");
}
const activeMenu = computed(() => {
const activeMenu = computed((): string => {
const { meta, path } = route;
if (meta.activeMenu) {
// @ts-ignore
return meta.activeMenu;
}
return path;
@@ -113,8 +119,8 @@ onMounted(() => {
<template>
<div class="horizontal-header">
<div class="horizontal-header-left" @click="backHome">
<i class="fa fa-optin-monster"></i>
<h4>{{ settings.title }}</h4>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<h4>{{ title }}</h4>
</div>
<el-menu
ref="menu"
@@ -126,13 +132,15 @@ 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 />
<!-- 全屏 -->
<screenfull v-show="!deviceDetection()" />
<!-- 国际化 -->
@@ -146,6 +154,8 @@ onMounted(() => {
color: locale === 'zh' ? '#f4f4f5' : '#000'
}"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
@@ -154,6 +164,8 @@ onMounted(() => {
color: locale === 'en' ? '#f4f4f5' : '#000'
}"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
@@ -162,24 +174,25 @@ 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("message.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<i
<el-icon
class="el-icon-setting"
:title="$t('message.hssystemSet')"
@click="onPanel"
></i>
>
<Setting />
</el-icon>
</div>
</div>
</template>
@@ -195,10 +208,27 @@ onMounted(() => {
color: #606266;
background: #f0f0f0;
}
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
}
.logout {
max-width: 120px;
.el-dropdown-menu__item {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
padding: 0 18px !important;
}

View File

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

View File

@@ -1,12 +1,18 @@
<script setup lang="ts">
import path from "path";
import { PropType, ref } from "vue";
import { PropType, ref, nextTick, getCurrentInstance } from "vue";
import { childrenType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { RouteRecordRaw } from "vue-router";
import { transformI18n } from "/@/plugins/i18n";
import { findIconReg } from "/@/components/ReIcon";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const menuMode = instance.$storage.layout?.layout === "vertical";
const pureApp = useAppStoreHook();
const props = defineProps({
item: {
type: Object as PropType<RouteRecordRaw>
type: Object as PropType<childrenType>
},
isNest: {
type: Boolean,
@@ -18,21 +24,33 @@ const props = defineProps({
}
});
type childrenType = {
path?: string;
noShowingChildren?: boolean;
children?: RouteRecordRaw[];
meta?: {
icon?: string;
title?: string;
};
};
const onlyOneChild: childrenType = ref(null);
// 存放菜单是否存在showTooltip属性标识
const hoverMenuMap = new WeakMap();
// 存储菜单文本dom元素
const menuTextRef = ref(null);
const onlyOneChild = ref<RouteRecordRaw | childrenType>({} as any);
function hoverMenu(key) {
// 如果当前菜单showTooltip属性已存在退出计算
if (hoverMenuMap.get(key)) return;
nextTick(() => {
// 如果文本内容的整体宽度大于其可视宽度,则文本溢出
menuTextRef.value?.scrollWidth > menuTextRef.value?.clientWidth
? Object.assign(key, {
showTooltip: true
})
: Object.assign(key, {
showTooltip: false
});
hoverMenuMap.set(key, true);
});
}
function hasOneShowingChild(
children: RouteRecordRaw[] = [],
parent: RouteRecordRaw
children: childrenType[] = [],
parent: childrenType
) {
const showingChildren = children.filter((item: any) => {
onlyOneChild.value = item;
@@ -51,7 +69,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>
@@ -59,26 +82,69 @@ function resolvePath(routePath) {
<template
v-if="
hasOneShowingChild(props.item.children, props.item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!props.item.alwaysShow
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
"
>
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
style="display: flex; align-items: center"
>
<i
:class="
onlyOneChild.meta.icon || (props.item.meta && props.item.meta.icon)
"
/>
<el-icon v-show="props.item.meta.icon">
<component
:is="
findIconReg(
onlyOneChild.meta.icon ||
(props.item.meta && props.item.meta.icon)
)
"
></component>
</el-icon>
<template #title>
<span>{{ $t(onlyOneChild.meta.title) }}</span>
<Icon
v-if="onlyOneChild.meta.extraIcon"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/>
<div
:style="{
width: pureApp.sidebar.opened ? '' : '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}"
>
<span v-if="!menuMode">{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}</span>
<el-tooltip
v-else
placement="top"
:offset="-10"
:disabled="!onlyOneChild.showTooltip"
>
<template #content>
{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}
</template>
<span
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
overflow: 'hidden',
textOverflow: 'ellipsis',
outline: 'none'
}"
@mouseover="hoverMenu(onlyOneChild)"
>
{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}
</span>
</el-tooltip>
<Icon
v-if="onlyOneChild.meta.extraIcon"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/>
</div>
</template>
</el-menu-item>
</template>
@@ -90,8 +156,38 @@ function resolvePath(routePath) {
popper-append-to-body
>
<template #title>
<i :class="props.item.meta.icon"></i>
<span>{{ $t(props.item.meta.title) }}</span>
<el-icon v-show="props.item.meta.icon" :class="props.item.meta.icon">
<component
:is="findIconReg(props.item.meta && props.item.meta.icon)"
></component>
</el-icon>
<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>
{{ 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'
}"
@mouseover="hoverMenu(props.item)"
>
<span style="overflow: hidden; text-overflow: ellipsis">
{{ transformI18n(props.item.meta.title, props.item.meta.i18n) }}
</span>
</div>
</el-tooltip>
<Icon
v-if="props.item.meta.extraIcon"
:svg="props.item.meta.extraIcon.svg ? true : false"

View File

@@ -12,14 +12,14 @@ 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 isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
const activeMenu = computed(() => {
const activeMenu = computed((): string => {
const { meta, path } = route;
if (meta.activeMenu) {
// @ts-ignore
return meta.activeMenu;
}
return path;
@@ -67,12 +67,14 @@ onBeforeMount(() => {
router
:collapse-transition="false"
mode="vertical"
class="outer-most"
@select="menuSelect"
>
<sidebar-item
v-for="route in routeStore.wholeRoutes"
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:item="route"
class="outer-most"
:base-path="route.path"
/>
</el-menu>

View File

@@ -0,0 +1,343 @@
@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;
padding: 0 12px;
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;
}

View File

@@ -9,14 +9,6 @@ import {
getCurrentInstance,
ComputedRef
} from "vue";
import { RouteConfigs, relativeStorageType, tagsViewsType } from "../../types";
import { emitter } from "/@/utils/mitt";
import { templateRef } from "@vueuse/core";
import { handleAliveRoute } from "/@/router";
import { storageLocal } from "/@/utils/storage";
import { useRoute, useRouter } from "vue-router";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { toggleClass, removeClass, hasClass } from "/@/utils/operate";
import close from "/@/assets/svg/close.svg";
import refresh from "/@/assets/svg/refresh.svg";
@@ -25,27 +17,173 @@ import closeLeft from "/@/assets/svg/close_left.svg";
import closeOther from "/@/assets/svg/close_other.svg";
import closeRight from "/@/assets/svg/close_right.svg";
let refreshButton = "refresh-button";
const instance = getCurrentInstance();
import { emitter } from "/@/utils/mitt";
import { isEqual, isEmpty } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import { storageLocal } from "/@/utils/storage";
import { useRoute, useRouter } from "vue-router";
import { RouteConfigs, tagsViewsType } from "../../types";
import { useSettingStoreHook } from "/@/store/modules/settings";
import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { toggleClass, removeClass, hasClass } from "/@/utils/operate";
import { templateRef, useResizeObserver, useDebounceFn } from "@vueuse/core";
// 响应式storage
let relativeStorage: relativeStorageType;
const route = useRoute();
const router = useRouter();
const translateX = ref<number>(0);
const activeIndex = ref<number>(-1);
let refreshButton = "refresh-button";
const instance = getCurrentInstance();
const pureSetting = useSettingStoreHook();
const showTags = ref(storageLocal.getItem("tagsVal") || false);
const tabDom = templateRef<HTMLElement | null>("tabDom", null);
const containerDom = templateRef<HTMLElement | null>("containerDom", null);
const activeIndex = ref(-1);
let routerArrays: Array<RouteConfigs> = [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
icon: "el-icon-s-home",
showLink: true
const scrollbarDom = templateRef<HTMLElement | null>("scrollbarDom", null);
let multiTags: ComputedRef<Array<RouteConfigs>> = computed(() => {
return useMultiTagsStoreHook()?.multiTags;
});
const linkIsActive = computed(() => {
return item => {
if (Object.keys(route.query).length === 0) {
if (route.path === item.path) {
return "is-active";
} else {
return "";
}
} else {
if (isEqual(route?.query, item?.query)) {
return "is-active";
} else {
return "";
}
}
};
});
const scheduleIsActive = computed(() => {
return item => {
if (Object.keys(route.query).length === 0) {
if (route.path === item.path) {
return "schedule-active";
} else {
return "";
}
} else {
if (isEqual(route?.query, item?.query)) {
return "schedule-active";
} else {
return "";
}
}
};
});
const iconIsActive = computed(() => {
return (item, index) => {
if (index === 0) return;
if (Object.keys(route.query).length === 0) {
if (route.path === item.path) {
return true;
} else {
return false;
}
} else {
if (isEqual(route?.query, item?.query)) {
return true;
} else {
return false;
}
}
};
});
const dynamicTagView = () => {
const index = multiTags.value.findIndex(item => {
return item.path === route.path;
});
moveToView(index);
};
watch([route], () => {
activeIndex.value = -1;
dynamicTagView();
});
useResizeObserver(
scrollbarDom,
useDebounceFn(() => {
dynamicTagView();
}, 200)
);
const tabNavPadding = 10;
const moveToView = (index: number): void => {
if (!instance.refs["dynamic" + index]) {
return;
}
const tabItemEl = instance.refs["dynamic" + index];
const tabItemElOffsetLeft = (tabItemEl as HTMLElement).offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement).offsetWidth;
// 标签页导航栏可视长度(不包含溢出部分)
const scrollbarDomWidth = scrollbarDom.value
? scrollbarDom.value.offsetWidth
: 0;
// 已有标签页总长度(包含溢出部分)
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
translateX.value = 0;
} else if (tabItemElOffsetLeft < -translateX.value) {
// 标签在可视区域左侧
translateX.value = -tabItemElOffsetLeft + tabNavPadding;
} else if (
tabItemElOffsetLeft > -translateX.value &&
tabItemElOffsetLeft + tabItemOffsetWidth <
-translateX.value + scrollbarDomWidth
) {
// 标签在可视区域
translateX.value = Math.min(
0,
scrollbarDomWidth -
tabItemOffsetWidth -
tabItemElOffsetLeft -
tabNavPadding
);
} else {
// 标签在可视区域右侧
translateX.value = -(
tabItemElOffsetLeft -
(scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth)
);
}
};
const handleScroll = (offset: number): void => {
const scrollbarDomWidth = scrollbarDom.value
? scrollbarDom.value?.offsetWidth
: 0;
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
if (offset > 0) {
translateX.value = Math.min(0, translateX.value + offset);
} else {
if (scrollbarDomWidth < tabDomWidth) {
if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
translateX.value = Math.max(
translateX.value + offset,
scrollbarDomWidth - tabDomWidth
);
}
} else {
translateX.value = 0;
}
}
];
};
const tagsViews = ref<Array<tagsViewsType>>([
{
icon: refresh,
@@ -58,41 +196,38 @@ const tagsViews = ref<Array<tagsViewsType>>([
icon: close,
text: "message.hscloseCurrentTab",
divided: false,
disabled: routerArrays.length > 1 ? false : true,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: closeLeft,
text: "message.hscloseLeftTabs",
divided: true,
disabled: routerArrays.length > 1 ? false : true,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: closeRight,
text: "message.hscloseRightTabs",
divided: false,
disabled: routerArrays.length > 1 ? false : true,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: closeOther,
text: "message.hscloseOtherTabs",
divided: true,
disabled: routerArrays.length > 2 ? false : true,
disabled: multiTags.value.length > 2 ? false : true,
show: true
},
{
icon: closeAll,
text: "message.hscloseAllTabs",
divided: false,
disabled: routerArrays.length > 1 ? false : true,
disabled: multiTags.value.length > 1 ? false : true,
show: true
}
]);
const dynamicTagList: ComputedRef<Array<RouteConfigs>> = computed(() => {
return relativeStorage.routesInStorage;
});
// 显示模式,默认灵动模式显示
const showModel = ref(storageLocal.getItem("showModel") || "smart");
@@ -108,7 +243,7 @@ let buttonTop = ref(0);
let currentSelect = ref({});
function dynamicRouteTag(value: string, parentPath: string): void {
const hasValue = relativeStorage.routesInStorage.some((item: any) => {
const hasValue = multiTags.value.some(item => {
return item.path === value;
});
@@ -117,12 +252,12 @@ function dynamicRouteTag(value: string, parentPath: string): void {
arr.forEach((arrItem: any) => {
let pathConcat = parentPath + arrItem.path;
if (arrItem.path === value || pathConcat === value) {
routerArrays.push({
useMultiTagsStoreHook().handleTags("push", {
path: value,
parentPath: `/${parentPath.split("/")[1]}`,
meta: arrItem.meta
meta: arrItem.meta,
name: arrItem.name
});
relativeStorage.routesInStorage = routerArrays;
} else {
if (arrItem.children && arrItem.children.length > 0) {
concatPath(arrItem.children, value, parentPath);
@@ -137,9 +272,10 @@ function dynamicRouteTag(value: string, parentPath: string): void {
// 重新加载
function onFresh() {
toggleClass(true, refreshButton, document.querySelector(".rotate"));
const { fullPath } = unref(route);
const { fullPath, query } = unref(route);
router.replace({
path: "/redirect" + fullPath
path: "/redirect" + fullPath,
query: query
});
setTimeout(() => {
removeClass(document.querySelector(".rotate"), refreshButton);
@@ -147,32 +283,44 @@ function onFresh() {
}
function deleteDynamicTag(obj: any, current: any, tag?: string) {
let valueIndex: number = routerArrays.findIndex((item: any) => {
return item.path === obj.path;
// 存放被删除的缓存路由
let delAliveRouteList = [];
let valueIndex: number = multiTags.value.findIndex((item: any) => {
if (item.query) {
if (item.path === obj.path) {
return item.query === obj.query;
}
} else {
return item.path === obj.path;
}
});
const spliceRoute = (start?: number, end?: number, other?: boolean): void => {
const spliceRoute = (
startIndex?: number,
length?: number,
other?: boolean
): void => {
if (other) {
relativeStorage.routesInStorage = [
useMultiTagsStoreHook().handleTags("equal", [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
i18n: true,
icon: "el-icon-s-home",
showLink: true
}
},
obj
];
routerArrays = relativeStorage.routesInStorage;
]);
} else {
routerArrays.splice(start, end);
relativeStorage.routesInStorage = routerArrays;
// @ts-ignore
delAliveRouteList = useMultiTagsStoreHook().handleTags("splice", "", {
startIndex,
length
});
}
router.push(obj.path);
// 删除缓存路由
handleAliveRoute(route.matched, "delete");
};
if (tag === "other") {
@@ -180,20 +328,37 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
} else if (tag === "left") {
spliceRoute(1, valueIndex - 1);
} else if (tag === "right") {
spliceRoute(valueIndex + 1, routerArrays.length);
spliceRoute(valueIndex + 1, multiTags.value.length);
} else {
// 从当前匹配到的路径中删除
spliceRoute(valueIndex, 1);
}
if (current === obj.path) {
let newRoute = useMultiTagsStoreHook().handleTags("slice");
if (current === route.path) {
// 删除缓存路由
tag
? delAliveRoutes(delAliveRouteList)
: handleAliveRoute(route.matched, "delete");
// 如果删除当前激活tag就自动切换到最后一个tag
let newRoute: any = routerArrays.slice(-1);
if (tag === "left") return;
nextTick(() => {
router.push({
path: newRoute[0].path
path: newRoute[0].path,
query: newRoute[0].query
});
});
} else {
// 删除缓存路由
tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
if (!multiTags.value.length) return;
let isHasActiveTag = multiTags.value.some(item => {
return item.path === route.path;
});
!isHasActiveTag &&
router.push({
path: newRoute[0].path,
query: newRoute[0].query
});
}
}
@@ -203,6 +368,19 @@ function deleteMenu(item, tag?: string) {
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
if (item && item.disabled) return;
let selectTagRoute;
if (selectRoute) {
selectTagRoute = {
path: selectRoute.path,
meta: selectRoute.meta,
name: selectRoute.name,
query: selectRoute.query
};
} else {
selectTagRoute = { path: route.path, meta: route.meta };
}
// 当前路由信息
switch (key) {
case 0:
@@ -211,57 +389,32 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
break;
case 1:
// 关闭当前标签页
selectRoute
? deleteMenu({ path: selectRoute.path, meta: selectRoute.meta })
: deleteMenu({ path: route.path, meta: route.meta });
deleteMenu(selectTagRoute);
break;
case 2:
// 关闭左侧标签页
selectRoute
? deleteMenu(
{
path: selectRoute.path,
meta: selectRoute.meta
},
"left"
)
: deleteMenu({ path: route.path, meta: route.meta }, "left");
deleteMenu(selectTagRoute, "left");
break;
case 3:
// 关闭右侧标签页
selectRoute
? deleteMenu(
{
path: selectRoute.path,
meta: selectRoute.meta
},
"right"
)
: deleteMenu({ path: route.path, meta: route.meta }, "right");
deleteMenu(selectTagRoute, "right");
break;
case 4:
// 关闭其他标签页
selectRoute
? deleteMenu(
{
path: selectRoute.path,
meta: selectRoute.meta
},
"other"
)
: deleteMenu({ path: route.path, meta: route.meta }, "other");
deleteMenu(selectTagRoute, "other");
break;
case 5:
// 关闭全部标签页
routerArrays.splice(1, routerArrays.length);
relativeStorage.routesInStorage = routerArrays;
useMultiTagsStoreHook().handleTags("splice", "", {
startIndex: 1,
length: multiTags.value.length
});
usePermissionStoreHook().clearAllCachePage();
router.push("/welcome");
break;
}
setTimeout(() => {
showMenuModel(route.fullPath);
showMenuModel(route.fullPath, route.query);
});
}
@@ -274,31 +427,44 @@ function closeMenu() {
visible.value = false;
}
function showMenus(value: Boolean) {
function showMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews.value[v].show = value;
});
}
function disabledMenus(value: Boolean) {
function disabledMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews.value[v].disabled = value;
});
}
// 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页
function showMenuModel(currentPath: string, refresh = false) {
let allRoute = unref(relativeStorage.routesInStorage);
let routeLength = unref(relativeStorage.routesInStorage).length;
// currentIndex为1时左侧的菜单是首页则不显示关闭左侧标签页
let currentIndex = allRoute.findIndex(v => v.path === currentPath);
// 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页
function showMenuModel(
currentPath: string,
query: object = {},
refresh = false
) {
let allRoute = multiTags.value;
let routeLength = multiTags.value.length;
let currentIndex = -1;
if (isEmpty(query)) {
currentIndex = allRoute.findIndex(v => v.path === currentPath);
} else {
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
}
showMenus(true);
if (refresh) {
tagsViews.value[0].show = true;
}
/**
* currentIndex为1时左侧的菜单是首页则不显示关闭左侧标签页
* 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页
*/
if (currentIndex === 1 && routeLength !== 2) {
// 左侧的菜单是首页,右侧存在别的菜单
tagsViews.value[2].show = false;
@@ -337,10 +503,10 @@ function openMenu(tag, e) {
} else if (route.path !== tag.path) {
// 右键菜单不匹配当前路由,隐藏刷新
tagsViews.value[0].show = false;
showMenuModel(tag.path);
showMenuModel(tag.path, tag.query);
} else if (
// eslint-disable-next-line no-dupe-else-if
relativeStorage.routesInStorage.length === 2 &&
multiTags.value.length === 2 &&
route.path !== tag.path
) {
showMenus(true);
@@ -348,7 +514,7 @@ function openMenu(tag, e) {
tagsViews.value[4].show = false;
} else if (route.path === tag.path) {
// 右键当前激活的菜单
showMenuModel(tag.path, true);
showMenuModel(tag.path, tag.query, true);
}
currentSelect.value = tag;
@@ -362,7 +528,9 @@ function openMenu(tag, e) {
} else {
buttonLeft.value = left;
}
buttonTop.value = e.clientY + 10;
pureSetting.hiddenSideBar
? (buttonTop.value = e.clientY)
: (buttonTop.value = e.clientY - 40);
setTimeout(() => {
visible.value = true;
}, 10);
@@ -370,7 +538,11 @@ function openMenu(tag, e) {
// 触发tags标签切换
function tagOnClick(item) {
showMenuModel(item.path);
router.push({
path: item?.path,
query: item?.query
});
showMenuModel(item?.path, item?.query);
}
// 鼠标移入
@@ -414,8 +586,6 @@ watch(
onBeforeMount(() => {
if (!instance) return;
relativeStorage = instance.appContext.app.config.globalProperties.$storage;
routerArrays = relativeStorage.routesInStorage ?? routerArrays;
// 根据当前路由初始化操作标签页的禁用状态
showMenuModel(route.fullPath);
@@ -443,38 +613,51 @@ onBeforeMount(() => {
<template>
<div ref="containerDom" class="tags-view" v-if="!showTags">
<el-scrollbar wrap-class="scrollbar-wrapper" class="scroll-container">
<i class="ri-arrow-left-s-line" @click="handleScroll(200)"></i>
<div ref="scrollbarDom" class="scroll-container">
<div
v-for="(item, index) in dynamicTagList"
:key="index"
:ref="'dynamic' + index"
:class="[
'scroll-item is-closable',
$route.path === item.path ? 'is-active' : '',
$route.path === item.path && showModel === 'card' ? 'card-active' : ''
]"
@contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(item, index)"
@mouseleave.prevent="onMouseleave(item, index)"
class="tab"
ref="tabDom"
:style="{ transform: `translateX(${translateX}px)` }"
>
<router-link :to="item.path" @click="tagOnClick(item)">{{
$t(item.meta.title)
}}</router-link>
<span
v-if="
($route.path === item.path && index !== 0) ||
(index === activeIndex && index !== 0)
"
class="el-icon-close"
@click="deleteMenu(item)"
></span>
<div
:ref="'schedule' + index"
v-if="showModel !== 'card'"
:class="[$route.path === item.path ? 'schedule-active' : '']"
></div>
:ref="'dynamic' + index"
v-for="(item, index) in multiTags"
:key="index"
:class="[
'scroll-item is-closable',
linkIsActive(item),
$route.path === item.path && showModel === 'card'
? 'card-active'
: ''
]"
@contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(item, index)"
@mouseleave.prevent="onMouseleave(item, index)"
@click="tagOnClick(item)"
>
<router-link :to="item.path"
>{{ transformI18n(item.meta.title, item.meta.i18n) }}
</router-link>
<el-icon
v-if="
iconIsActive(item, index) ||
(index === activeIndex && index !== 0)
"
class="el-icon-close"
@click.stop="deleteMenu(item)"
>
<CloseBold />
</el-icon>
<div
:ref="'schedule' + index"
v-if="showModel !== 'card'"
:class="[scheduleIsActive(item)]"
></div>
</div>
</div>
</el-scrollbar>
</div>
<i class="ri-arrow-right-s-line" @click="handleScroll(-200)"></i>
<!-- 右键菜单按钮 -->
<transition name="el-zoom-in-top">
<ul
@@ -498,15 +681,19 @@ onBeforeMount(() => {
<!-- 右侧功能按钮 -->
<ul class="right-button">
<li>
<i
<el-icon
:title="$t('message.hsrefreshRoute')"
class="el-icon-refresh-right rotate"
@click="onFresh"
></i>
>
<RefreshRight />
</el-icon>
</li>
<li>
<el-dropdown trigger="click" placement="bottom-end">
<i class="el-icon-arrow-down"></i>
<el-icon>
<ArrowDown />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@@ -531,277 +718,5 @@ onBeforeMount(() => {
</template>
<style lang="scss" scoped>
@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);
}
}
.tags-view {
width: 100%;
font-size: 14px;
display: flex;
box-shadow: 0 0 1px #888;
.scroll-item {
border-radius: 3px 3px 0 0;
padding: 2px 6px;
display: inline-block;
position: relative;
margin-right: 4px;
height: 28px;
line-height: 25px;
transition: all 0.4s;
.el-icon-close {
font-size: 10px;
color: #1890ff;
cursor: pointer;
&:hover {
border-radius: 50%;
color: #fff;
background: #b4bccc;
font-size: 14px;
}
}
&.is-closable:not(:first-child) {
&:hover {
padding-right: 8px;
}
}
}
a {
text-decoration: none;
color: #666;
padding: 0 4px 0 4px;
}
.scroll-container {
padding: 5px 0;
white-space: nowrap;
position: relative;
width: 100%;
background: #fff;
.scroll-item {
&:nth-child(1) {
margin-left: 5px;
}
}
.scrollbar-wrapper {
position: absolute;
height: 40px;
overflow-x: hidden !important;
}
}
// 右键菜单
.contextmenu {
margin: 0;
background: #fff;
z-index: 3000;
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;
}
}
}
}
.right-button {
display: flex;
align-items: center;
background: #fff;
font-size: 16px;
li {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
border-right: 1px solid #ccc;
cursor: pointer;
}
}
.el-dropdown-menu {
padding: 0;
li {
width: 100%;
margin: 0;
padding: 0 12px;
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;
}
.is-active {
background-color: #eaf4fe;
position: relative;
color: #fff;
a {
color: #1890ff;
}
}
// 卡片模式
.card-active {
border: 1px solid #1890ff;
}
// 卡片模式下鼠标移入显示蓝色边框
.card-in {
border: 1px solid #1890ff;
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;
}
@import "./index.scss";
</style>

View File

@@ -1,53 +1,23 @@
<script lang="ts">
import { routerArrays } from "./types";
export default {
computed: {
layout() {
if (!this.$storage.layout) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.$storage.layout = { layout: "vertical-dark" };
}
if (
!this.$storage.routesInStorage ||
this.$storage.routesInStorage.length === 0
) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.$storage.routesInStorage = routerArrays;
}
if (!this.$storage.locale) {
// eslint-disable-next-line
this.$storage.locale = { locale: "zh" };
useI18n().locale.value = "zh";
}
return this.$storage?.layout.layout;
}
}
};
</script>
<script setup lang="ts">
import {
ref,
unref,
h,
reactive,
computed,
onMounted,
watchEffect,
useCssModule,
onBeforeMount,
defineComponent,
getCurrentInstance
} from "vue";
import { setType } from "./types";
import options from "/@/settings";
import { useI18n } from "vue-i18n";
import { routerArrays } from "./types";
import { emitter } from "/@/utils/mitt";
import { toggleClass } from "/@/utils/operate";
import { useEventListener } from "@vueuse/core";
import { storageLocal } from "/@/utils/storage";
import backTop from "/@/assets/svg/back_top.svg";
import { useAppStoreHook } from "/@/store/modules/app";
import fullScreen from "/@/assets/svg/full_screen.svg";
import exitScreen from "/@/assets/svg/exit_screen.svg";
import { deviceDetection } from "/@/utils/deviceDetection";
import { useSettingStoreHook } from "/@/store/modules/settings";
import { useMultiTagsStore } from "/@/store/modules/multiTags";
import navbar from "./components/navbar.vue";
import tag from "./components/tag/index.vue";
@@ -56,13 +26,46 @@ import setting from "./components/setting/index.vue";
import Vertical from "./components/sidebar/vertical.vue";
import Horizontal from "./components/sidebar/horizontal.vue";
const isMobile = deviceDetection();
const pureSetting = useSettingStoreHook();
const { hiddenMainContainer } = useCssModule();
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const instance =
getCurrentInstance().appContext.app.config.globalProperties.$storage;
let containerHiddenSideBar = ref(options.hiddenSideBar);
// 清空缓存后从serverConfig.json读取默认配置并赋值到storage中
const layout = computed(() => {
// 路由
if (
useMultiTagsStore().multiTagsCache &&
(!instance.$storage.tags || instance.$storage.tags.length === 0)
) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.tags = routerArrays;
}
// 国际化
if (!instance.$storage.locale) {
// eslint-disable-next-line
instance.$storage.locale = { locale: instance.$config?.Locale ?? "zh" };
useI18n().locale.value = instance.$config?.Locale ?? "zh";
}
// 导航
if (!instance.$storage.layout) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.layout = {
layout: instance.$config?.Layout ?? "vertical",
theme: instance.$config?.Theme ?? "default"
};
}
// 灰色模式、色弱模式、隐藏标签页
if (!instance.$storage.sets) {
// eslint-disable-next-line
instance.$storage.sets = {
grey: instance.$config?.Grey ?? false,
weak: instance.$config?.Weak ?? false,
hideTabs: instance.$config?.HideTabs ?? false,
multiTagsCache: instance.$config?.MultiTagsCache ?? false
};
}
return instance.$storage?.layout.layout;
});
const set: setType = reactive({
sidebar: computed(() => {
@@ -84,81 +87,108 @@ const set: setType = reactive({
withoutAnimation: set.sidebar.withoutAnimation,
mobile: set.device === "mobile"
};
}),
hideTabs: computed(() => {
return instance.$storage?.sets.hideTabs;
})
});
const handleClickOutside = (params: boolean) => {
useAppStoreHook().closeSideBar({ withoutAnimation: params });
};
function setTheme(layoutModel: string) {
let { layout } = storageLocal.getItem("responsive-layout");
let theme = layout.match(/-(.*)/)[1];
window.document.body.setAttribute("data-layout", layoutModel);
window.document.body.setAttribute("data-theme", theme);
instance.layout = { layout: `${layoutModel}-${theme}` };
window.document.body.setAttribute("layout", layoutModel);
instance.$storage.layout = {
layout: `${layoutModel}`,
theme: instance.$storage.layout?.theme
};
}
function toggle(device: string, bool: boolean) {
useAppStoreHook().toggleDevice(device);
useAppStoreHook().toggleSideBar(bool, "resize");
}
// 判断是否可自动关闭菜单栏
let isAutoCloseSidebar = true;
// 监听容器
emitter.on("resize", ({ detail }) => {
if (isMobile) return;
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);
/** 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;
}
}
};
function onFullScreen() {
if (unref(containerHiddenSideBar)) {
containerHiddenSideBar.value = false;
toggleClass(
false,
hiddenMainContainer,
document.querySelector(".main-container")
);
} else {
containerHiddenSideBar.value = true;
toggleClass(
true,
hiddenMainContainer,
document.querySelector(".main-container")
);
}
}
onMounted(() => {
const isMobile = $_isMobile();
if (isMobile) {
useAppStoreHook().toggleDevice("mobile");
handleClickOutside(true);
}
toggleClass(
unref(containerHiddenSideBar),
hiddenMainContainer,
document.querySelector(".main-container")
);
});
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(
"div",
{
class: { "fixed-header": set.fixedHeader },
style: [
set.hideTabs && layout.value.includes("horizontal")
? "box-shadow: 0 1px 4px rgb(0 21 41 / 8%);"
: ""
]
},
{
default: () => [
!pureSetting.hiddenSideBar && layout.value.includes("vertical")
? h(navbar)
: h("div"),
!pureSetting.hiddenSideBar && layout.value.includes("horizontal")
? h(Horizontal)
: h("div"),
h(
tag,
{},
{
default: () => [
h(
"span",
{ onClick: onFullScreen },
{
default: () => [
!pureSetting.hiddenSideBar ? h(fullScreen) : h(exitScreen)
]
}
)
]
}
)
]
}
);
}
});
</script>
@@ -170,41 +200,39 @@ onBeforeMount(() => {
set.sidebar.opened &&
layout.includes('vertical')
"
class="drawer-bg"
@click="handleClickOutside(false)"
class="app-mask"
@click="useAppStoreHook().toggleSideBar()"
/>
<Vertical v-show="!containerHiddenSideBar && layout.includes('vertical')" />
<div class="main-container">
<div :class="{ 'fixed-header': set.fixedHeader }">
<!-- 顶部导航栏 -->
<navbar
v-show="!containerHiddenSideBar && layout.includes('vertical')"
/>
<!-- tabs标签页 -->
<Horizontal
v-show="!containerHiddenSideBar && layout.includes('horizontal')"
/>
<tag>
<span @click="onFullScreen">
<fullScreen v-if="!containerHiddenSideBar" />
<exitScreen v-else />
</span>
</tag>
<Vertical
v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')"
/>
<div
:class="[
'main-container',
pureSetting.hiddenSideBar ? 'main-hidden' : ''
]"
>
<div v-if="set.fixedHeader">
<layout-header />
<!-- 主体内容 -->
<app-main :fixed-header="set.fixedHeader" />
</div>
<!-- 主体内容 -->
<app-main />
<el-scrollbar v-else>
<el-backtop
title="回到顶部"
target=".main-container .el-scrollbar__wrap"
><backTop />
</el-backtop>
<layout-header />
<!-- 主体内容 -->
<app-main :fixed-header="set.fixedHeader" />
</el-scrollbar>
</div>
<!-- 系统设置 -->
<setting />
</div>
</template>
<style scoped module>
.hiddenMainContainer {
margin-left: 0 !important;
}
</style>
<style lang="scss" scoped>
@mixin clearfix {
&::after {
@@ -227,7 +255,11 @@ onBeforeMount(() => {
}
}
.drawer-bg {
.main-hidden {
margin-left: 0 !important;
}
.app-mask {
background: #000;
opacity: 0.3;
width: 100%;
@@ -237,19 +269,6 @@ onBeforeMount(() => {
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - 210px);
transition: width 0.28s;
}
.mobile .fixed-header {
width: 100%;
}
.re-screen {
margin-top: 12px;
}

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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
/* 绿宝石 */
$subMenuActiveText: #fff;
$menuBg: #032121;
$menuHover: #59bfc1;
$subMenuBg: #000;
$subMenuActiveBg: #59bfc1;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #053434;
$menuTitleHover: #fff;
$menuActiveBefore: #59bfc1;

View File

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

View File

@@ -0,0 +1,11 @@
/* 深紫罗兰色 */
$subMenuActiveText: #fff;
$menuBg: #130824;
$menuHover: #693ac9;
$subMenuBg: #000;
$subMenuActiveBg: #693ac9;
$navTextColor: #7a80b4;
$menuText: #7a80b4;
$sidebarLogo: #1f0c38;
$menuTitleHover: #fff;
$menuActiveBefore: #693ac9;

View File

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

View File

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

View File

@@ -1,36 +1,49 @@
export const routerArrays: Array<RouteConfigs> = [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
i18n: true,
icon: "el-icon-s-home",
showLink: true
}
}
];
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;
text: string;
divided: {
valueOf: () => boolean;
};
disabled: {
valueOf: () => boolean;
};
show: {
valueOf: () => boolean;
};
divided: boolean;
disabled: boolean;
show: boolean;
};
export interface setType {
sidebar: {
opened: boolean;
withoutAnimation: boolean;
isClickHamburger: boolean;
};
device: string;
fixedHeader: boolean;
@@ -40,16 +53,33 @@ export interface setType {
withoutAnimation: boolean;
mobile: boolean;
};
hideTabs: boolean;
}
export const routerArrays: Array<RouteConfigs> = [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
icon: "el-icon-s-home",
showLink: true
}
}
];
export type childrenType = {
path?: string;
noShowingChildren?: boolean;
children?: childrenType[];
value: unknown;
meta?: {
icon?: string;
title?: string;
i18n?: boolean;
extraIcon?: {
svg?: boolean;
name?: string;
};
};
showTooltip?: boolean;
};
export type themeColorsType = {
rgb: string;
themeColor: string;
};
export interface scrollbarDomType extends HTMLElement {
wrap?: {
offsetWidth: number;
};
}

View File

@@ -4,7 +4,9 @@ import { setupStore } from "/@/store";
import { getServerConfig } from "./config";
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";
@@ -27,7 +29,13 @@ Object.keys(directives).forEach(key => {
getServerConfig(app).then(async config => {
injectResponsiveStorage(app, config);
setupStore(app);
app.use(router).use(useElementPlus).use(useTable).use(usI18n);
app
.use(router)
.use(MotionPlugin)
.use(useElementPlus)
.use(useTable)
.use(usI18n)
.use(useFontawesome);
await router.isReady();
app.mount("#app");
});

View File

@@ -23,19 +23,35 @@ import {
ElInput,
ElForm,
ElFormItem,
ElLoading,
ElPopover,
ElPopper,
ElTooltip,
ElDrawer,
ElPagination,
ElAlert,
ElRadio,
ElRadioButton,
ElRadioGroup,
ElDescriptions,
ElDescriptionsItem
ElDescriptionsItem,
ElBacktop,
ElSwitch,
ElBadge,
ElTabs,
ElTabPane,
ElAvatar,
ElEmpty,
ElCollapse,
ElCollapseItem,
ElTreeV2,
// 指令
ElLoading,
ElInfiniteScroll
} from "element-plus";
// Directives
const plugins = [ElLoading, ElInfiniteScroll];
const components = [
ElTag,
ElAffix,
@@ -66,19 +82,72 @@ const components = [
ElDrawer,
ElPagination,
ElAlert,
ElRadio,
ElRadioButton,
ElRadioGroup,
ElDescriptions,
ElDescriptionsItem
ElDescriptionsItem,
ElBacktop,
ElSwitch,
ElBadge,
ElTabs,
ElTabPane,
ElAvatar,
ElEmpty,
ElCollapse,
ElCollapseItem,
ElTreeV2
];
const plugins = [ElLoading];
// https://element-plus.org/zh-CN/component/icon.html
import {
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold,
Bell
} from "@element-plus/icons-vue";
// Icon
export const iconComponents = [
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold,
Bell
];
export function useElementPlus(app: App) {
// 注册组件
components.forEach((component: Component) => {
app.component(component.name, component);
});
// 注册指令
plugins.forEach(plugin => {
app.use(plugin);
});
// 注册图标
iconComponents.forEach((component: Component) => {
app.component(component.name, component);
});
}

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

@@ -42,6 +42,8 @@ export const menusConfig = {
permission: "权限管理",
permissionPage: "页面权限",
permissionButton: "按钮权限",
hstabs: "标签页操作",
hsMenuTree: "菜单树结构",
externalLink: "外链"
}
},
@@ -78,6 +80,8 @@ export const menusConfig = {
permission: "Permission Manage",
permissionPage: "Page Permission",
permissionButton: "Button Permission",
hstabs: "Tabs Operate",
hsMenuTree: "Menu Tree",
externalLink: "External Link"
}
}
@@ -92,14 +96,13 @@ export const buttonConfig = {
hsexitfullscreen: "退出全屏",
hsrefreshRoute: "刷新路由",
hslogin: "登陆",
hsregister: "注册",
hsadd: "新增",
hsmark: "标记/取消",
hssave: "保存",
hssearch: "搜索",
hsexpendAll: "全部展开",
hscollapseAll: "全部折叠",
hssystemSet: "系统设置",
hssystemSet: "打开项目配置",
hsdelete: "删除",
hsreload: "重新加载",
hscloseCurrentTab: "关闭当前标签页",
@@ -116,14 +119,13 @@ export const buttonConfig = {
hsexitfullscreen: "exitFullscreen",
hsrefreshRoute: "refreshRoute",
hslogin: "login",
hsregister: "register",
hsadd: "Add",
hsmark: "Mark/Cancel",
hssave: "Save",
hssearch: "Search",
hsexpendAll: "Expand All",
hscollapseAll: "Collapse All",
hssystemSet: "System Set",
hssystemSet: "Open ProjectConfig",
hsdelete: "Delete",
hsreload: "Reload",
hscloseCurrentTab: "Close CurrentTab",

View File

@@ -13,3 +13,27 @@ export const i18n = createI18n({
export function usI18n(app: App) {
app.use(i18n);
}
/**
* 国际化转换工具函数
* @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;
}
}

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,
@@ -51,7 +50,7 @@ import {
VXETable.setup({
size: "medium",
version: 0,
zIndex: 100,
zIndex: 1002,
table: {
// 自动监听父元素的变化去重新计算表格
autoResize: true,

View File

@@ -1,129 +1,27 @@
import {
Router,
createRouter,
RouteComponent,
createWebHashHistory,
RouteRecordNormalized
} from "vue-router";
import { split } 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 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) {
@@ -139,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", "/register"];
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");
@@ -197,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 则是超链接 反之是普通路由
@@ -212,23 +72,74 @@ 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?.realPath) {
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
) {
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", newLocalRoutes);
}
}
router.push(to.fullPath);
});
next();
}

View File

@@ -4,10 +4,11 @@ const componentsRouter = {
path: "/components",
name: "components",
component: Layout,
redirect: "/components/split-pane",
redirect: "/components/video",
meta: {
icon: "el-icon-menu",
icon: "Menu",
title: "message.hscomponents",
i18n: true,
showLink: true,
rank: 4
},
@@ -18,7 +19,8 @@ const componentsRouter = {
component: () => import("/@/views/components/video/index.vue"),
meta: {
title: "message.hsvideo",
showLink: true
showLink: true,
i18n: true
}
},
{
@@ -29,6 +31,7 @@ const componentsRouter = {
title: "message.hsmap",
showLink: true,
keepAlive: true,
i18n: true,
transition: {
name: "fade"
}
@@ -41,6 +44,7 @@ const componentsRouter = {
meta: {
title: "message.hsdraggable",
showLink: true,
i18n: true,
transition: {
enterTransition: "animate__zoomIn",
leaveTransition: "animate__zoomOut"
@@ -55,6 +59,7 @@ const componentsRouter = {
meta: {
title: "message.hssplitPane",
showLink: true,
i18n: true,
extraIcon: {
svg: true,
name: "team-iconxinpinrenqiwang"
@@ -67,6 +72,7 @@ const componentsRouter = {
component: () => import("/@/views/components/button/index.vue"),
meta: {
title: "message.hsbutton",
i18n: true,
showLink: true
}
},
@@ -76,6 +82,7 @@ const componentsRouter = {
component: () => import("/@/views/components/cropping/index.vue"),
meta: {
title: "message.hscropping",
i18n: true,
showLink: true
}
},
@@ -85,6 +92,7 @@ const componentsRouter = {
component: () => import("/@/views/components/count-to/index.vue"),
meta: {
title: "message.hscountTo",
i18n: true,
showLink: true
}
},
@@ -94,6 +102,7 @@ const componentsRouter = {
component: () => import("/@/views/components/selector/index.vue"),
meta: {
title: "message.hsselector",
i18n: true,
showLink: true
}
},
@@ -103,6 +112,7 @@ const componentsRouter = {
component: () => import("/@/views/components/seamless-scroll/index.vue"),
meta: {
title: "message.hsseamless",
i18n: true,
showLink: true
}
},
@@ -112,6 +122,7 @@ const componentsRouter = {
component: () => import("/@/views/components/contextmenu/index.vue"),
meta: {
title: "message.hscontextmenu",
i18n: true,
showLink: true
}
}

View File

@@ -6,8 +6,9 @@ const editorRouter = {
component: Layout,
redirect: "/editor/index",
meta: {
icon: "el-icon-edit-outline",
icon: "Edit",
title: "message.hseditor",
i18n: true,
showLink: true,
rank: 2
},
@@ -19,6 +20,7 @@ const editorRouter = {
meta: {
title: "message.hseditor",
showLink: true,
i18n: true,
keepAlive: true,
extraIcon: {
svg: true,

View File

@@ -6,9 +6,10 @@ const errorRouter = {
component: Layout,
redirect: "/error/401",
meta: {
icon: "el-icon-position",
icon: "Position",
title: "message.hserror",
showLink: true,
i18n: true,
rank: 7
},
children: [
@@ -18,6 +19,7 @@ const errorRouter = {
component: () => import("/@/views/error/401.vue"),
meta: {
title: "message.hsfourZeroOne",
i18n: true,
showLink: true
}
},
@@ -27,6 +29,7 @@ const errorRouter = {
component: () => import("/@/views/error/404.vue"),
meta: {
title: "message.hsfourZeroFour",
i18n: true,
showLink: true
}
}

View File

@@ -5,18 +5,19 @@ const externalLink = {
name: "external",
component: Layout,
meta: {
icon: "el-icon-link",
icon: "Link",
title: "message.externalLink",
showLink: true,
i18n: true,
rank: 190
},
children: [
{
path: "https://github.com/xiaoxian521/vue-pure-admin",
meta: {
icon: "el-icon-link",
title: "message.externalLink",
showLink: true,
i18n: true,
rank: 191
}
}

View File

@@ -6,9 +6,10 @@ const flowChartRouter = {
component: Layout,
redirect: "/flowChart/index",
meta: {
icon: "el-icon-set-up",
icon: "SetUp",
title: "message.hsflowChart",
showLink: true,
i18n: true,
rank: 1
},
children: [
@@ -18,6 +19,7 @@ const flowChartRouter = {
component: () => import("/@/views/flow-chart/index.vue"),
meta: {
title: "message.hsflowChart",
i18n: true,
showLink: true
}
}

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