Compare commits

..

70 Commits

Author SHA1 Message Date
Soybean
e6aa25e9f8 chore(projects): release v1.3.4 2024-08-01 16:02:56 +08:00
Soybean
968370b1aa chore(projects): update deps & fix vue-router type 2024-08-01 02:18:22 +08:00
Soybean
cb1d44525d optimize(projects): optimize code 2024-07-31 23:11:50 +08:00
Soybean
939c512f02 refactor(projects)!: don't reset active menu of vertical-mix layout when it is mixSiderFixed 2024-07-31 23:11:16 +08:00
Soybean
3f23993a83 chore(projects): release v1.3.3 2024-07-30 18:03:26 +08:00
Soybean
ebc838c3e0 docs(projects): update CHANGELOG 2024-07-30 18:03:06 +08:00
Soybean
5646a50ddf fix(projects): fix watermark settings 2024-07-30 17:53:17 +08:00
Soybean
217a3bb259 chore(projects): release v1.3.2 2024-07-30 17:18:16 +08:00
Soybean
d37591dd7a docs(projects): update CHANGELOG 2024-07-30 17:17:54 +08:00
Soybean
cf67d55cbf optimize(projects): optimize response code comparison 2024-07-30 15:12:45 +08:00
Soybean
98b75c22c3 optimize(projects): remove home NAlert closable 2024-07-30 14:57:53 +08:00
Soybean
993e9caf79 chore(deps): update deps 2024-07-30 14:55:45 +08:00
Soybean
f26d0a61eb optimize(projects): add type WatermarkProps 2024-07-30 14:41:30 +08:00
Azir
eed617f9eb optimize(types): remove useless types. 2024-07-30 09:33:35 +08:00
paynezhuang
ea8aa6c4e6 feat(projects): add full screen watermark. close#571 (#573) 2024-07-29 23:04:26 +08:00
青菜白玉汤
4dde4c22b1 feat(projects): add color fading mode.close #567 (#569) 2024-07-28 23:00:22 +08:00
Soybean
59f07d8ac4 fix(projects): fix vertical-mix menu selected 2024-07-23 15:52:01 +08:00
Soybean
e8378318c6 chore(projects): release v1.3.1 2024-07-22 19:04:20 +08:00
Soybean
840273474f fix(projects): fix vertical-menu will not render when the layout is from mobile 2024-07-22 19:03:01 +08:00
Soybean
4e55b0e9bd fix(projects): fix HorizontalMixMenu inverted. fixed #563 2024-07-22 19:02:56 +08:00
Azir
5377002932 style(other): modify the Chinese name of the grayscale mode 2024-07-22 14:39:04 +08:00
青菜白玉汤
c469512bd4 fix(projects): fix the issue of abnormal width of the sidebar in the top menu mix and reverse mode (#562) 2024-07-22 11:39:19 +08:00
Soybean
613c836e0f docs(projects): update CHANGELOG 2024-07-22 00:28:40 +08:00
Soybean
f73f70c51b chore(projects): release v1.3.0 2024-07-22 00:26:38 +08:00
Soybean
a0b76daeff docs(projects): update CHANGELOG 2024-07-22 00:26:01 +08:00
Soybean
f6bd6b8603 chore(deps): update deps 2024-07-22 00:21:58 +08:00
Soybean
087e532613 refactor(projects)!: refactor global menu & support reversed-horizontal-mix-menu. close #365 2024-07-22 00:19:12 +08:00
Soybean
00f41dd25e refactor(projects): change css vars mount to root 2024-07-20 04:05:24 +08:00
Soybean
1d1b148c8f refactor(projects): combine theme tokens and theme settings. close #379 2024-07-20 04:00:32 +08:00
Soybean
958d0baf3f feat(packages): @sa/scripts: add ignore pattern list for command gitCommitVerify. close #504 2024-07-20 03:17:23 +08:00
Soybean
0206969e98 chore(projects): add script czh 2024-07-20 03:07:46 +08:00
Soybean
527fd79df2 feat(packages): @sa/axios: replace CancelTokenSource by AbortController. close #530, close #532 2024-07-20 03:04:23 +08:00
142vip.cn
06971f3935 feat(packages): @sa/scripts: command gitCommit support chinese (#548) 2024-07-20 02:45:31 +08:00
青菜白玉汤
f4eeb2ed63 fix(hooks): prevent program freezing when pagesize returns 0 (#545) 2024-07-20 02:45:25 +08:00
青菜白玉汤
793b16e9ba feat(projects): make branch main tiny & modify request retry times to 0 2024-07-20 02:44:32 +08:00
Soybean
cf9fc7d31f chore(projects): release v1.2.8 2024-07-20 02:27:55 +08:00
Soybean
58fc09623c docs(projects): update CHANGELOG 2024-07-20 02:27:35 +08:00
Soybean
276ea7fa2f fix(types): fix data type of useHookTable 2024-07-20 02:23:25 +08:00
Soybean
96c1044ffb fix(packages): @sa/hooks: fix searchParams of useHookTable. fixed #552 2024-07-20 02:18:58 +08:00
Soybean
a91335d74b refactor(projects): replace cloneDeep of lodash-es with klona 2024-07-20 02:14:20 +08:00
dodu2014
3e2a993db8 fix(types): Fixed the reference type error (#551) 2024-07-20 02:00:59 +08:00
Soybean
cf0192ad71 chore(deps): update deps 2024-07-20 02:00:21 +08:00
Soybean
a454b620f1 chore(projects): release v1.2.7 2024-07-12 09:27:51 +08:00
Soybean
752ec1e9e7 chore(deps): update deps 2024-07-12 00:46:56 +08:00
wynn-w
e035eab223 optimize(projects): supports custom menu icon sizes (#534) 2024-07-09 16:59:03 +08:00
Soybean
be13ca274b chore(deps): update deps 2024-07-08 11:33:56 +08:00
Soybean
72ede8bf9c chore(deps): update deps 2024-06-23 23:15:26 +08:00
paynezhuang
0d20e4c915 chore(projects): Fix deprecated configuration config (#524) 2024-06-23 15:26:00 +08:00
Soybean
c791adc6bc chore(projects): release v1.2.6 2024-06-21 10:14:05 +08:00
Soybean
693f704690 optimize(projects): optimize getRouteQueryOfLoginRoute 2024-06-21 10:07:44 +08:00
Soybean
49f60b2d50 fix(projects): fix login redirect to routeHome when routeHome of dynamic route is not same as static route. fixed #511 2024-06-20 19:51:55 +08:00
Soybean
425c69acc2 fix(projects): add getDataByPage for useTable. fixed #499 2024-06-20 19:27:39 +08:00
Soybean
f7de3fd0f1 fix(projects): request modal title use i18n. fixed #507 2024-06-20 19:15:46 +08:00
Soybean
c7f6f2a537 chore(deps): update deps 2024-06-20 19:11:40 +08:00
Soybean
531432d5ff chore(deps): update deps. close #510 2024-06-18 22:01:47 +08:00
Soybean
ff1d50461f ci(projects): update github issues template 2024-06-17 15:21:17 +08:00
Soybean
b5027c8f3e ci(projects): add github issues template 2024-06-17 15:10:24 +08:00
Soybean
1e67ae8c49 docs(projects): update README 2024-06-17 11:38:37 +08:00
Soybean
5c67d0650e docs(projects): update CHANGELOG 2024-06-15 01:12:35 +08:00
Soybean
af735e87b8 chore(projects): release v1.2.5 2024-06-15 01:11:55 +08:00
Soybean
b336841567 docs(projects): update CHANGELOG 2024-06-15 01:11:28 +08:00
Soybean
b094d6882b chore(deps): update deps 2024-06-15 01:03:23 +08:00
Soybean
b94baa18c1 optimize(projects): optimize code 2024-06-15 00:49:24 +08:00
青菜白玉汤
3eeace94dd fix(projects): Fix the issue of abnormal tab caching after logout. fixed #495 2024-06-15 00:39:53 +08:00
青菜白玉汤
ad2f24707a optimize(utils): Reduce code indentation and improve readability (#496) 2024-06-14 18:24:56 +08:00
青菜白玉汤
cd9d58d4de optimize(types): Enhance compatibility of global types (#494) 2024-06-14 18:11:01 +08:00
CHENZL
2198b982f0 perf(project): Initializing the static routing function does not require asynchronization (#493) 2024-06-14 11:47:16 +08:00
Soybean
376e2f7d84 chore(projects): release v1.2.4 2024-06-14 00:04:43 +08:00
Soybean
069fa8a817 optimize(projects): get buildTime with timezone 'Asia/Shanghai' 2024-06-14 00:00:30 +08:00
Soybean
b5a723cb5f optimize(projects): optimize setupAppVersionNotification 2024-06-13 23:44:41 +08:00
113 changed files with 3302 additions and 7068 deletions

View File

@@ -1,4 +1,4 @@
name: Bug提交
name: 🐞 Bug提交
description: 在使用软件或功能的过程中遇到了错误
title: '[Bug]: '
labels: [ "bug?" ]

View File

@@ -1,4 +1,4 @@
name: Bug Report
name: 🐞 Bug Report
description: Encountered an error while using the software or feature
title: '[Bug]: '
labels: [ "bug?" ]

View File

@@ -0,0 +1,48 @@
---
name: 🚀 功能请求
description: 提出一个想法以帮助我们改进W&B
title: "[功能]: "
labels:
- "功能请求"
body:
- type: markdown
attributes:
value: |
**感谢 :heart: 您花时间填写此功能请求报告!**
我们恳请您搜索看看您的功能是否[已经存在](https://github.com/soybeanjs/soybean-admin/issues?q=is%3Aissue+sort%3Acreated-desc+)。
我们也很乐意接受用户的贡献。有关更多详细信息,请参阅[此处](https://github.com/soybeanjs/soybean-admin/blob/main/README.zh_CN.md#%E5%A6%82%E4%BD%95%E8%B4%A1%E7%8C%AE)。
- type: textarea
attributes:
label: 描述
description: |
对您感兴趣的功能的清晰简洁的描述。
validations:
required: true
- type: textarea
attributes:
label: 建议的解决方案
description: |
描述您想要的解决方案。对您希望发生的事情的清晰简洁的描述。
validations:
required: true
- type: textarea
attributes:
label: 替代方案
description: |
描述您考虑过的替代方案。
对您考虑过的任何替代解决方案或功能的清晰简洁的描述。
validations:
required: false
- type: textarea
attributes:
label: 额外的上下文
description: |
在此处添加有关问题的其他上下文。
validations:
required: false

View File

@@ -0,0 +1,48 @@
---
name: 🚀 Feature Request
description: Suggest an idea to help us improve W&B
title: "[Feature]: "
labels:
- "feature_request"
body:
- type: markdown
attributes:
value: |
**Thanks :heart: for taking the time to fill out this feature request report!**
We kindly ask that you search to see if an issue [already exists](https://github.com/soybeanjs/soybean-admin/issues?q=is%3Aissue+sort%3Acreated-desc+) for your feature.
We are also happy to accept contributions from our users. For more details see [here](https://github.com/soybeanjs/soybean-admin/blob/main/README.md#how-to-contribute).
- type: textarea
attributes:
label: Description
description: |
A clear and concise description of the feature you're interested in.
validations:
required: true
- type: textarea
attributes:
label: Suggested Solution
description: |
Describe the solution you'd like. A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: |
Describe alternatives you've considered.
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
attributes:
label: Additional Context
description: |
Add any other context about the problem here.
validations:
required: false

View File

@@ -3,7 +3,7 @@
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"eslint.experimental.useFlatConfig": true,
"eslint.useFlatConfig": true,
"editor.formatOnSave": false,
"eslint.validate": ["html", "css", "scss", "json", "jsonc"],
"i18n-ally.displayLanguage": "zh-cn",

View File

@@ -1,6 +1,254 @@
# Changelog
## [v1.3.4](https://github.com/honghuangdc/soybean-admin/compare/v1.3.3...v1.3.4) (2024-08-01)
###    🚨 Breaking Changes
- **projects**: don't reset active menu of vertical-mix layout when it is mixSiderFixed &nbsp;-&nbsp; by @honghuangdc [<samp>(939c5)</samp>](https://github.com/honghuangdc/soybean-admin/commit/939c512)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: optimize code &nbsp;-&nbsp; by @honghuangdc [<samp>(cb1d4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cb1d445)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **projects**: update deps & fix vue-router type &nbsp;-&nbsp; by @honghuangdc [<samp>(96837)</samp>](https://github.com/honghuangdc/soybean-admin/commit/968370b)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;
## [v1.3.3](https://github.com/honghuangdc/soybean-admin/compare/v1.3.2...v1.3.3) (2024-07-30)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**: fix watermark settings &nbsp;-&nbsp; by @honghuangdc [<samp>(5646a)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5646a50)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(ebc83)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ebc838c)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;
## [v1.3.2](https://github.com/honghuangdc/soybean-admin/compare/v1.3.1...v1.3.2) (2024-07-30)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- add color fading mode.close #567 &nbsp;-&nbsp; by @Azir-11 in https://github.com/honghuangdc/soybean-admin/issues/569 and https://github.com/honghuangdc/soybean-admin/issues/567 [<samp>(4dde4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4dde4c2)
- add full screen watermark. close#571 &nbsp;-&nbsp; by @paynezhuang in https://github.com/honghuangdc/soybean-admin/issues/573 and https://github.com/honghuangdc/soybean-admin/issues/571 [<samp>(ea8aa)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ea8aa6c)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**: fix vertical-mix menu selected &nbsp;-&nbsp; by @honghuangdc [<samp>(59f07)</samp>](https://github.com/honghuangdc/soybean-admin/commit/59f07d8)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**:
- add type WatermarkProps &nbsp;-&nbsp; by @honghuangdc [<samp>(f26d0)</samp>](https://github.com/honghuangdc/soybean-admin/commit/f26d0a6)
- remove home NAlert closable &nbsp;-&nbsp; by @honghuangdc [<samp>(98b75)</samp>](https://github.com/honghuangdc/soybean-admin/commit/98b75c2)
- optimize response code comparison &nbsp;-&nbsp; by @honghuangdc [<samp>(cf67d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cf67d55)
- **types**:
- remove useless types. &nbsp;-&nbsp; by **Azir** [<samp>(eed61)</samp>](https://github.com/honghuangdc/soybean-admin/commit/eed617f)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(d3759)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d37591d)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(993e9)</samp>](https://github.com/honghuangdc/soybean-admin/commit/993e9ca)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.1](https://github.com/honghuangdc/soybean-admin/compare/v1.3.0...v1.3.1) (2024-07-22)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- fix the issue of abnormal width of the sidebar in the top menu mix and reverse mode &nbsp;-&nbsp; by @Azir-11 in https://github.com/honghuangdc/soybean-admin/issues/562 [<samp>(c4695)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c469512)
- fix HorizontalMixMenu inverted. fixed #563 &nbsp;-&nbsp; by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/563 [<samp>(4e55b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4e55b0e)
- fix vertical-menu will not render when the layout is from mobile &nbsp;-&nbsp; by @honghuangdc [<samp>(84027)</samp>](https://github.com/honghuangdc/soybean-admin/commit/8402734)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(613c8)</samp>](https://github.com/honghuangdc/soybean-admin/commit/613c836)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **other**: modify the Chinese name of the grayscale mode &nbsp;-&nbsp; by **Azir** [<samp>(53770)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5377002)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22)
### &nbsp;&nbsp;&nbsp;🚨 Breaking Changes
- **projects**: refactor global menu & support `reversed-horizontal-mix-menu`. close #365 &nbsp;-&nbsp; by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/365 [<samp>(087e5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/087e532)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **packages**:
- `@sa/scripts`: command `gitCommit` support chinese &nbsp;-&nbsp; by @mmdapl in https://github.com/soybeanjs/soybean-admin/issues/548 [<samp>(06971)</samp>](https://github.com/soybeanjs/soybean-admin/commit/06971f3)
- @sa/axios: replace CancelTokenSource by AbortController. close #530, close #532 &nbsp;-&nbsp; by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/530 and https://github.com/soybeanjs/soybean-admin/issues/532 [<samp>(527fd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/527fd79)
- @sa/scripts: add ignore pattern list for command `gitCommitVerify`. close #504 &nbsp;-&nbsp; by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/504 [<samp>(958d0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/958d0ba)
- **projects**:
- make branch `main` tiny & modify request retry times to 0 &nbsp;-&nbsp; by @Azir-11 [<samp>(793b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/793b16e)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **hooks**: prevent program freezing when pagesize returns 0 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/545 [<samp>(f4eeb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f4eeb2e)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **projects**:
- combine `theme tokens` and `theme settings`. close #379 &nbsp;-&nbsp; by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/379 [<samp>(1d1b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1d1b148)
- change css vars mount to root &nbsp;-&nbsp; by @honghuangdc [<samp>(00f41)</samp>](https://github.com/soybeanjs/soybean-admin/commit/00f41dd)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(a0b76)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a0b76da)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(f6bd6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f6bd6b8)
- **projects**: add script `czh` &nbsp;-&nbsp; by @honghuangdc [<samp>(02069)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0206969)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![mmdapl](https://github.com/mmdapl.png?size=48)](https://github.com/mmdapl)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
## [v1.2.8](https://github.com/soybeanjs/soybean-admin/compare/v1.2.7...v1.2.8) (2024-07-20)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **packages**:
- @sa/hooks: fix searchParams of useHookTable. fixed #552 &nbsp;-&nbsp; by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/552 [<samp>(96c10)</samp>](https://github.com/soybeanjs/soybean-admin/commit/96c1044)
- **types**:
- Fixed the reference type error &nbsp;-&nbsp; by **dodu2014** in https://github.com/soybeanjs/soybean-admin/issues/551 [<samp>(3e2a9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e2a993)
- fix data type of useHookTable &nbsp;-&nbsp; by @honghuangdc [<samp>(276ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/276ea7f)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **projects**: replace `cloneDeep` of `lodash-es` with `klona` &nbsp;-&nbsp; by @honghuangdc [<samp>(a9133)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a91335d)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(58fc0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/58fc096)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(cf019)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cf0192a)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;
[dodu2014](mailto:dodu@live.cn)
## [v1.2.7](https://github.com/honghuangdc/soybean-admin/compare/v1.2.6...v1.2.7) (2024-07-12)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: supports custom menu icon sizes &nbsp;-&nbsp; by @wynn-w in https://github.com/honghuangdc/soybean-admin/issues/534 [<samp>(e035e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/e035eab)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(72ede)</samp>](https://github.com/honghuangdc/soybean-admin/commit/72ede8b)
- update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(be13c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/be13ca2)
- update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(752ec)</samp>](https://github.com/honghuangdc/soybean-admin/commit/752ec1e)
- **projects**:
- Fix deprecated configuration config &nbsp;-&nbsp; by @paynezhuang in https://github.com/honghuangdc/soybean-admin/issues/524 [<samp>(0d20e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0d20e4c)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![wynn-w](https://github.com/wynn-w.png?size=48)](https://github.com/wynn-w)&nbsp;&nbsp;[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
## [v1.2.6](https://github.com/honghuangdc/soybean-admin/compare/v1.2.5...v1.2.6) (2024-06-21)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- request modal title use i18n. fixed #507 &nbsp;-&nbsp; by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/507 [<samp>(f7de3)</samp>](https://github.com/honghuangdc/soybean-admin/commit/f7de3fd)
- add `getDataByPage` for `useTable`. fixed #499 &nbsp;-&nbsp; by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/499 [<samp>(425c6)</samp>](https://github.com/honghuangdc/soybean-admin/commit/425c69a)
- fix login redirect to routeHome when routeHome of dynamic route is not same as static route. fixed #511 &nbsp;-&nbsp; by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/511 [<samp>(49f60)</samp>](https://github.com/honghuangdc/soybean-admin/commit/49f60b2)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: optimize `getRouteQueryOfLoginRoute` &nbsp;-&nbsp; by @honghuangdc [<samp>(693f7)</samp>](https://github.com/honghuangdc/soybean-admin/commit/693f704)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**:
- update CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(5c67d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5c67d06)
- update README &nbsp;-&nbsp; by @honghuangdc [<samp>(1e67a)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1e67ae8)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps. close #510 &nbsp;-&nbsp; by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/510 [<samp>(53143)</samp>](https://github.com/honghuangdc/soybean-admin/commit/531432d)
- update deps &nbsp;-&nbsp; by @honghuangdc [<samp>(c7f6f)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c7f6f2a)
### &nbsp;&nbsp;&nbsp;🤖 CI
- **projects**:
- add github issues template &nbsp;-&nbsp; by @honghuangdc [<samp>(b5027)</samp>](https://github.com/honghuangdc/soybean-admin/commit/b5027c8)
- update github issues template &nbsp;-&nbsp; by @honghuangdc [<samp>(ff1d5)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ff1d504)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;
## [v1.2.5](https://github.com/soybeanjs/soybean-admin/compare/v1.2.4...v1.2.5) (2024-06-15)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**: Fix the issue of abnormal tab caching after logout. fixed #495 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/495 [<samp>(3eeac)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3eeace9)
### &nbsp;&nbsp;&nbsp;🔥 Performance
- **project**: Initializing the static routing function does not require asynchronization &nbsp;-&nbsp; by **CHENZL** in https://github.com/soybeanjs/soybean-admin/issues/493 [<samp>(2198b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2198b98)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: optimize code &nbsp;-&nbsp; by @soybeanjs [<samp>(b94ba)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b94baa1)
- **types**: Enhance compatibility of global types &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/494 [<samp>(cd9d5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cd9d58d)
- **utils**: Reduce code indentation and improve readability &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/496 [<samp>(ad2f2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ad2f247)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @soybeanjs [<samp>(b3368)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b336841)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(b094d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b094d68)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
[CHENZL](mailto:zlong5568863@qq.com)
## [v1.2.4](https://github.com/soybeanjs/soybean-admin/compare/v1.2.3...v1.2.4) (2024-06-14)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**:
- optimize `setupAppVersionNotification` &nbsp;-&nbsp; by @soybeanjs [<samp>(b5a72)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b5a723c)
- get buildTime with timezone 'Asia/Shanghai' &nbsp;-&nbsp; by @soybeanjs [<samp>(069fa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/069fa8a)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;
## [v1.2.3](https://github.com/soybeanjs/soybean-admin/compare/v1.2.2...v1.2.3) (2024-06-13)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes

View File

@@ -1,5 +1,290 @@
# 更新日志
## [v1.3.2](https://github.com/honghuangdc/soybean-admin/compare/v1.3.1...v1.3.2) (2024-07-30)
### &nbsp;&nbsp;&nbsp;🚀 新功能
- **项目**:
- 添加颜色渐变模式。关闭 #567 &nbsp;-&nbsp;@Azir-11 在 https://github.com/honghuangdc/soybean-admin/issues/569 和 https://github.com/honghuangdc/soybean-admin/issues/567 [<samp>(4dde4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4dde4c2)
- 添加全屏水印。关闭 #571 &nbsp;-&nbsp;@paynezhuang 在 https://github.com/honghuangdc/soybean-admin/issues/573 和 https://github.com/honghuangdc/soybean-admin/issues/571 [<samp>(ea8aa)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ea8aa6c)
### &nbsp;&nbsp;&nbsp;🐞 Bug 修复
- **项目**: 修复 vertical-mix 菜单选中问题 &nbsp;-&nbsp;@honghuangdc [<samp>(59f07)</samp>](https://github.com/honghuangdc/soybean-admin/commit/59f07d8)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **项目**:
- 添加类型 WatermarkProps &nbsp;-&nbsp;@honghuangdc [<samp>(f26d0)</samp>](https://github.com/honghuangdc/soybean-admin/commit/f26d0a6)
- 移除 home NAlert 可关闭 &nbsp;-&nbsp;@honghuangdc [<samp>(98b75)</samp>](https://github.com/honghuangdc/soybean-admin/commit/98b75c2)
- 优化响应代码比较 &nbsp;-&nbsp;@honghuangdc [<samp>(cf67d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cf67d55)
- **类型**:
- 移除无用类型。 &nbsp;-&nbsp;**Azir** [<samp>(eed61)</samp>](https://github.com/honghuangdc/soybean-admin/commit/eed617f)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新更新日志 &nbsp;-&nbsp;@honghuangdc [<samp>(d3759)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d37591d)
### &nbsp;&nbsp;&nbsp;🏡 杂项
- **依赖**: 更新依赖 &nbsp;-&nbsp;@honghuangdc [<samp>(993e9)</samp>](https://github.com/honghuangdc/soybean-admin/commit/993e9ca)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.1](https://github.com/honghuangdc/soybean-admin/compare/v1.3.0...v1.3.1) (2024-07-22)
### &nbsp;&nbsp;&nbsp;🐞 Bug 修复
- **项目**:
- 修复顶部菜单混合和反向模式下侧边栏宽度异常的问题 &nbsp;-&nbsp;@Azir-11 在 https://github.com/honghuangdc/soybean-admin/issues/562 修复 [<samp>(c4695)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c469512)
- 修复水平混合菜单反转问题。修复 #563 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/563 修复 [<samp>(4e55b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4e55b0e)
- 修复当布局来自移动端时垂直菜单不渲染的问题 &nbsp;-&nbsp;@honghuangdc 修复 [<samp>(84027)</samp>](https://github.com/honghuangdc/soybean-admin/commit/8402734)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新更新日志 &nbsp;-&nbsp;@honghuangdc 更新 [<samp>(613c8)</samp>](https://github.com/honghuangdc/soybean-admin/commit/613c836)
### &nbsp;&nbsp;&nbsp;🎨 样式
- **其他**: 修改灰度模式的中文名称 &nbsp;-&nbsp;**Azir** 修改 [<samp>(53770)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5377002)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22)
### &nbsp;&nbsp;&nbsp;🚨 破坏性变更
- **项目**: 重构全局菜单 & 支持 `reversed-horizontal-mix-menu`。关闭 #365 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/365 提出 [<samp>(087e5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/087e532)
### &nbsp;&nbsp;&nbsp;🚀 功能
- **包**:
- `@sa/scripts`: 命令 `gitCommit` 支持中文 &nbsp;-&nbsp;@mmdapl 在 https://github.com/soybeanjs/soybean-admin/issues/548 提出 [<samp>(06971)</samp>](https://github.com/soybeanjs/soybean-admin/commit/06971f3)
- @sa/axios: 用 AbortController 替换 CancelTokenSource。关闭 #530, 关闭 #532 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/530 和 https://github.com/soybeanjs/soybean-admin/issues/532 提出 [<samp>(527fd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/527fd79)
- @sa/scripts: 为命令 `gitCommitVerify` 添加忽略模式列表。关闭 #504 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/504 提出 [<samp>(958d0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/958d0ba)
- **项目**:
- 使分支 `main` 更精简 & 修改请求重试次数为 0 &nbsp;-&nbsp;@Azir-11 提出 [<samp>(793b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/793b16e)
### &nbsp;&nbsp;&nbsp;🐞 修复
- **钩子**: 当 pagesize 返回 0 时防止程序冻结 &nbsp;-&nbsp;@Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/545 提出 [<samp>(f4eeb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f4eeb2e)
### &nbsp;&nbsp;&nbsp;💅 重构
- **项目**:
- 合并 `theme tokens``theme settings`。关闭 #379 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/379 提出 [<samp>(1d1b1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1d1b148)
- 将 css 变量挂载到 root &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(00f41)</samp>](https://github.com/soybeanjs/soybean-admin/commit/00f41dd)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新更新日志 &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(a0b76)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a0b76da)
### &nbsp;&nbsp;&nbsp;🏡 杂务
- **依赖**: 更新依赖 &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(f6bd6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f6bd6b8)
- **项目**: 添加脚本 `czh` &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(02069)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0206969)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![mmdapl](https://github.com/mmdapl.png?size=48)](https://github.com/mmdapl)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
## [v1.2.8](https://github.com/soybeanjs/soybean-admin/compare/v1.2.7...v1.2.8) (2024-07-20)
### &nbsp;&nbsp;&nbsp;🐞 修复
- **包**:
- @sa/hooks: 修复 useHookTable 的 searchParams。修复了 #552 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/552 提出 [<samp>(96c10)</samp>](https://github.com/soybeanjs/soybean-admin/commit/96c1044)
- **类型**:
- 修复了引用类型错误 &nbsp;-&nbsp;**dodu2014** 在 https://github.com/soybeanjs/soybean-admin/issues/551 提出 [<samp>(3e2a9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e2a993)
- 修复 useHookTable 的数据类型 &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(276ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/276ea7f)
### &nbsp;&nbsp;&nbsp;💅 重构
- **项目**: 用 `klona` 替换 `lodash-es``cloneDeep` &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(a9133)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a91335d)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新更新日志 &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(58fc0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/58fc096)
### &nbsp;&nbsp;&nbsp;🏡 杂务
- **依赖**: 更新依赖 &nbsp;-&nbsp;@honghuangdc 提出 [<samp>(cf019)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cf0192a)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;
[dodu2014](mailto:dodu@live.cn)
## [v1.2.7](https://github.com/honghuangdc/soybean-admin/compare/v1.2.6...v1.2.7) (2024-07-12)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **项目**: 支持自定义菜单图标大小 &nbsp;-&nbsp;@wynn-w 在 https://github.com/honghuangdc/soybean-admin/issues/534 提出 [<samp>(e035e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/e035eab)
### &nbsp;&nbsp;&nbsp;🏡 杂务
- **依赖**:
- 更新依赖 &nbsp;-&nbsp;@honghuangdc [<samp>(72ede)</samp>](https://github.com/honghuangdc/soybean-admin/commit/72ede8b)
- 更新依赖 &nbsp;-&nbsp;@honghuangdc [<samp>(be13c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/be13ca2)
- 更新依赖 &nbsp;-&nbsp;@honghuangdc [<samp>(752ec)</samp>](https://github.com/honghuangdc/soybean-admin/commit/752ec1e)
- **项目**:
- 修复已弃用的配置配置 &nbsp;-&nbsp;@paynezhuang 在 https://github.com/honghuangdc/soybean-admin/issues/524 提出 [<samp>(0d20e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0d20e4c)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;[![wynn-w](https://github.com/wynn-w.png?size=48)](https://github.com/wynn-w)&nbsp;&nbsp;[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
## [v1.2.6](https://github.com/honghuangdc/soybean-admin/compare/v1.2.5...v1.2.6) (2024-06-21)
### &nbsp;&nbsp;&nbsp;🐞 修复
- **项目**:
- 请求模态标题使用 i18n。修复 #507 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/507 提出 [<samp>(f7de3)</samp>](https://github.com/honghuangdc/soybean-admin/commit/f7de3fd)
-`useTable` 添加 `getDataByPage`。修复 #499 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/499 提出 [<samp>(425c6)</samp>](https://github.com/honghuangdc/soybean-admin/commit/425c69a)
- 修复登录重定向到 routeHome 时,动态路由的 routeHome 与静态路由不同的问题。修复 #511 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/511 提出 [<samp>(49f60)</samp>](https://github.com/honghuangdc/soybean-admin/commit/49f60b2)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **项目**: 优化 `getRouteQueryOfLoginRoute` &nbsp;-&nbsp;@honghuangdc [<samp>(693f7)</samp>](https://github.com/honghuangdc/soybean-admin/commit/693f704)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**:
- 更新更新日志 &nbsp;-&nbsp;@honghuangdc [<samp>(5c67d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5c67d06)
- 更新自述文件 &nbsp;-&nbsp;@honghuangdc [<samp>(1e67a)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1e67ae8)
### &nbsp;&nbsp;&nbsp;🏡 杂务
- **依赖**:
- 更新依赖。关闭 #510 &nbsp;-&nbsp;@honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/510 提出 [<samp>(53143)</samp>](https://github.com/honghuangdc/soybean-admin/commit/531432d)
- 更新依赖 &nbsp;-&nbsp;@honghuangdc [<samp>(c7f6f)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c7f6f2a)
### &nbsp;&nbsp;&nbsp;🤖 CI
- **项目**:
- 添加 github 问题模板 &nbsp;-&nbsp;@honghuangdc [<samp>(b5027)</samp>](https://github.com/honghuangdc/soybean-admin/commit/b5027c8)
- 更新 github 问题模板 &nbsp;-&nbsp;@honghuangdc [<samp>(ff1d5)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ff1d504)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![honghuangdc](https://github.com/honghuangdc.png?size=48)](https://github.com/honghuangdc)&nbsp;&nbsp;
## [v1.2.5](https://github.com/soybeanjs/soybean-admin/compare/v1.2.4...v1.2.5) (2024-06-15)
### &nbsp;&nbsp;&nbsp;🐞 错误修复
- **项目**: 修复登出后标签页异常缓存的问题。已修复 #495 &nbsp;-&nbsp;@Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/495 [<samp>(3eeac)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3eeace9)
### &nbsp;&nbsp;&nbsp;🔥 性能
- **项目**: 初始化静态路由功能不需要异步 &nbsp;-&nbsp;**CHENZL** 在 https://github.com/soybeanjs/soybean-admin/issues/493 [<samp>(2198b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2198b98)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **项目**: 优化代码 &nbsp;-&nbsp;@soybeanjs [<samp>(b94ba)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b94baa1)
- **类型**: 提高全局类型的兼容性 &nbsp;-&nbsp;@Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/494 [<samp>(cd9d5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cd9d58d)
- **工具**: 减少代码缩进,提高可读性 &nbsp;-&nbsp;@Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/496 [<samp>(ad2f2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ad2f247)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新CHANGELOG &nbsp;-&nbsp;@soybeanjs [<samp>(f70d2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f70d29b)
### &nbsp;&nbsp;&nbsp;🏡 杂项
- **依赖**: 更新依赖 &nbsp;-&nbsp;@soybeanjs [<samp>(b094d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b094d68)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;
[CHENZL](mailto:zlong5568863@qq.com)
## [v1.2.4](https://github.com/soybeanjs/soybean-admin/compare/v1.2.3...v1.2.4) (2024-06-14)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **项目**:
- 优化 `setupAppVersionNotification` &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(b5a72)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b5a723c)
- 获取 'Asia/Shanghai' 时区的构建时间 &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(069fa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/069fa8a)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;
## [v1.2.3](https://github.com/soybeanjs/soybean-admin/compare/v1.2.2...v1.2.3) (2024-06-13)
### &nbsp;&nbsp;&nbsp;🐞 修复错误
- **项目**:
- 通过在index.html中添加color-scheme元标签修复移动浏览器主题问题 &nbsp;-&nbsp;@KickCashew 在 https://github.com/soybeanjs/soybean-admin/issues/488 中提交 [<samp>(c2125)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c212565)
- 修复二级目录组件为空 &nbsp;-&nbsp;@paynezhuang 在 https://github.com/soybeanjs/soybean-admin/issues/491 中提交 [<samp>(aabb2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/aabb2a4)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**:
- 修复超链接指向错误 &nbsp;-&nbsp;**Azir** 提交 [<samp>(20a81)</samp>](https://github.com/soybeanjs/soybean-admin/commit/20a8127)
- 更新 README &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(70261)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7026126)
### &nbsp;&nbsp;&nbsp;🏡 杂项
- **依赖**:
- 更新依赖 &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(813d8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/813d8ce)
- 更新依赖 &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(bf718)</samp>](https://github.com/soybeanjs/soybean-admin/commit/bf71837)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;[![KickCashew](https://github.com/KickCashew.png?size=48)](https://github.com/KickCashew)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com)
## [v1.2.2](https://github.com/honghuangdc/soybean-admin/compare/v1.2.1...v1.2.2) (2024-06-12)
### &nbsp;&nbsp;&nbsp;🚀 特性
- **项目**: 切换标签时重置滚动位置 &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(9094b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/9094b21)
### &nbsp;&nbsp;&nbsp;🐞 修复错误
- **项目**:
- 在DEV模式下隐藏AppVersionNotification &nbsp;-&nbsp;@sigma-plus 在 https://github.com/honghuangdc/soybean-admin/issues/482 中提交 [<samp>(62592)</samp>](https://github.com/honghuangdc/soybean-admin/commit/6259287)
- 修复在移动布局中隐藏menu-toggler。fixed #483 &nbsp;-&nbsp;@soybeanjs 在 https://github.com/honghuangdc/soybean-admin/issues/483 中提交 [<samp>(4470c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4470cb4)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新 README &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(8f9a7)</samp>](https://github.com/honghuangdc/soybean-admin/commit/8f9a705)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![sigma-plus](https://github.com/sigma-plus.png?size=48)](https://github.com/sigma-plus)&nbsp;&nbsp;
## [v1.2.1](https://github.com/honghuangdc/soybean-admin/compare/v1.2.0...v1.2.1) (2024-06-07)
### &nbsp;&nbsp;&nbsp;🐞 修复错误
- **项目**:
- 修复页面重新加载时获取用户信息 &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(ff51b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ff51b72)
- 修复setupAppVersionNotification渲染 &nbsp;-&nbsp;@soybeanjs 提交 [<samp>(6a6eb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/6a6eb9a)
### &nbsp;&nbsp;&nbsp;📖 文档
- **项目**: 更新CHANGELOG &nbsp;-&nbsp;@soybeanjs [<samp>(fe06b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/fe06b8c)完成
### &nbsp;&nbsp;&nbsp;🏡 日常任务
- **依赖**: 更新依赖 &nbsp;-&nbsp;@soybeanjs [<samp>(08827)</samp>](https://github.com/honghuangdc/soybean-admin/commit/08827a4)完成
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;
## [v1.2.0](https://github.com/soybeanjs/soybean-admin/compare/v1.1.5...v1.2.0) (2024-06-06)

View File

@@ -157,10 +157,10 @@ Thanks the following people for their contributions. If you want to contribute t
<p>QQ Group</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" />
</div>
<div>
<!-- <div>
<p>WeChat Group</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/picgo/soybean-admin-wechat-0620.jpg" style="width:200px" />
</div>
</div> -->
<div>
<p>Add the following WeChat to invite to the WeChat group</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" />

View File

@@ -157,10 +157,10 @@ pnpm build
<p>QQ交流群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" />
</div>
<div>
<!-- <div>
<p>微信群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/picgo/soybean-admin-wechat-0620.jpg" style="width:200px" />
</div>
</div> -->
<div>
<p>添加下面微信邀请进微信群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" />

View File

@@ -1 +1,2 @@
export * from './proxy';
export * from './time';

12
build/config/time.ts Normal file
View File

@@ -0,0 +1,12 @@
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
export function getBuildTime() {
dayjs.extend(utc);
dayjs.extend(timezone);
const buildTime = dayjs.tz(Date.now(), 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
return buildTime;
}

View File

@@ -8,20 +8,6 @@ export function setupElegantRouter() {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
customRoutes: {
names: [
'exception_403',
'exception_404',
'exception_500',
'document_project',
'document_project-link',
'document_vue',
'document_vite',
'document_unocss',
'document_naive',
'document_antd'
]
},
routePathTransformer(routeName, routePath) {
const key = routeName as RouteKey;

View File

@@ -1,7 +1,7 @@
{
"name": "soybean-admin",
"type": "module",
"version": "1.2.3",
"version": "1.3.4",
"description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
"author": {
"name": "Soybean",
@@ -35,6 +35,7 @@
"build:test": "vite build --mode test",
"cleanup": "sa cleanup",
"commit": "sa git-commit",
"czh": "sa git-commit -l=zh-cn",
"dev": "vite --mode test",
"dev:prod": "vite --mode prod",
"gen-route": "sa gen-route",
@@ -55,50 +56,48 @@
"@sa/utils": "workspace:*",
"@vueuse/core": "10.11.0",
"clipboard": "2.0.11",
"dayjs": "1.11.11",
"echarts": "5.5.0",
"lodash-es": "4.17.21",
"naive-ui": "2.38.2",
"dayjs": "1.11.12",
"echarts": "5.5.1",
"naive-ui": "2.39.0",
"nprogress": "0.2.0",
"pinia": "2.1.7",
"tailwind-merge": "2.3.0",
"vue": "3.4.27",
"vue-draggable-plus": "0.5.0",
"pinia": "2.2.0",
"tailwind-merge": "2.4.0",
"vue": "3.4.35",
"vue-draggable-plus": "0.5.2",
"vue-i18n": "9.13.1",
"vue-router": "4.3.3"
"vue-router": "4.4.1"
},
"devDependencies": {
"@elegant-router/vue": "0.3.7",
"@iconify/json": "2.2.218",
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.232",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.3.6",
"@types/lodash-es": "4.17.12",
"@types/node": "20.14.2",
"@soybeanjs/eslint-config": "1.4.0",
"@types/node": "22.0.1",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "0.61.0",
"@unocss/preset-icons": "0.61.0",
"@unocss/preset-uno": "0.61.0",
"@unocss/transformer-directives": "0.61.0",
"@unocss/transformer-variant-group": "0.61.0",
"@unocss/vite": "0.61.0",
"@vitejs/plugin-vue": "5.0.5",
"@unocss/eslint-config": "0.61.9",
"@unocss/preset-icons": "0.61.9",
"@unocss/preset-uno": "0.61.9",
"@unocss/transformer-directives": "0.61.9",
"@unocss/transformer-variant-group": "0.61.9",
"@unocss/vite": "0.61.9",
"@vitejs/plugin-vue": "5.1.1",
"@vitejs/plugin-vue-jsx": "4.0.0",
"eslint": "9.4.0",
"eslint-plugin-vue": "9.26.0",
"eslint": "9.8.0",
"eslint-plugin-vue": "9.27.0",
"lint-staged": "15.2.7",
"sass": "1.77.5",
"sass": "1.77.8",
"simple-git-hooks": "2.11.1",
"tsx": "4.15.4",
"typescript": "5.4.5",
"unplugin-icons": "0.19.0",
"unplugin-vue-components": "0.27.0",
"vite": "5.3.0",
"tsx": "4.16.3",
"typescript": "5.5.4",
"unplugin-icons": "0.19.1",
"unplugin-vue-components": "0.27.3",
"vite": "5.3.5",
"vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "7.2.1",
"vite-plugin-vue-devtools": "7.3.7",
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.0.21"
"vue-tsc": "2.0.29"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/axios",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -12,8 +12,8 @@
"dependencies": {
"@sa/utils": "workspace:*",
"axios": "1.7.2",
"axios-retry": "4.4.0",
"qs": "6.12.1"
"axios-retry": "4.4.2",
"qs": "6.12.3"
},
"devDependencies": {
"@types/qs": "6.9.15"

View File

@@ -1,5 +1,5 @@
import axios, { AxiosError } from 'axios';
import type { AxiosResponse, CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
import axiosRetry from 'axios-retry';
import { nanoid } from '@sa/utils';
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
@@ -22,7 +22,7 @@ function createCommonRequest<ResponseData = any>(
const axiosConf = createAxiosConfig(axiosConfig);
const instance = axios.create(axiosConf);
const cancelTokenSourceMap = new Map<string, CancelTokenSource>();
const abortControllerMap = new Map<string, AbortController>();
// config axios retry
const retryOptions = createRetryOptions(axiosConf);
@@ -35,10 +35,12 @@ function createCommonRequest<ResponseData = any>(
const requestId = nanoid();
config.headers.set(REQUEST_ID_KEY, requestId);
// config cancel token
const cancelTokenSource = axios.CancelToken.source();
config.cancelToken = cancelTokenSource.token;
cancelTokenSourceMap.set(requestId, cancelTokenSource);
// config abort controller
if (!config.signal) {
const abortController = new AbortController();
config.signal = abortController.signal;
abortControllerMap.set(requestId, abortController);
}
// handle config by hook
const handledConfig = opts.onRequest?.(config) || config;
@@ -79,18 +81,18 @@ function createCommonRequest<ResponseData = any>(
);
function cancelRequest(requestId: string) {
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
if (cancelTokenSource) {
cancelTokenSource.cancel();
cancelTokenSourceMap.delete(requestId);
const abortController = abortControllerMap.get(requestId);
if (abortController) {
abortController.abort();
abortControllerMap.delete(requestId);
}
}
function cancelAllRequest() {
cancelTokenSourceMap.forEach(cancelTokenSource => {
cancelTokenSource.cancel();
abortControllerMap.forEach(abortController => {
abortController.abort();
});
cancelTokenSourceMap.clear();
abortControllerMap.clear();
}
return {

View File

@@ -20,7 +20,7 @@ export function createDefaultOptions<ResponseData = any>(options?: Partial<Reque
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
const retryConfig: IAxiosRetryConfig = {
retries: 3
retries: 0
};
Object.assign(retryConfig, config);

View File

@@ -69,7 +69,19 @@ export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<Axi
};
export interface RequestInstanceCommon<T> {
/**
* cancel the request by request id
*
* if the request provide abort controller sign from config, it will not collect in the abort controller map
*
* @param requestId
*/
cancelRequest: (requestId: string) => void;
/**
* cancel all request
*
* if the request provide abort controller sign from config, it will not collect in the abort controller map
*/
cancelAllRequest: () => void;
/** you can set custom state in the request instance */
state: T;

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/color",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/hooks",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -10,6 +10,7 @@
}
},
"dependencies": {
"@sa/axios": "workspace:*"
"@sa/axios": "workspace:*",
"@sa/utils": "workspace:*"
}
}

View File

@@ -1,5 +1,6 @@
import { computed, reactive, ref } from 'vue';
import type { Ref } from 'vue';
import { jsonClone } from '@sa/utils';
import useBoolean from './use-boolean';
import useLoading from './use-loading';
@@ -65,11 +66,11 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
const searchParams: NonNullable<Parameters<A>[0]> = reactive({ ...apiParams });
const searchParams: NonNullable<Parameters<A>[0]> = reactive(jsonClone({ ...apiParams }));
const allColumns = ref(config.columns()) as Ref<C[]>;
const data: Ref<T[]> = ref([]);
const data: Ref<TableDataWithIndex<T>[]> = ref([]);
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
@@ -129,7 +130,7 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
/** reset search params */
function resetSearchParams() {
Object.assign(searchParams, apiParams);
Object.assign(searchParams, jsonClone(apiParams));
}
if (immediate) {

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/materials",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -11,7 +11,7 @@
},
"dependencies": {
"@sa/utils": "workspace:*",
"simplebar-vue": "2.3.4"
"simplebar-vue": "2.3.5"
},
"devDependencies": {
"typed-css-modules": "0.9.1"

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/fetch",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/scripts",
"version": "1.2.3",
"version": "1.3.4",
"bin": {
"sa": "./bin.ts"
},
@@ -15,13 +15,13 @@
"devDependencies": {
"@soybeanjs/changelog": "0.3.24",
"bumpp": "9.4.1",
"c12": "1.10.0",
"c12": "1.11.1",
"cac": "6.7.14",
"consola": "3.2.3",
"enquirer": "2.4.1",
"execa": "9.2.0",
"execa": "9.3.0",
"kolorist": "1.8.0",
"npm-check-updates": "16.14.20",
"rimraf": "5.0.7"
"npm-check-updates": "17.0.0",
"rimraf": "6.0.1"
}
}

View File

@@ -1,9 +1,9 @@
import path from 'node:path';
import { readFileSync } from 'node:fs';
import { prompt } from 'enquirer';
import { bgRed, green, red, yellow } from 'kolorist';
import { execCommand } from '../shared';
import type { CliOption } from '../types';
import { locales } from '../locales';
import type { Lang } from '../locales';
interface PromptObject {
types: string;
@@ -14,13 +14,11 @@ interface PromptObject {
/**
* Git commit with Conventional Commits standard
*
* @param gitCommitTypes
* @param gitCommitScopes
* @param lang
*/
export async function gitCommit(
gitCommitTypes: CliOption['gitCommitTypes'],
gitCommitScopes: CliOption['gitCommitScopes']
) {
export async function gitCommit(lang: Lang = 'en-us') {
const { gitCommitMessages, gitCommitTypes, gitCommitScopes } = locales[lang];
const typesChoices = gitCommitTypes.map(([value, msg]) => {
const nameWithSuffix = `${value}:`;
@@ -41,19 +39,19 @@ export async function gitCommit(
{
name: 'types',
type: 'select',
message: 'Please select a type',
message: gitCommitMessages.types,
choices: typesChoices
},
{
name: 'scopes',
type: 'select',
message: 'Please select a scope',
message: gitCommitMessages.scopes,
choices: scopesChoices
},
{
name: 'description',
type: 'text',
message: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
message: gitCommitMessages.description
}
]);
@@ -67,20 +65,20 @@ export async function gitCommit(
}
/** Git commit message verify */
export async function gitCommitVerify() {
export async function gitCommitVerify(lang: Lang = 'en-us', ignores: RegExp[] = []) {
const gitPath = await execCommand('git', ['rev-parse', '--show-toplevel']);
const gitMsgPath = path.join(gitPath, '.git', 'COMMIT_EDITMSG');
const commitMsg = readFileSync(gitMsgPath, 'utf8').trim();
if (ignores.some(regExp => regExp.test(commitMsg))) return;
const REG_EXP = /(?<type>[a-z]+)(?:\((?<scope>.+)\))?(?<breaking>!)?: (?<description>.+)/i;
if (!REG_EXP.test(commitMsg)) {
throw new Error(
`${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
)}`
);
const errorMsg = locales[lang].gitCommitVerify;
throw new Error(errorMsg);
}
}

View File

@@ -12,34 +12,18 @@ const defaultOptions: CliOption = {
'**/node_modules',
'!node_modules/**'
],
gitCommitTypes: [
['feat', 'A new feature'],
['fix', 'A bug fix'],
['docs', 'Documentation only changes'],
['style', 'Changes that do not affect the meaning of the code'],
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
['perf', 'A code change that improves performance'],
['optimize', 'A code change that optimizes code quality'],
['test', 'Adding missing tests or correcting existing tests'],
['build', 'Changes that affect the build system or external dependencies'],
['ci', 'Changes to our CI configuration files and scripts'],
['chore', "Other changes that don't modify src or test files"],
['revert', 'Reverts a previous commit']
],
gitCommitScopes: [
['projects', 'project'],
['packages', 'packages'],
['components', 'components'],
['hooks', 'hook functions'],
['utils', 'utils functions'],
['types', 'TS declaration'],
['styles', 'style'],
['deps', 'project dependencies'],
['release', 'release project'],
['other', 'other changes']
],
ncuCommandArgs: ['--deep', '-u'],
changelogOptions: {}
changelogOptions: {},
gitCommitVerifyIgnores: [
/^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
/^(Merge tag (.*?))(?:\r?\n)*$/m,
/^(R|r)evert (.*)/,
/^(amend|fixup|squash)!/,
/^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
/^Merge remote-tracking branch(\s*)(.*)/,
/^Automatic merge(.*)/,
/^Auto-merged (.*?) into (.*)/
]
};
export async function loadCliOptions(overrides?: Partial<CliOption>, cwd = process.cwd()) {

View File

@@ -3,6 +3,7 @@ import { blue, lightGreen } from 'kolorist';
import { version } from '../package.json';
import { cleanup, genChangelog, generateRoute, gitCommit, gitCommitVerify, release, updatePkg } from './commands';
import { loadCliOptions } from './config';
import type { Lang } from './locales';
type Command = 'cleanup' | 'update-pkg' | 'git-commit' | 'git-commit-verify' | 'changelog' | 'release' | 'gen-route';
@@ -18,13 +19,19 @@ interface CommandArg {
/** Generate changelog by total tags */
total?: boolean;
/**
* The glob pattern of dirs to cleanup
* The glob pattern of dirs to clean up
*
* If not set, it will use the default value
*
* Multiple values use "," to separate them
*/
cleanupDir?: string;
/**
* display lang of cli
*
* @default 'en-us'
*/
lang?: Lang;
}
export async function setupCli() {
@@ -44,6 +51,7 @@ export async function setupCli() {
'-c, --cleanupDir <dir>',
'The glob pattern of dirs to cleanup, If not set, it will use the default value, Multiple values use "," to separate them'
)
.option('-l, --lang <lang>', 'display lang of cli', { default: 'en-us', type: [String] })
.help();
const commands: CommandWithAction<CommandArg> = {
@@ -61,14 +69,14 @@ export async function setupCli() {
},
'git-commit': {
desc: 'git commit, generate commit message which match Conventional Commits standard',
action: async () => {
await gitCommit(cliOptions.gitCommitTypes, cliOptions.gitCommitScopes);
action: async args => {
await gitCommit(args?.lang);
}
},
'git-commit-verify': {
desc: 'verify git commit message, make sure it match Conventional Commits standard',
action: async () => {
await gitCommitVerify();
action: async args => {
await gitCommitVerify(args?.lang, cliOptions.gitCommitVerifyIgnores);
}
},
changelog: {

View File

@@ -0,0 +1,78 @@
import { bgRed, green, red, yellow } from 'kolorist';
export type Lang = 'zh-cn' | 'en-us';
export const locales = {
'zh-cn': {
gitCommitMessages: {
types: '请选择提交类型',
scopes: '请选择提交范围',
description: `请输入描述信息(${yellow('!')}开头表示破坏性改动`
},
gitCommitTypes: [
['feat', '新功能'],
['fix', '修复Bug'],
['docs', '只更新文档'],
['style', '修改代码风格,不影响代码含义的变更'],
['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'],
['perf', '可提高性能的代码更改'],
['optimize', '优化代码质量的代码更改'],
['test', '添加缺失的测试或更正现有测'],
['build', '影响构建系统或外部依赖项的更改'],
['ci', '对 CI 配置文件和脚本的更改'],
['chore', '没有修改src或测试文件的其他变更'],
['revert', '还原先前的提交']
] as [string, string][],
gitCommitScopes: [
['projects', '项目'],
['packages', '包'],
['components', '组件'],
['hooks', '钩子函数'],
['utils', '工具函数'],
['types', 'TS类型声明'],
['styles', '代码风格'],
['deps', '项目依赖'],
['release', '发布项目新版本'],
['other', '其他的变更']
] as [string, string][],
gitCommitVerify: `${bgRed(' 错误 ')} ${red('git 提交信息必须符合 Conventional Commits 标准!')}\n\n${green(
'推荐使用命令 `pnpm commit` 生成符合 Conventional Commits 标准的提交信息。\n获取有关 Conventional Commits 的更多信息,请访问此链接: https://conventionalcommits.org'
)}`
},
'en-us': {
gitCommitMessages: {
types: 'Please select a type',
scopes: 'Please select a scope',
description: `Please enter a description (add prefix ${yellow('!')} to indicate breaking change)`
},
gitCommitTypes: [
['feat', 'A new feature'],
['fix', 'A bug fix'],
['docs', 'Documentation only changes'],
['style', 'Changes that do not affect the meaning of the code'],
['refactor', 'A code change that neither fixes a bug nor adds a feature'],
['perf', 'A code change that improves performance'],
['optimize', 'A code change that optimizes code quality'],
['test', 'Adding missing tests or correcting existing tests'],
['build', 'Changes that affect the build system or external dependencies'],
['ci', 'Changes to our CI configuration files and scripts'],
['chore', "Other changes that don't modify src or test files"],
['revert', 'Reverts a previous commit']
] as [string, string][],
gitCommitScopes: [
['projects', 'project'],
['packages', 'packages'],
['components', 'components'],
['hooks', 'hook functions'],
['utils', 'utils functions'],
['types', 'TS declaration'],
['styles', 'style'],
['deps', 'project dependencies'],
['release', 'release project'],
['other', 'other changes']
] as [string, string][],
gitCommitVerify: `${bgRed(' ERROR ')} ${red('git commit message must match the Conventional Commits standard!')}\n\n${green(
'Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org'
)}`
}
} satisfies Record<Lang, Record<string, unknown>>;

View File

@@ -14,10 +14,6 @@ export interface CliOption {
* ```
*/
cleanupDirs: string[];
/** Git commit types */
gitCommitTypes: [string, string][];
/** Git commit scopes */
gitCommitScopes: [string, string][];
/**
* Npm-check-updates command args
*
@@ -30,4 +26,6 @@ export interface CliOption {
* @link https://github.com/soybeanjs/changelog
*/
changelogOptions: Partial<ChangelogOption>;
/** The ignore pattern list of git commit verify */
gitCommitVerifyIgnores: RegExp[];
}

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/uno-preset",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/utils",
"version": "1.2.3",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -12,6 +12,7 @@
"dependencies": {
"colord": "2.9.3",
"crypto-js": "4.2.0",
"klona": "2.0.6",
"localforage": "1.10.0",
"nanoid": "5.0.7"
},

View File

@@ -1,3 +1,4 @@
export * from './crypto';
export * from './storage';
export * from './nanoid';
export * from './klona';

View File

@@ -0,0 +1,3 @@
import { klona as jsonClone } from 'klona/json';
export { jsonClone };

4334
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue';
import { NConfigProvider, darkTheme } from 'naive-ui';
import type { WatermarkProps } from 'naive-ui';
import { useAppStore } from './store/modules/app';
import { useThemeStore } from './store/modules/theme';
import { naiveDateLocales, naiveLocales } from './locales/naive';
@@ -21,6 +22,22 @@ const naiveLocale = computed(() => {
const naiveDateLocale = computed(() => {
return naiveDateLocales[appStore.locale];
});
const watermarkProps = computed<WatermarkProps>(() => {
return {
content: themeStore.watermark?.text || 'SoybeanAdmin',
cross: true,
fullscreen: true,
fontSize: 16,
lineHeight: 16,
width: 384,
height: 384,
xOffset: 12,
yOffset: 60,
rotate: -15,
zIndex: 9999
};
});
</script>
<template>
@@ -33,6 +50,7 @@ const naiveDateLocale = computed(() => {
>
<AppProvider>
<RouterView class="bg-layout" />
<NWatermark v-if="themeStore.watermark?.visible" v-bind="watermarkProps" />
</AppProvider>
</NConfigProvider>
</template>

View File

@@ -9,7 +9,7 @@ defineProps<Props>();
</script>
<template>
<div class="bg-container text-base_text transition-300" :class="{ 'bg-inverted text-#1f1f1f': inverted }">
<div class="bg-container text-base-text transition-300" :class="{ 'bg-inverted text-#1f1f1f': inverted }">
<slot></slot>
</div>
</template>

View File

@@ -1,5 +1,9 @@
import { transformRecordToOption } from '@/utils/common';
export const GLOBAL_HEADER_MENU_ID = '__GLOBAL_HEADER_MENU__';
export const GLOBAL_SIDER_MENU_ID = '__GLOBAL_SIDER_MENU__';
export const themeSchemaRecord: Record<UnionKey.ThemeScheme, App.I18n.I18nKey> = {
light: 'theme.themeSchema.light',
dark: 'theme.themeSchema.dark',

View File

@@ -1,29 +0,0 @@
import { transformRecordToOption } from '@/utils/common';
export const enableStatusRecord: Record<Api.Common.EnableStatus, App.I18n.I18nKey> = {
'1': 'page.manage.common.status.enable',
'2': 'page.manage.common.status.disable'
};
export const enableStatusOptions = transformRecordToOption(enableStatusRecord);
export const userGenderRecord: Record<Api.SystemManage.UserGender, App.I18n.I18nKey> = {
'1': 'page.manage.user.gender.male',
'2': 'page.manage.user.gender.female'
};
export const userGenderOptions = transformRecordToOption(userGenderRecord);
export const menuTypeRecord: Record<Api.SystemManage.MenuType, App.I18n.I18nKey> = {
'1': 'page.manage.menu.type.directory',
'2': 'page.manage.menu.type.menu'
};
export const menuTypeOptions = transformRecordToOption(menuTypeRecord);
export const menuIconTypeRecord: Record<Api.SystemManage.IconType, App.I18n.I18nKey> = {
'1': 'page.manage.menu.iconType.iconify',
'2': 'page.manage.menu.iconType.local'
};
export const menuIconTypeOptions = transformRecordToOption(menuIconTypeRecord);

View File

@@ -30,17 +30,30 @@ export function useRouterPush(inSetup = true) {
name: key
};
if (query) {
if (Object.keys(query || {}).length) {
routeLocation.query = query;
}
if (params) {
if (Object.keys(params || {}).length) {
routeLocation.params = params;
}
return routerPush(routeLocation);
}
function routerPushByKeyWithMetaQuery(key: RouteKey) {
const allRoutes = router.getRoutes();
const meta = allRoutes.find(item => item.name === key)?.meta || null;
const query: Record<string, string> = {};
meta?.query?.forEach(item => {
query[item.key] = item.value;
});
return routerPushByKey(key, { query });
}
async function toHome() {
return routerPushByKey('root');
}
@@ -95,6 +108,7 @@ export function useRouterPush(inSetup = true) {
routerPush,
routerBack,
routerPushByKey,
routerPushByKeyWithMetaQuery,
toLogin,
toggleLoginModule,
redirectFromLogin

View File

@@ -1,7 +1,7 @@
import { computed, effectScope, onScopeDispose, reactive, ref, watch } from 'vue';
import type { Ref } from 'vue';
import type { PaginationProps } from 'naive-ui';
import { cloneDeep } from 'lodash-es';
import { jsonClone } from '@sa/utils';
import { useBoolean, useHookTable } from '@sa/hooks';
import { useAppStore } from '@/store/modules/app';
import { $t } from '@/locales';
@@ -40,17 +40,20 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
transformer: res => {
const { records = [], current = 1, size = 10, total = 0 } = res.data || {};
// Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
const pageSize = size <= 0 ? 10 : size;
const recordsWithIndex = records.map((item, index) => {
return {
...item,
index: (current - 1) * size + index + 1
index: (current - 1) * pageSize + index + 1
};
});
return {
data: recordsWithIndex,
pageNum: current,
pageSize: size,
pageSize,
total
};
},
@@ -160,6 +163,24 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
Object.assign(pagination, update);
}
/**
* get data by page number
*
* @param pageNum the page number. default is 1
*/
async function getDataByPage(pageNum: number = 1) {
updatePagination({
page: pageNum
});
updateSearchParams({
current: pageNum,
size: pagination.pageSize!
});
await getData();
}
scope.run(() => {
watch(
() => appStore.locale,
@@ -184,6 +205,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
mobilePagination,
updatePagination,
getData,
getDataByPage,
searchParams,
updateSearchParams,
resetSearchParams
@@ -206,7 +228,7 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
function handleEdit(id: T['id']) {
operateType.value = 'edit';
const findItem = data.value.find(item => item.id === id) || null;
editingData.value = cloneDeep(findItem);
editingData.value = jsonClone(findItem);
openDrawer();
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, defineAsyncComponent } from 'vue';
import { AdminLayout, LAYOUT_SCROLL_EL_ID } from '@sa/materials';
import type { LayoutMode } from '@sa/materials';
import { useAppStore } from '@/store/modules/app';
@@ -18,7 +18,9 @@ defineOptions({
const appStore = useAppStore();
const themeStore = useThemeStore();
const { menus } = setupMixMenuContext();
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext();
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
const layoutMode = computed(() => {
const vertical: LayoutMode = 'vertical';
@@ -26,30 +28,34 @@ const layoutMode = computed(() => {
return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
});
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
vertical: {
showLogo: false,
showMenu: false,
showMenuToggler: true
},
'vertical-mix': {
showLogo: false,
showMenu: false,
showMenuToggler: false
},
horizontal: {
showLogo: true,
showMenu: true,
showMenuToggler: false
},
'horizontal-mix': {
showLogo: true,
showMenu: true,
showMenuToggler: false
}
};
const headerProps = computed(() => {
const { mode, reverseHorizontalMix } = themeStore.layout;
const headerProps = computed(() => headerPropsConfig[themeStore.layout.mode]);
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
vertical: {
showLogo: false,
showMenu: false,
showMenuToggler: true
},
'vertical-mix': {
showLogo: false,
showMenu: false,
showMenuToggler: false
},
horizontal: {
showLogo: true,
showMenu: true,
showMenuToggler: false
},
'horizontal-mix': {
showLogo: true,
showMenu: true,
showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value
}
};
return headerPropsConfig[mode];
});
const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
@@ -62,11 +68,16 @@ const siderWidth = computed(() => getSiderWidth());
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
function getSiderWidth() {
const { reverseHorizontalMix } = themeStore.layout;
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
if (isHorizontalMix.value && reverseHorizontalMix) {
return isActiveFirstLevelMenuHasChildren.value ? width : 0;
}
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
w += mixChildMenuWidth;
}
@@ -74,11 +85,16 @@ function getSiderWidth() {
}
function getSiderCollapsedWidth() {
const { reverseHorizontalMix } = themeStore.layout;
const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider;
if (isHorizontalMix.value && reverseHorizontalMix) {
return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0;
}
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
if (isVerticalMix.value && appStore.mixSiderFixed && menus.value.length) {
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
w += mixChildMenuWidth;
}
@@ -116,6 +132,7 @@ function getSiderCollapsedWidth() {
<template #sider>
<GlobalSider />
</template>
<GlobalMenu />
<GlobalContent />
<ThemeDrawer />
<template #footer>

View File

@@ -26,10 +26,30 @@ function useMixMenu() {
setActiveFirstLevelMenuKey(firstLevelRouteName);
}
const menus = computed(
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
const firstLevelMenus = computed<App.Global.Menu[]>(() =>
routeStore.menus.map(menu => {
const { children: _, ...rest } = menu;
return rest;
})
);
const childLevelMenus = computed<App.Global.Menu[]>(
() => routeStore.menus.find(menu => menu.key === activeFirstLevelMenuKey.value)?.children || []
);
const isActiveFirstLevelMenuHasChildren = computed(() => {
if (!activeFirstLevelMenuKey.value) {
return false;
}
const findItem = allMenus.value.find(item => item.key === activeFirstLevelMenuKey.value);
return Boolean(findItem?.children?.length);
});
watch(
() => route.name,
() => {
@@ -39,9 +59,12 @@ function useMixMenu() {
);
return {
allMenus,
firstLevelMenus,
childLevelMenus,
isActiveFirstLevelMenuHasChildren,
activeFirstLevelMenuKey,
setActiveFirstLevelMenuKey,
getActiveFirstLevelMenuKey,
menus
getActiveFirstLevelMenuKey
};
}

View File

@@ -18,7 +18,7 @@ function loginOrRegister() {
toLogin();
}
type DropdownKey = 'user-center' | 'logout';
type DropdownKey = 'logout';
type DropdownOption =
| {
@@ -33,15 +33,6 @@ type DropdownOption =
const options = computed(() => {
const opts: DropdownOption[] = [
{
label: $t('common.userCenter'),
key: 'user-center',
icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 })
},
{
type: 'divider',
key: 'divider'
},
{
label: $t('common.logout'),
key: 'logout',
@@ -68,6 +59,7 @@ function handleDropdown(key: DropdownKey) {
if (key === 'logout') {
logout();
} else {
// If your other options are jumps from other routes, they will be directly supported here
routerPushByKey(key);
}
}

View File

@@ -1,14 +1,11 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useFullscreen } from '@vueuse/core';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import HorizontalMenu from '../global-menu/base-menu.vue';
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
import GlobalLogo from '../global-logo/index.vue';
import GlobalBreadcrumb from '../global-breadcrumb/index.vue';
import GlobalSearch from '../global-search/index.vue';
import { useMixMenuContext } from '../../context';
import ThemeButton from './components/theme-button.vue';
import UserAvatar from './components/user-avatar.vue';
@@ -29,29 +26,15 @@ defineProps<Props>();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { isFullscreen, toggle } = useFullscreen();
const { menus } = useMixMenuContext();
const headerMenus = computed(() => {
if (themeStore.layout.mode === 'horizontal') {
return routeStore.menus;
}
if (themeStore.layout.mode === 'horizontal-mix') {
return menus.value;
}
return [];
});
</script>
<template>
<DarkModeContainer class="h-full flex-y-center px-12px shadow-header">
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
<div v-if="showMenu" :id="GLOBAL_HEADER_MENU_ID" class="h-full flex-y-center flex-1-hidden"></div>
<div v-else class="h-full flex-y-center flex-1-hidden">
<MenuToggler v-if="showMenuToggler" :collapsed="appStore.siderCollapse" @click="appStore.toggleSiderCollapse" />
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
</div>
<div class="h-full flex-y-center justify-end">

View File

@@ -1,96 +0,0 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import type { MentionOption, MenuProps } from 'naive-ui';
import { SimpleScrollbar } from '@sa/materials';
import type { RouteKey } from '@elegant-router/types';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router';
defineOptions({
name: 'BaseMenu'
});
interface Props {
darkTheme?: boolean;
mode?: MenuProps['mode'];
menus: App.Global.Menu[];
}
const props = withDefaults(defineProps<Props>(), {
mode: 'vertical'
});
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { routerPushByKey } = useRouterPush();
const naiveMenus = computed(() => props.menus as unknown as MentionOption[]);
const isHorizontal = computed(() => props.mode === 'horizontal');
const siderCollapse = computed(() => themeStore.layout.mode === 'vertical' && appStore.siderCollapse);
const headerHeight = computed(() => `${themeStore.header.height}px`);
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
const expandedKeys = ref<string[]>([]);
function updateExpandedKeys() {
if (isHorizontal.value || siderCollapse.value || !selectedKey.value) {
expandedKeys.value = [];
return;
}
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
}
function handleClickMenu(key: RouteKey) {
const query = routeStore.getRouteQueryOfMetaByKey(key);
routerPushByKey(key, { query });
}
watch(
() => route.name,
() => {
updateExpandedKeys();
},
{ immediate: true }
);
</script>
<template>
<SimpleScrollbar>
<NMenu
v-model:expanded-keys="expandedKeys"
:mode="mode"
:value="selectedKey"
:collapsed="siderCollapse"
:collapsed-width="themeStore.sider.collapsedWidth"
:collapsed-icon-size="22"
:options="naiveMenus"
:inverted="darkTheme"
:indent="18"
responsive
@update:value="handleClickMenu"
/>
</SimpleScrollbar>
</template>
<style scoped>
:deep(.n-menu--horizontal) {
--n-item-height: v-bind(headerHeight) !important;
}
</style>

View File

@@ -3,31 +3,29 @@ import { computed } from 'vue';
import { createReusableTemplate } from '@vueuse/core';
import { SimpleScrollbar } from '@sa/materials';
import { transformColorWithOpacity } from '@sa/color';
import { useAppStore } from '@/store/modules/app';
import { useRouteStore } from '@/store/modules/route';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({
name: 'FirstLevelMenu'
});
interface Props {
menus: App.Global.Menu[];
activeMenuKey?: string;
inverted?: boolean;
siderCollapse?: boolean;
darkMode?: boolean;
themeColor: string;
}
defineProps<Props>();
const props = defineProps<Props>();
interface Emits {
(e: 'select', menu: App.Global.Menu): boolean;
(e: 'toggleSiderCollapse'): void;
}
const emit = defineEmits<Emits>();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
interface MixMenuItemProps {
/** Menu item label */
label: App.Global.Menu['label'];
@@ -36,12 +34,12 @@ interface MixMenuItemProps {
/** Active menu item */
active: boolean;
/** Mini size */
isMini: boolean;
isMini?: boolean;
}
const [DefineMixMenuItem, MixMenuItem] = createReusableTemplate<MixMenuItemProps>();
const selectedBgColor = computed(() => {
const { darkMode, themeColor } = themeStore;
const { darkMode, themeColor } = props;
const light = transformColorWithOpacity(themeColor, 0.1, '#ffffff');
const dark = transformColorWithOpacity(themeColor, 0.3, '#000000');
@@ -52,6 +50,10 @@ const selectedBgColor = computed(() => {
function handleClickMixMenu(menu: App.Global.Menu) {
emit('select', menu);
}
function toggleSiderCollapse() {
emit('toggleSiderCollapse');
}
</script>
<template>
@@ -80,21 +82,21 @@ function handleClickMixMenu(menu: App.Global.Menu) {
<slot></slot>
<SimpleScrollbar>
<MixMenuItem
v-for="menu in routeStore.menus"
v-for="menu in menus"
:key="menu.key"
:label="menu.label"
:icon="menu.icon"
:active="menu.key === activeMenuKey"
:is-mini="appStore.siderCollapse"
:is-mini="siderCollapse"
@click="handleClickMixMenu(menu)"
/>
</SimpleScrollbar>
<MenuToggler
arrow-icon
:collapsed="appStore.siderCollapse"
:collapsed="siderCollapse"
:z-index="99"
:class="{ 'text-white:88 !hover:text-white': inverted }"
@click="appStore.toggleSiderCollapse"
@click="toggleSiderCollapse"
/>
</div>
</template>

View File

@@ -1,28 +0,0 @@
<script setup lang="ts">
import { useRouterPush } from '@/hooks/common/router';
import { useMixMenuContext } from '../../context';
import FirstLevelMenu from './first-level-menu.vue';
defineOptions({
name: 'HorizontalMixMenu'
});
const { activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
const { routerPushByKey } = useRouterPush();
function handleSelectMixMenu(menu: App.Global.Menu) {
setActiveFirstLevelMenuKey(menu.key);
if (!menu.children?.length) {
routerPushByKey(menu.routeKey);
}
}
</script>
<template>
<FirstLevelMenu :active-menu-key="activeFirstLevelMenuKey" @select="handleSelectMixMenu">
<slot></slot>
</FirstLevelMenu>
</template>
<style scoped></style>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import { computed } from 'vue';
import type { Component } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import VerticalMenu from './modules/vertical-menu.vue';
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
import HorizontalMenu from './modules/horizontal-menu.vue';
import HorizontalMixMenu from './modules/horizontal-mix-menu.vue';
import ReversedHorizontalMixMenu from './modules/reversed-horizontal-mix-menu.vue';
defineOptions({
name: 'GlobalMenu'
});
const appStore = useAppStore();
const themeStore = useThemeStore();
const activeMenu = computed(() => {
const menuMap: Record<UnionKey.ThemeLayoutMode, Component> = {
vertical: VerticalMenu,
'vertical-mix': VerticalMixMenu,
horizontal: HorizontalMenu,
'horizontal-mix': themeStore.layout.reverseHorizontalMix ? ReversedHorizontalMixMenu : HorizontalMixMenu
};
return menuMap[themeStore.layout.mode];
});
const reRenderVertical = computed(() => themeStore.layout.mode === 'vertical' && appStore.isMobile);
</script>
<template>
<component :is="activeMenu" :key="reRenderVertical" />
</template>
<style scoped></style>

View File

@@ -0,0 +1,39 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router';
defineOptions({
name: 'HorizontalMenu'
});
const route = useRoute();
const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
</script>
<template>
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
<NMenu
mode="horizontal"
:value="selectedKey"
:options="routeStore.menus"
:indent="18"
responsive
@update:value="routerPushByKeyWithMetaQuery"
/>
</Teleport>
</template>
<style scoped></style>

View File

@@ -0,0 +1,63 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
import FirstLevelMenu from '../components/first-level-menu.vue';
import { useMixMenuContext } from '../../../context';
defineOptions({
name: 'HorizontalMixMenu'
});
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
function handleSelectMixMenu(menu: App.Global.Menu) {
setActiveFirstLevelMenuKey(menu.key);
if (!menu.children?.length) {
routerPushByKeyWithMetaQuery(menu.routeKey);
}
}
</script>
<template>
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
<NMenu
mode="horizontal"
:value="selectedKey"
:options="childLevelMenus"
:indent="18"
responsive
@update:value="routerPushByKeyWithMetaQuery"
/>
</Teleport>
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
<FirstLevelMenu
:menus="allMenus"
:active-menu-key="activeFirstLevelMenuKey"
:sider-collapse="appStore.siderCollapse"
:dark-mode="themeStore.darkMode"
:theme-color="themeStore.themeColor"
@select="handleSelectMixMenu"
@toggle-sider-collapse="appStore.toggleSiderCollapse"
/>
</Teleport>
</template>
<style scoped></style>

View File

@@ -0,0 +1,94 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import type { RouteKey } from '@elegant-router/types';
import { SimpleScrollbar } from '@sa/materials';
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router';
import { useMixMenuContext } from '../../../context';
defineOptions({
name: 'ReversedHorizontalMixMenu'
});
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const {
firstLevelMenus,
childLevelMenus,
activeFirstLevelMenuKey,
setActiveFirstLevelMenuKey,
isActiveFirstLevelMenuHasChildren
} = useMixMenuContext();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
function handleSelectMixMenu(key: RouteKey) {
setActiveFirstLevelMenuKey(key);
if (!isActiveFirstLevelMenuHasChildren.value) {
routerPushByKeyWithMetaQuery(key);
}
}
const expandedKeys = ref<string[]>([]);
function updateExpandedKeys() {
if (appStore.siderCollapse || !selectedKey.value) {
expandedKeys.value = [];
return;
}
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
}
watch(
() => route.name,
() => {
updateExpandedKeys();
},
{ immediate: true }
);
</script>
<template>
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
<NMenu
mode="horizontal"
:value="activeFirstLevelMenuKey"
:options="firstLevelMenus"
:indent="18"
responsive
@update:value="handleSelectMixMenu"
/>
</Teleport>
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
<SimpleScrollbar>
<NMenu
v-model:expanded-keys="expandedKeys"
mode="vertical"
:value="selectedKey"
:collapsed="appStore.siderCollapse"
:collapsed-width="themeStore.sider.collapsedWidth"
:collapsed-icon-size="22"
:options="childLevelMenus"
:indent="18"
@update:value="routerPushByKeyWithMetaQuery"
/>
</SimpleScrollbar>
</Teleport>
</template>
<style scoped></style>

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { SimpleScrollbar } from '@sa/materials';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router';
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
defineOptions({
name: 'VerticalMenu'
});
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
const expandedKeys = ref<string[]>([]);
function updateExpandedKeys() {
if (appStore.siderCollapse || !selectedKey.value) {
expandedKeys.value = [];
return;
}
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
}
watch(
() => route.name,
() => {
updateExpandedKeys();
},
{ immediate: true }
);
</script>
<template>
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
<SimpleScrollbar>
<NMenu
v-model:expanded-keys="expandedKeys"
mode="vertical"
:value="selectedKey"
:collapsed="appStore.siderCollapse"
:collapsed-width="themeStore.sider.collapsedWidth"
:collapsed-icon-size="22"
:options="routeStore.menus"
:inverted="inverted"
:indent="18"
@update:value="routerPushByKeyWithMetaQuery"
/>
</SimpleScrollbar>
</Teleport>
</template>
<style scoped></style>

View File

@@ -0,0 +1,139 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { SimpleScrollbar } from '@sa/materials';
import { useBoolean } from '@sa/hooks';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import { useMixMenuContext } from '../../../context';
import FirstLevelMenu from '../components/first-level-menu.vue';
import GlobalLogo from '../../global-logo/index.vue';
defineOptions({
name: 'VerticalMenuMix'
});
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
const {
allMenus,
childLevelMenus,
activeFirstLevelMenuKey,
setActiveFirstLevelMenuKey,
getActiveFirstLevelMenuKey
//
} = useMixMenuContext();
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
function handleSelectMixMenu(menu: App.Global.Menu) {
setActiveFirstLevelMenuKey(menu.key);
if (menu.children?.length) {
setDrawerVisible(true);
} else {
routerPushByKeyWithMetaQuery(menu.routeKey);
}
}
function handleResetActiveMenu() {
setDrawerVisible(false);
if (!appStore.mixSiderFixed) {
getActiveFirstLevelMenuKey();
}
}
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
const expandedKeys = ref<string[]>([]);
function updateExpandedKeys() {
if (appStore.siderCollapse || !selectedKey.value) {
expandedKeys.value = [];
return;
}
expandedKeys.value = routeStore.getSelectedMenuKeyPath(selectedKey.value);
}
watch(
() => route.name,
() => {
updateExpandedKeys();
},
{ immediate: true }
);
</script>
<template>
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
<FirstLevelMenu
:menus="allMenus"
:active-menu-key="activeFirstLevelMenuKey"
:inverted="inverted"
:sider-collapse="appStore.siderCollapse"
:dark-mode="themeStore.darkMode"
:theme-color="themeStore.themeColor"
@select="handleSelectMixMenu"
@toggle-sider-collapse="appStore.toggleSiderCollapse"
>
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
</FirstLevelMenu>
<div
class="relative h-full transition-width-300"
:style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
>
<DarkModeContainer
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
:inverted="inverted"
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
>
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
<PinToggler
:pin="appStore.mixSiderFixed"
:class="{ 'text-white:88 !hover:text-white': inverted }"
@click="appStore.toggleMixSiderFixed"
/>
</header>
<SimpleScrollbar>
<NMenu
v-model:expanded-keys="expandedKeys"
mode="vertical"
:value="selectedKey"
:options="childLevelMenus"
:collapsed="appStore.siderCollapse"
:collapsed-width="themeStore.sider.collapsedWidth"
:collapsed-icon-size="22"
:inverted="inverted"
:indent="18"
@update:value="routerPushByKeyWithMetaQuery"
/>
</SimpleScrollbar>
</DarkModeContainer>
</div>
</div>
</Teleport>
</template>
<style scoped></style>

View File

@@ -1,72 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useBoolean } from '@sa/hooks';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
import { useMixMenuContext } from '../../context';
import FirstLevelMenu from './first-level-menu.vue';
import BaseMenu from './base-menu.vue';
defineOptions({
name: 'VerticalMixMenu'
});
const appStore = useAppStore();
const themeStore = useThemeStore();
const { routerPushByKey } = useRouterPush();
const { bool: drawerVisible, setBool: setDrawerVisible } = useBoolean();
const { menus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey, getActiveFirstLevelMenuKey } = useMixMenuContext();
const siderInverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
const hasMenus = computed(() => menus.value.length > 0);
const showDrawer = computed(() => hasMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
function handleSelectMixMenu(menu: App.Global.Menu) {
setActiveFirstLevelMenuKey(menu.key);
if (menu.children?.length) {
setDrawerVisible(true);
} else {
routerPushByKey(menu.routeKey);
}
}
function handleResetActiveMenu() {
getActiveFirstLevelMenuKey();
setDrawerVisible(false);
}
</script>
<template>
<div class="h-full flex" @mouseleave="handleResetActiveMenu">
<FirstLevelMenu :active-menu-key="activeFirstLevelMenuKey" :inverted="siderInverted" @select="handleSelectMixMenu">
<slot></slot>
</FirstLevelMenu>
<div
class="relative h-full transition-width-300"
:style="{ width: appStore.mixSiderFixed && hasMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
>
<DarkModeContainer
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"
:inverted="siderInverted"
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
>
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
<PinToggler
:pin="appStore.mixSiderFixed"
:class="{ 'text-white:88 !hover:text-white': siderInverted }"
@click="appStore.toggleMixSiderFixed"
/>
</header>
<BaseMenu :dark-theme="siderInverted" :menus="menus" />
</DarkModeContainer>
</div>
</div>
</template>
<style scoped></style>

View File

@@ -2,11 +2,8 @@
import { computed } from 'vue';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import GlobalLogo from '../global-logo/index.vue';
import VerticalMenu from '../global-menu/base-menu.vue';
import VerticalMixMenu from '../global-menu/vertical-mix-menu.vue';
import HorizontalMixMenu from '../global-menu/horizontal-mix-menu.vue';
defineOptions({
name: 'GlobalSider'
@@ -14,12 +11,12 @@ defineOptions({
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
const darkMenu = computed(() => !themeStore.darkMode && !isHorizontalMix.value && themeStore.sider.inverted);
const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
</script>
<template>
@@ -29,11 +26,7 @@ const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
:show-title="!appStore.siderCollapse"
:style="{ height: themeStore.header.height + 'px' }"
/>
<VerticalMixMenu v-if="isVerticalMix">
<GlobalLogo :show-title="false" :style="{ height: themeStore.header.height + 'px' }" />
</VerticalMixMenu>
<HorizontalMixMenu v-else-if="isHorizontalMix" />
<VerticalMenu v-else :dark-theme="darkMenu" :menus="routeStore.menus" />
<div :id="GLOBAL_SIDER_MENU_ID" :class="menuWrapperClass"></div>
</DarkModeContainer>
</template>

View File

@@ -14,7 +14,7 @@ defineProps<Props>();
<template>
<div class="w-full flex-y-center justify-between">
<div>
<span class="pr-8px text-base_text">{{ label }}</span>
<span class="pr-8px text-base-text">{{ label }}</span>
<slot name="suffix"></slot>
</div>
<slot></slot>

View File

@@ -25,6 +25,10 @@ function handleGrayscaleChange(value: boolean) {
themeStore.setGrayscale(value);
}
function handleColourWeaknessChange(value: boolean) {
themeStore.setColourWeakness(value);
}
const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layout.mode.includes('vertical'));
</script>
@@ -53,6 +57,9 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo
<SettingItem :label="$t('theme.grayscale')">
<NSwitch :value="themeStore.grayscale" @update:value="handleGrayscaleChange" />
</SettingItem>
<SettingItem :label="$t('theme.colourWeakness')">
<NSwitch :value="themeStore.colourWeakness" @update:value="handleColourWeaknessChange" />
</SettingItem>
</div>
</template>

View File

@@ -3,6 +3,7 @@ import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { $t } from '@/locales';
import LayoutModeCard from '../components/layout-mode-card.vue';
import SettingItem from '../components/setting-item.vue';
defineOptions({
name: 'LayoutMode'
@@ -10,6 +11,10 @@ defineOptions({
const appStore = useAppStore();
const themeStore = useThemeStore();
function handleReverseHorizontalMixChange(value: boolean) {
themeStore.setLayoutReverseHorizontalMix(value);
}
</script>
<template>
@@ -44,6 +49,13 @@ const themeStore = useThemeStore();
</div>
</template>
</LayoutModeCard>
<SettingItem
v-if="themeStore.layout.mode === 'horizontal-mix'"
:label="$t('theme.layoutMode.reverseHorizontalMix')"
class="mt-16px"
>
<NSwitch :value="themeStore.layout.reverseHorizontalMix" @update:value="handleReverseHorizontalMixChange" />
</SettingItem>
</template>
<style scoped>

View File

@@ -101,6 +101,19 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
>
<NSwitch v-model:value="themeStore.footer.right" />
</SettingItem>
<SettingItem v-if="themeStore.watermark" key="8" :label="$t('theme.watermark.visible')">
<NSwitch v-model:value="themeStore.watermark.visible" />
</SettingItem>
<SettingItem v-if="themeStore.watermark?.visible" key="8-1" :label="$t('theme.watermark.text')">
<NInput
v-model:value="themeStore.watermark.text"
autosize
type="text"
size="small"
class="w-120px"
placeholder="SoybeanAdmin"
/>
</SettingItem>
</TransitionGroup>
</template>

View File

@@ -23,6 +23,8 @@ const local: App.I18n.Schema = {
deleteSuccess: 'Delete Success',
confirmDelete: 'Are you sure you want to delete?',
edit: 'Edit',
warning: 'Warning',
error: 'Error',
index: 'Index',
keywordSearch: 'Please enter keyword',
logout: 'Logout',
@@ -63,12 +65,14 @@ const local: App.I18n.Schema = {
auto: 'Follow System'
},
grayscale: 'Grayscale',
colourWeakness: 'Colour Weakness',
layoutMode: {
title: 'Layout Mode',
vertical: 'Vertical Menu Mode',
horizontal: 'Horizontal Menu Mode',
'vertical-mix': 'Vertical Mix Menu Mode',
'horizontal-mix': 'Horizontal Mix menu Mode'
'horizontal-mix': 'Horizontal Mix menu Mode',
reverseHorizontalMix: 'Reverse first level menus and child level menus position'
},
recommendColor: 'Apply Recommended Color Algorithm',
recommendColorDesc: 'The recommended color algorithm refers to',
@@ -131,6 +135,10 @@ const local: App.I18n.Schema = {
height: 'Footer Height',
right: 'Right Footer'
},
watermark: {
visible: 'Watermark Full Screen Visible',
text: 'Watermark Text'
},
themeDrawerTitle: 'Theme Configuration',
pageFunTitle: 'Page Function',
configOperation: {
@@ -146,42 +154,7 @@ const local: App.I18n.Schema = {
404: 'Page Not Found',
500: 'Server Error',
'iframe-page': 'Iframe',
home: 'Home',
document: 'Document',
document_project: 'Project Document',
'document_project-link': 'Project Document(External Link)',
document_vue: 'Vue Document',
document_vite: 'Vite Document',
document_unocss: 'UnoCSS Document',
document_naive: 'Naive UI Document',
document_antd: 'Ant Design Vue Document',
'user-center': 'User Center',
about: 'About',
function: 'System Function',
function_tab: 'Tab',
'function_multi-tab': 'Multi Tab',
'function_hide-child': 'Hide Child',
'function_hide-child_one': 'Hide Child',
'function_hide-child_two': 'Two',
'function_hide-child_three': 'Three',
function_request: 'Request',
'function_toggle-auth': 'Toggle Auth',
'function_super-page': 'Super Admin Visible',
manage: 'System Manage',
manage_user: 'User Manage',
'manage_user-detail': 'User Detail',
manage_role: 'Role Manage',
manage_menu: 'Menu Manage',
'multi-menu': 'Multi Menu',
'multi-menu_first': 'Menu One',
'multi-menu_first_child': 'Menu One Child',
'multi-menu_second': 'Menu Two',
'multi-menu_second_child': 'Menu Two Child',
'multi-menu_second_child_home': 'Menu Two Child Home',
exception: 'Exception',
exception_403: '403',
exception_404: '404',
exception_500: '500'
home: 'Home'
},
page: {
login: {
@@ -230,20 +203,9 @@ const local: App.I18n.Schema = {
title: 'Bind WeChat'
}
},
about: {
title: 'About',
introduction: `SoybeanAdmin is an elegant and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite5, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. SoybeanAdmin provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.`,
projectInfo: {
title: 'Project Info',
version: 'Version',
latestBuildTime: 'Latest Build Time',
githubLink: 'Github Link',
previewLink: 'Preview Link'
},
prdDep: 'Production Dependency',
devDep: 'Development Dependency'
},
home: {
branchDesc:
'For the convenience of everyone in developing and updating the merge, we have streamlined the code of the main branch, only retaining the homepage menu, and the rest of the content has been moved to the example branch for maintenance. The preview address displays the content of the example branch.',
greeting: 'Good morning, {userName}, today is another day full of vitality!',
weatherDesc: 'Today is cloudy to clear, 20℃ - 25℃!',
projectCount: 'Project Count',
@@ -269,163 +231,6 @@ const local: App.I18n.Schema = {
desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
},
creativity: 'Creativity'
},
function: {
tab: {
tabOperate: {
title: 'Tab Operation',
addTab: 'Add Tab',
addTabDesc: 'To about page',
closeTab: 'Close Tab',
closeCurrentTab: 'Close Current Tab',
closeAboutTab: 'Close "About" Tab',
addMultiTab: 'Add Multi Tab',
addMultiTabDesc1: 'To MultiTab page',
addMultiTabDesc2: 'To MultiTab page(with query params)'
},
tabTitle: {
title: 'Tab Title',
changeTitle: 'Change Title',
change: 'Change',
resetTitle: 'Reset Title',
reset: 'Reset'
}
},
multiTab: {
routeParam: 'Route Param',
backTab: 'Back function_tab'
},
toggleAuth: {
toggleAccount: 'Toggle Account',
authHook: 'Auth Hook Function `hasAuth`',
superAdminVisible: 'Super Admin Visible',
adminVisible: 'Admin Visible',
adminOrUserVisible: 'Admin and User Visible'
},
request: {
repeatedErrorOccurOnce: 'Repeated Request Error Occurs Once',
repeatedError: 'Repeated Request Error',
repeatedErrorMsg1: 'Custom Request Error 1',
repeatedErrorMsg2: 'Custom Request Error 2'
}
},
manage: {
common: {
status: {
enable: 'Enable',
disable: 'Disable'
}
},
role: {
title: 'Role List',
roleName: 'Role Name',
roleCode: 'Role Code',
roleStatus: 'Role Status',
roleDesc: 'Role Description',
menuAuth: 'Menu Auth',
buttonAuth: 'Button Auth',
form: {
roleName: 'Please enter role name',
roleCode: 'Please enter role code',
roleStatus: 'Please select role status',
roleDesc: 'Please enter role description'
},
addRole: 'Add Role',
editRole: 'Edit Role'
},
user: {
title: 'User List',
userName: 'User Name',
userGender: 'Gender',
nickName: 'Nick Name',
userPhone: 'Phone Number',
userEmail: 'Email',
userStatus: 'User Status',
userRole: 'User Role',
form: {
userName: 'Please enter user name',
userGender: 'Please select gender',
nickName: 'Please enter nick name',
userPhone: 'Please enter phone number',
userEmail: 'Please enter email',
userStatus: 'Please select user status',
userRole: 'Please select user role'
},
addUser: 'Add User',
editUser: 'Edit User',
gender: {
male: 'Male',
female: 'Female'
}
},
menu: {
home: 'Home',
title: 'Menu List',
id: 'ID',
parentId: 'Parent ID',
menuType: 'Menu Type',
menuName: 'Menu Name',
routeName: 'Route Name',
routePath: 'Route Path',
pathParam: 'Path Param',
layout: 'Layout Component',
page: 'Page Component',
i18nKey: 'I18n Key',
icon: 'Icon',
localIcon: 'Local Icon',
iconTypeTitle: 'Icon Type',
order: 'Order',
constant: 'Constant',
keepAlive: 'Keep Alive',
href: 'Href',
hideInMenu: 'Hide In Menu',
activeMenu: 'Active Menu',
multiTab: 'Multi Tab',
fixedIndexInTab: 'Fixed Index In Tab',
query: 'Query Params',
button: 'Button',
buttonCode: 'Button Code',
buttonDesc: 'Button Desc',
menuStatus: 'Menu Status',
form: {
home: 'Please select home',
menuType: 'Please select menu type',
menuName: 'Please enter menu name',
routeName: 'Please enter route name',
routePath: 'Please enter route path',
pathParam: 'Please enter path param',
page: 'Please select page component',
layout: 'Please select layout component',
i18nKey: 'Please enter i18n key',
icon: 'Please enter iconify name',
localIcon: 'Please enter local icon name',
order: 'Please enter order',
keepAlive: 'Please select whether to cache route',
href: 'Please enter href',
hideInMenu: 'Please select whether to hide menu',
activeMenu: 'Please select route name of the highlighted menu',
multiTab: 'Please select whether to support multiple tabs',
fixedInTab: 'Please select whether to fix in the tab',
fixedIndexInTab: 'Please enter the index fixed in the tab',
queryKey: 'Please enter route parameter Key',
queryValue: 'Please enter route parameter Value',
button: 'Please select whether it is a button',
buttonCode: 'Please enter button code',
buttonDesc: 'Please enter button description',
menuStatus: 'Please select menu status'
},
addMenu: 'Add Menu',
editMenu: 'Edit Menu',
addChildMenu: 'Add Child Menu',
type: {
directory: 'Directory',
menu: 'Menu'
},
iconType: {
iconify: 'Iconify Icon',
local: 'Local Icon'
}
}
}
},
form: {

View File

@@ -23,6 +23,8 @@ const local: App.I18n.Schema = {
deleteSuccess: '删除成功',
confirmDelete: '确认删除吗?',
edit: '编辑',
warning: '警告',
error: '错误',
index: '序号',
keywordSearch: '请输入关键词搜索',
logout: '退出登录',
@@ -62,13 +64,15 @@ const local: App.I18n.Schema = {
dark: '暗黑模式',
auto: '跟随系统'
},
grayscale: '灰模式',
grayscale: '灰模式',
colourWeakness: '色弱模式',
layoutMode: {
title: '布局模式',
vertical: '左侧菜单模式',
'vertical-mix': '左侧菜单混合模式',
horizontal: '顶部菜单模式',
'horizontal-mix': '顶部菜单混合模式'
'horizontal-mix': '顶部菜单混合模式',
reverseHorizontalMix: '一级菜单与子级菜单位置反转'
},
recommendColor: '应用推荐算法的颜色',
recommendColorDesc: '推荐颜色的算法参照',
@@ -131,6 +135,10 @@ const local: App.I18n.Schema = {
height: '底部高度',
right: '底部局右'
},
watermark: {
visible: '显示全屏水印',
text: '水印文本'
},
themeDrawerTitle: '主题配置',
pageFunTitle: '页面功能',
configOperation: {
@@ -146,42 +154,7 @@ const local: App.I18n.Schema = {
404: '页面不存在',
500: '服务器错误',
'iframe-page': '外链页面',
home: '首页',
document: '文档',
document_project: '项目文档',
'document_project-link': '项目文档(外链)',
document_vue: 'Vue文档',
document_vite: 'Vite文档',
document_unocss: 'UnoCSS文档',
document_naive: 'Naive UI文档',
document_antd: 'Ant Design Vue文档',
'user-center': '个人中心',
about: '关于',
function: '系统功能',
function_tab: '标签页',
'function_multi-tab': '多标签页',
'function_hide-child': '隐藏子菜单',
'function_hide-child_one': '隐藏子菜单',
'function_hide-child_two': '菜单二',
'function_hide-child_three': '菜单三',
function_request: '请求',
'function_toggle-auth': '切换权限',
'function_super-page': '超级管理员可见',
manage: '系统管理',
manage_user: '用户管理',
'manage_user-detail': '用户详情',
manage_role: '角色管理',
manage_menu: '菜单管理',
'multi-menu': '多级菜单',
'multi-menu_first': '菜单一',
'multi-menu_first_child': '菜单一子菜单',
'multi-menu_second': '菜单二',
'multi-menu_second_child': '菜单二子菜单',
'multi-menu_second_child_home': '菜单二子菜单首页',
exception: '异常页',
exception_403: '403',
exception_404: '404',
exception_500: '500'
home: '首页'
},
page: {
login: {
@@ -230,20 +203,9 @@ const local: App.I18n.Schema = {
title: '绑定微信'
}
},
about: {
title: '关于',
introduction: `SoybeanAdmin 是一个优雅且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。SoybeanAdmin 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。`,
projectInfo: {
title: '项目信息',
version: '版本',
latestBuildTime: '最新构建时间',
githubLink: 'Github 地址',
previewLink: '预览地址'
},
prdDep: '生产依赖',
devDep: '开发依赖'
},
home: {
branchDesc:
'为了方便大家开发和更新合并我们对main分支的代码进行了精简只保留了首页菜单其余内容已移至example分支进行维护。预览地址显示的内容即为example分支的内容。',
greeting: '早安,{userName}, 今天又是充满活力的一天!',
weatherDesc: '今日多云转晴20℃ - 25℃!',
projectCount: '项目数',
@@ -269,163 +231,6 @@ const local: App.I18n.Schema = {
desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!'
},
creativity: '创意'
},
function: {
tab: {
tabOperate: {
title: '标签页操作',
addTab: '添加标签页',
addTabDesc: '跳转到关于页面',
closeTab: '关闭标签页',
closeCurrentTab: '关闭当前标签页',
closeAboutTab: '关闭"关于"标签页',
addMultiTab: '添加多标签页',
addMultiTabDesc1: '跳转到多标签页页面',
addMultiTabDesc2: '跳转到多标签页页面(带有查询参数)'
},
tabTitle: {
title: '标签页标题',
changeTitle: '修改标题',
change: '修改',
resetTitle: '重置标题',
reset: '重置'
}
},
multiTab: {
routeParam: '路由参数',
backTab: '返回 function_tab'
},
toggleAuth: {
toggleAccount: '切换账号',
authHook: '权限钩子函数 `hasAuth`',
superAdminVisible: '超级管理员可见',
adminVisible: '管理员可见',
adminOrUserVisible: '管理员和用户可见'
},
request: {
repeatedErrorOccurOnce: '重复请求错误只出现一次',
repeatedError: '重复请求错误',
repeatedErrorMsg1: '自定义请求错误 1',
repeatedErrorMsg2: '自定义请求错误 2'
}
},
manage: {
common: {
status: {
enable: '启用',
disable: '禁用'
}
},
role: {
title: '角色列表',
roleName: '角色名称',
roleCode: '角色编码',
roleStatus: '角色状态',
roleDesc: '角色描述',
menuAuth: '菜单权限',
buttonAuth: '按钮权限',
form: {
roleName: '请输入角色名称',
roleCode: '请输入角色编码',
roleStatus: '请选择角色状态',
roleDesc: '请输入角色描述'
},
addRole: '新增角色',
editRole: '编辑角色'
},
user: {
title: '用户列表',
userName: '用户名',
userGender: '性别',
nickName: '昵称',
userPhone: '手机号',
userEmail: '邮箱',
userStatus: '用户状态',
userRole: '用户角色',
form: {
userName: '请输入用户名',
userGender: '请选择性别',
nickName: '请输入昵称',
userPhone: '请输入手机号',
userEmail: '请输入邮箱',
userStatus: '请选择用户状态',
userRole: '请选择用户角色'
},
addUser: '新增用户',
editUser: '编辑用户',
gender: {
male: '男',
female: '女'
}
},
menu: {
home: '首页',
title: '菜单列表',
id: 'ID',
parentId: '父级菜单ID',
menuType: '菜单类型',
menuName: '菜单名称',
routeName: '路由名称',
routePath: '路由路径',
pathParam: '路径参数',
layout: '布局',
page: '页面组件',
i18nKey: '国际化key',
icon: '图标',
localIcon: '本地图标',
iconTypeTitle: '图标类型',
order: '排序',
constant: '常量路由',
keepAlive: '缓存路由',
href: '外链',
hideInMenu: '隐藏菜单',
activeMenu: '高亮的菜单',
multiTab: '支持多页签',
fixedIndexInTab: '固定在页签中的序号',
query: '路由参数',
button: '按钮',
buttonCode: '按钮编码',
buttonDesc: '按钮描述',
menuStatus: '菜单状态',
form: {
home: '请选择首页',
menuType: '请选择菜单类型',
menuName: '请输入菜单名称',
routeName: '请输入路由名称',
routePath: '请输入路由路径',
pathParam: '请输入路径参数',
page: '请选择页面组件',
layout: '请选择布局组件',
i18nKey: '请输入国际化key',
icon: '请输入图标',
localIcon: '请选择本地图标',
order: '请输入排序',
keepAlive: '请选择是否缓存路由',
href: '请输入外链',
hideInMenu: '请选择是否隐藏菜单',
activeMenu: '请选择高亮的菜单的路由名称',
multiTab: '请选择是否支持多标签',
fixedInTab: '请选择是否固定在页签中',
fixedIndexInTab: '请输入固定在页签中的序号',
queryKey: '请输入路由参数Key',
queryValue: '请输入路由参数Value',
button: '请选择是否按钮',
buttonCode: '请输入按钮编码',
buttonDesc: '请输入按钮描述',
menuStatus: '请选择菜单状态'
},
addMenu: '新增菜单',
editMenu: '编辑菜单',
addChildMenu: '新增子菜单',
type: {
directory: '目录',
menu: '菜单'
},
iconType: {
iconify: 'iconify图标',
local: '本地图标'
}
}
}
},
form: {

View File

@@ -3,38 +3,51 @@ import { NButton } from 'naive-ui';
import { $t } from '../locales';
export function setupAppVersionNotification() {
let isShow = false;
document.addEventListener('visibilitychange', async () => {
const preConditions = [!isShow, document.visibilityState === 'visible', !import.meta.env.DEV];
if (!preConditions.every(Boolean)) return;
const buildTime = await getHtmlBuildTime();
if (!import.meta.env.DEV && buildTime !== BUILD_TIME && document.visibilityState === 'visible') {
const n = window.$notification?.create({
title: $t('system.updateTitle'),
content: $t('system.updateContent'),
action() {
return h('div', { style: { display: 'flex', justifyContent: 'end', gap: '12px', width: '325px' } }, [
h(
NButton,
{
onClick() {
n?.destroy();
}
},
() => $t('system.updateCancel')
),
h(
NButton,
{
type: 'primary',
onClick() {
location.reload();
}
},
() => $t('system.updateConfirm')
)
]);
}
});
if (buildTime === BUILD_TIME) {
return;
}
isShow = true;
const n = window.$notification?.create({
title: $t('system.updateTitle'),
content: $t('system.updateContent'),
action() {
return h('div', { style: { display: 'flex', justifyContent: 'end', gap: '12px', width: '325px' } }, [
h(
NButton,
{
onClick() {
n?.destroy();
}
},
() => $t('system.updateCancel')
),
h(
NButton,
{
type: 'primary',
onClick() {
location.reload();
}
},
() => $t('system.updateConfirm')
)
]);
},
onClose() {
isShow = false;
}
});
});
}

View File

@@ -20,21 +20,5 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
500: () => import("@/views/_builtin/500/index.vue"),
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
login: () => import("@/views/_builtin/login/index.vue"),
about: () => import("@/views/about/index.vue"),
"function_hide-child_one": () => import("@/views/function/hide-child/one/index.vue"),
"function_hide-child_three": () => import("@/views/function/hide-child/three/index.vue"),
"function_hide-child_two": () => import("@/views/function/hide-child/two/index.vue"),
"function_multi-tab": () => import("@/views/function/multi-tab/index.vue"),
function_request: () => import("@/views/function/request/index.vue"),
"function_super-page": () => import("@/views/function/super-page/index.vue"),
function_tab: () => import("@/views/function/tab/index.vue"),
"function_toggle-auth": () => import("@/views/function/toggle-auth/index.vue"),
home: () => import("@/views/home/index.vue"),
manage_menu: () => import("@/views/manage/menu/index.vue"),
manage_role: () => import("@/views/manage/role/index.vue"),
"manage_user-detail": () => import("@/views/manage/user-detail/[id].vue"),
manage_user: () => import("@/views/manage/user/index.vue"),
"multi-menu_first_child": () => import("@/views/multi-menu/first_child/index.vue"),
"multi-menu_second_child_home": () => import("@/views/multi-menu/second_child_home/index.vue"),
"user-center": () => import("@/views/user-center/index.vue"),
};

View File

@@ -39,135 +39,6 @@ export const generatedRoutes: GeneratedRoute[] = [
hideInMenu: true
}
},
{
name: 'about',
path: '/about',
component: 'layout.base$view.about',
meta: {
title: 'about',
i18nKey: 'route.about',
icon: 'fluent:book-information-24-regular',
order: 10
}
},
{
name: 'function',
path: '/function',
component: 'layout.base',
meta: {
title: 'function',
i18nKey: 'route.function',
icon: 'icon-park-outline:all-application',
order: 6
},
children: [
{
name: 'function_hide-child',
path: '/function/hide-child',
meta: {
title: 'function_hide-child',
i18nKey: 'route.function_hide-child',
icon: 'material-symbols:filter-list-off',
order: 2
},
redirect: '/function/hide-child/one',
children: [
{
name: 'function_hide-child_one',
path: '/function/hide-child/one',
component: 'view.function_hide-child_one',
meta: {
title: 'function_hide-child_one',
i18nKey: 'route.function_hide-child_one',
icon: 'material-symbols:filter-list-off',
hideInMenu: true,
activeMenu: 'function_hide-child'
}
},
{
name: 'function_hide-child_three',
path: '/function/hide-child/three',
component: 'view.function_hide-child_three',
meta: {
title: 'function_hide-child_three',
i18nKey: 'route.function_hide-child_three',
hideInMenu: true,
activeMenu: 'function_hide-child'
}
},
{
name: 'function_hide-child_two',
path: '/function/hide-child/two',
component: 'view.function_hide-child_two',
meta: {
title: 'function_hide-child_two',
i18nKey: 'route.function_hide-child_two',
hideInMenu: true,
activeMenu: 'function_hide-child'
}
}
]
},
{
name: 'function_multi-tab',
path: '/function/multi-tab',
component: 'view.function_multi-tab',
meta: {
title: 'function_multi-tab',
i18nKey: 'route.function_multi-tab',
icon: 'ic:round-tab',
multiTab: true,
hideInMenu: true,
activeMenu: 'function_tab'
}
},
{
name: 'function_request',
path: '/function/request',
component: 'view.function_request',
meta: {
title: 'function_request',
i18nKey: 'route.function_request',
icon: 'carbon:network-overlay',
order: 3
}
},
{
name: 'function_super-page',
path: '/function/super-page',
component: 'view.function_super-page',
meta: {
title: 'function_super-page',
i18nKey: 'route.function_super-page',
icon: 'ic:round-supervisor-account',
order: 5,
roles: ['R_SUPER']
}
},
{
name: 'function_tab',
path: '/function/tab',
component: 'view.function_tab',
meta: {
title: 'function_tab',
i18nKey: 'route.function_tab',
icon: 'ic:round-tab',
order: 1
}
},
{
name: 'function_toggle-auth',
path: '/function/toggle-auth',
component: 'view.function_toggle-auth',
meta: {
title: 'function_toggle-auth',
i18nKey: 'route.function_toggle-auth',
icon: 'ic:round-construction',
order: 4
}
}
]
},
{
name: 'home',
path: '/home',
@@ -203,141 +74,5 @@ export const generatedRoutes: GeneratedRoute[] = [
constant: true,
hideInMenu: true
}
},
{
name: 'manage',
path: '/manage',
component: 'layout.base',
meta: {
title: 'manage',
i18nKey: 'route.manage',
icon: 'carbon:cloud-service-management',
order: 9,
roles: ['R_ADMIN']
},
children: [
{
name: 'manage_menu',
path: '/manage/menu',
component: 'view.manage_menu',
meta: {
title: 'manage_menu',
i18nKey: 'route.manage_menu',
icon: 'material-symbols:route',
order: 3,
roles: ['R_ADMIN'],
keepAlive: true
}
},
{
name: 'manage_role',
path: '/manage/role',
component: 'view.manage_role',
meta: {
title: 'manage_role',
i18nKey: 'route.manage_role',
icon: 'carbon:user-role',
order: 2,
roles: ['R_SUPER']
}
},
{
name: 'manage_user',
path: '/manage/user',
component: 'view.manage_user',
meta: {
title: 'manage_user',
i18nKey: 'route.manage_user',
icon: 'ic:round-manage-accounts',
order: 1,
roles: ['R_ADMIN']
}
},
{
name: 'manage_user-detail',
path: '/manage/user-detail/:id',
component: 'view.manage_user-detail',
props: true,
meta: {
title: 'manage_user-detail',
i18nKey: 'route.manage_user-detail',
hideInMenu: true,
roles: ['R_ADMIN'],
activeMenu: 'manage_user'
}
}
]
},
{
name: 'multi-menu',
path: '/multi-menu',
component: 'layout.base',
meta: {
title: 'multi-menu',
i18nKey: 'route.multi-menu',
order: 8
},
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
meta: {
title: 'multi-menu_first',
i18nKey: 'route.multi-menu_first',
order: 1
},
children: [
{
name: 'multi-menu_first_child',
path: '/multi-menu/first/child',
component: 'view.multi-menu_first_child',
meta: {
title: 'multi-menu_first_child',
i18nKey: 'route.multi-menu_first_child'
}
}
]
},
{
name: 'multi-menu_second',
path: '/multi-menu/second',
meta: {
title: 'multi-menu_second',
i18nKey: 'route.multi-menu_second',
order: 2
},
children: [
{
name: 'multi-menu_second_child',
path: '/multi-menu/second/child',
meta: {
title: 'multi-menu_second_child',
i18nKey: 'route.multi-menu_second_child'
},
children: [
{
name: 'multi-menu_second_child_home',
path: '/multi-menu/second/child/home',
component: 'view.multi-menu_second_child_home',
meta: {
title: 'multi-menu_second_child_home',
i18nKey: 'route.multi-menu_second_child_home'
}
}
]
}
]
}
]
},
{
name: 'user-center',
path: '/user-center',
component: 'layout.base$view.user-center',
meta: {
title: 'user-center',
i18nKey: 'route.user-center',
hideInMenu: true
}
}
];

View File

@@ -97,10 +97,13 @@ function transformElegantRouteToVueRoute(
if (component) {
if (isSingleLevelRoute(route)) {
const { layout, view } = getSingleLevelRouteComponent(component);
const singleLevelRoute: RouteRecordRaw = {
path,
component: layouts[layout],
meta: {
title: route.meta?.title || ''
},
children: [
{
name,
@@ -110,36 +113,35 @@ function transformElegantRouteToVueRoute(
} as RouteRecordRaw
]
};
return [singleLevelRoute];
}
if (isLayout(component)) {
const layoutName = getLayoutName(component);
vueRoute.component = layouts[layoutName];
}
if (isView(component)) {
const viewName = getViewName(component);
vueRoute.component = views[viewName];
}
}
} catch (error: any) {
console.error(`Error transforming route "${route.name}": ${error.toString()}`);
return [];
}
// add redirect to child
if (children?.length && !vueRoute.redirect) {
vueRoute.redirect = {
name: children[0].name
};
}
if (children?.length) {
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
@@ -161,47 +163,12 @@ function transformElegantRouteToVueRoute(
const routeMap: RouteMap = {
"root": "/",
"not-found": "/:pathMatch(.*)*",
"exception": "/exception",
"exception_403": "/exception/403",
"exception_404": "/exception/404",
"exception_500": "/exception/500",
"document": "/document",
"document_project": "/document/project",
"document_project-link": "/document/project-link",
"document_vue": "/document/vue",
"document_vite": "/document/vite",
"document_unocss": "/document/unocss",
"document_naive": "/document/naive",
"document_antd": "/document/antd",
"403": "/403",
"404": "/404",
"500": "/500",
"about": "/about",
"function": "/function",
"function_hide-child": "/function/hide-child",
"function_hide-child_one": "/function/hide-child/one",
"function_hide-child_three": "/function/hide-child/three",
"function_hide-child_two": "/function/hide-child/two",
"function_multi-tab": "/function/multi-tab",
"function_request": "/function/request",
"function_super-page": "/function/super-page",
"function_tab": "/function/tab",
"function_toggle-auth": "/function/toggle-auth",
"home": "/home",
"iframe-page": "/iframe-page/:url",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"manage": "/manage",
"manage_menu": "/manage/menu",
"manage_role": "/manage/role",
"manage_user": "/manage/user",
"manage_user-detail": "/manage/user-detail/:id",
"multi-menu": "/multi-menu",
"multi-menu_first": "/multi-menu/first",
"multi-menu_first_child": "/multi-menu/first/child",
"multi-menu_second": "/multi-menu/second",
"multi-menu_second_child": "/multi-menu/second/child",
"multi-menu_second_child_home": "/multi-menu/second/child/home",
"user-center": "/user-center"
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"
};
/**

View File

@@ -6,6 +6,7 @@ import type {
Router
} from 'vue-router';
import type { RouteKey, RoutePath } from '@elegant-router/types';
import { getRouteName } from '@/router/elegant/transform';
import { useAuthStore } from '@/store/modules/auth';
import { useRouteStore } from '@/store/modules/route';
import { localStg } from '@/utils/storage';
@@ -149,9 +150,7 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
// initialize the auth route requires the user to be logged in, if not, redirect to the login page
if (!isLogin) {
const loginRoute: RouteKey = 'login';
const redirect = to.fullPath;
const query: LocationQueryRaw = to.name !== loginRoute ? { redirect } : {};
const query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
const location: RouteLocationRaw = {
name: loginRoute,
@@ -197,3 +196,20 @@ function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNorma
next();
}
function getRouteQueryOfLoginRoute(to: RouteLocationNormalized, routeHome: RouteKey) {
const loginRoute: RouteKey = 'login';
const redirect = to.fullPath;
const [redirectPath, redirectQuery] = redirect.split('?');
const redirectName = getRouteName(redirectPath as RoutePath);
const isRedirectHome = routeHome === redirectName;
const query: LocationQueryRaw = to.name !== loginRoute && !isRedirectHome ? { redirect } : {};
if (isRedirectHome && redirectQuery) {
query.redirect = `/?${redirectQuery}`;
}
return query;
}

View File

@@ -8,160 +8,7 @@ import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [
{
name: 'exception',
path: '/exception',
component: 'layout.base',
meta: {
title: 'exception',
i18nKey: 'route.exception',
icon: 'ant-design:exception-outlined',
order: 7
},
children: [
{
name: 'exception_403',
path: '/exception/403',
component: 'view.403',
meta: {
title: 'exception_403',
i18nKey: 'route.exception_403',
icon: 'ic:baseline-block'
}
},
{
name: 'exception_404',
path: '/exception/404',
component: 'view.404',
meta: {
title: 'exception_404',
i18nKey: 'route.exception_404',
icon: 'ic:baseline-web-asset-off'
}
},
{
name: 'exception_500',
path: '/exception/500',
component: 'view.500',
meta: {
title: 'exception_500',
i18nKey: 'route.exception_500',
icon: 'ic:baseline-wifi-off'
}
}
]
},
{
name: 'document',
path: '/document',
component: 'layout.base',
meta: {
title: 'document',
i18nKey: 'route.document',
order: 2,
icon: 'mdi:file-document-multiple-outline'
},
children: [
{
name: 'document_antd',
path: '/document/antd',
component: 'view.iframe-page',
props: {
url: 'https://antdv.com/components/overview-cn'
},
meta: {
title: 'document_antd',
i18nKey: 'route.document_antd',
order: 7,
icon: 'logos:ant-design'
}
},
{
name: 'document_naive',
path: '/document/naive',
component: 'view.iframe-page',
props: {
url: 'https://www.naiveui.com/zh-CN/os-theme/docs/introduction'
},
meta: {
title: 'document_naive',
i18nKey: 'route.document_naive',
order: 6,
icon: 'logos:naiveui'
}
},
{
name: 'document_project',
path: '/document/project',
component: 'view.iframe-page',
props: {
url: 'https://docs.soybeanjs.cn/zh'
},
meta: {
title: 'document_project',
i18nKey: 'route.document_project',
order: 1,
localIcon: 'logo'
}
},
{
name: 'document_project-link',
path: '/document/project-link',
component: 'view.iframe-page',
meta: {
title: 'document_project-link',
i18nKey: 'route.document_project-link',
order: 2,
localIcon: 'logo',
href: 'https://docs.soybeanjs.cn/zh'
}
},
{
name: 'document_unocss',
path: '/document/unocss',
component: 'view.iframe-page',
props: {
url: 'https://unocss.dev/'
},
meta: {
title: 'document_unocss',
i18nKey: 'route.document_unocss',
order: 5,
icon: 'logos:unocss'
}
},
{
name: 'document_vite',
path: '/document/vite',
component: 'view.iframe-page',
props: {
url: 'https://cn.vitejs.dev/'
},
meta: {
title: 'document_vite',
i18nKey: 'route.document_vite',
order: 4,
icon: 'logos:vitejs'
}
},
{
name: 'document_vue',
path: '/document/vue',
component: 'view.iframe-page',
props: {
url: 'https://cn.vuejs.org/'
},
meta: {
title: 'document_vue',
i18nKey: 'route.document_vue',
order: 3,
icon: 'logos:vue'
}
}
]
}
];
const customRoutes: CustomRoute[] = [];
/** create routes when the auth route mode is static */
export function createStaticRoutes() {

View File

@@ -1,3 +1,2 @@
export * from './auth';
export * from './route';
export * from './system-manage';

View File

@@ -1,55 +0,0 @@
import { request } from '../request';
/** get role list */
export function fetchGetRoleList(params?: Api.SystemManage.RoleSearchParams) {
return request<Api.SystemManage.RoleList>({
url: '/systemManage/getRoleList',
method: 'get',
params
});
}
/**
* get all roles
*
* these roles are all enabled
*/
export function fetchGetAllRoles() {
return request<Api.SystemManage.AllRole[]>({
url: '/systemManage/getAllRoles',
method: 'get'
});
}
/** get user list */
export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
return request<Api.SystemManage.UserList>({
url: '/systemManage/getUserList',
method: 'get',
params
});
}
/** get menu list */
export function fetchGetMenuList() {
return request<Api.SystemManage.MenuList>({
url: '/systemManage/getMenuList/v2',
method: 'get'
});
}
/** get all pages */
export function fetchGetAllPages() {
return request<string[]>({
url: '/systemManage/getAllPages',
method: 'get'
});
}
/** get menu tree */
export function fetchGetMenuTree() {
return request<Api.SystemManage.MenuTree[]>({
url: '/systemManage/getMenuTree',
method: 'get'
});
}

View File

@@ -35,6 +35,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
},
async onBackendFail(response, instance) {
const authStore = useAuthStore();
const responseCode = String(response.data.code);
function handleLogout() {
authStore.resetStore();
@@ -49,21 +50,21 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
if (logoutCodes.includes(response.data.code)) {
if (logoutCodes.includes(responseCode)) {
handleLogout();
return null;
}
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(response.data.code) && !request.state.errMsgStack?.includes(response.data.msg)) {
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
// prevent the user from refreshing the page
window.addEventListener('beforeunload', handleLogout);
window.$dialog?.error({
title: 'Error',
title: $t('common.error'),
content: response.data.msg,
positiveText: $t('common.confirm'),
maskClosable: false,
@@ -82,7 +83,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
// when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
// the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) {
if (expiredTokenCodes.includes(responseCode) && !request.state.isRefreshingToken) {
request.state.isRefreshingToken = true;
const refreshConfig = await handleRefreshToken(response.config);
@@ -108,7 +109,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
// get backend error message and code
if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.msg || message;
backendErrorCode = error.response?.data?.code || '';
backendErrorCode = String(error.response?.data?.code || '');
}
// the error message is displayed in the modal

View File

@@ -8,11 +8,13 @@ import { fetchGetUserInfo, fetchLogin } from '@/service/api';
import { localStg } from '@/utils/storage';
import { $t } from '@/locales';
import { useRouteStore } from '../route';
import { useTabStore } from '../tab';
import { clearAuthStorage, getToken } from './shared';
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const route = useRoute();
const routeStore = useRouteStore();
const tabStore = useTabStore();
const { toLogin, redirectFromLogin } = useRouterPush(false);
const { loading: loginLoading, startLoading, endLoading } = useLoading();
@@ -47,6 +49,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
await toLogin();
}
tabStore.cacheTabs();
routeStore.resetStore();
}

View File

@@ -219,7 +219,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Init auth route */
async function initAuthRoute() {
if (authRouteMode.value === 'static') {
await initStaticAuthRoute();
initStaticAuthRoute();
} else {
await initDynamicAuthRoute();
}
@@ -228,7 +228,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
}
/** Init static auth route */
async function initStaticAuthRoute() {
function initStaticAuthRoute() {
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
if (authStore.isStaticSuper) {
@@ -354,34 +354,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
}
/**
* Get route meta by key
*
* @param key Route key
*/
function getRouteMetaByKey(key: string) {
const allRoutes = router.getRoutes();
return allRoutes.find(route => route.name === key)?.meta || null;
}
/**
* Get route query of meta by key
*
* @param key
*/
function getRouteQueryOfMetaByKey(key: string) {
const meta = getRouteMetaByKey(key);
const query: Record<string, string> = {};
meta?.query?.forEach(item => {
query[item.key] = item.value;
});
return query;
}
return {
resetStore,
routeHome,
@@ -398,7 +370,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
isInitAuthRoute,
setIsInitAuthRoute,
getIsAuthRouteExist,
getSelectedMenuKeyPath,
getRouteQueryOfMetaByKey
getSelectedMenuKeyPath
};
});

View File

@@ -123,7 +123,7 @@ function getGlobalMenuByBaseRoute(route: RouteLocationNormalizedLoaded | Elegant
const { SvgIconVNode } = useSvgIcon();
const { name, path } = route;
const { title, i18nKey, icon = import.meta.env.VITE_MENU_ICON, localIcon } = route.meta ?? {};
const { title, i18nKey, icon = import.meta.env.VITE_MENU_ICON, localIcon, iconFontSize } = route.meta ?? {};
const label = i18nKey ? $t(i18nKey) : title!;
@@ -133,7 +133,7 @@ function getGlobalMenuByBaseRoute(route: RouteLocationNormalizedLoaded | Elegant
i18nKey,
routeKey: name as RouteKey,
routePath: path as RouteMap[RouteKey],
icon: SvgIconVNode({ icon, localIcon, fontSize: 20 })
icon: SvgIconVNode({ icon, localIcon, fontSize: iconFontSize || 20 })
};
return menu;

View File

@@ -290,6 +290,7 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
resetTabLabel,
isTabRetain,
updateTabsByLocale,
getTabIdByRoute
getTabIdByRoute,
cacheTabs
};
});

View File

@@ -6,12 +6,12 @@ import { getPaletteColorByNumber } from '@sa/color';
import { SetupStoreId } from '@/enum';
import { localStg } from '@/utils/storage';
import {
addThemeVarsToHtml,
addThemeVarsToGlobal,
createThemeToken,
getNaiveTheme,
initThemeSettings,
toggleCssDarkMode,
toggleGrayscaleMode
toggleAuxiliaryColorModes,
toggleCssDarkMode
} from './shared';
/** Theme store */
@@ -33,6 +33,9 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
/** grayscale mode */
const grayscaleMode = computed(() => settings.value.grayscale);
/** colourWeakness mode */
const colourWeaknessMode = computed(() => settings.value.colourWeakness);
/** Theme colors */
const themeColors = computed(() => {
const { themeColor, otherColor, isInfoFollowPrimary } = settings.value;
@@ -79,6 +82,15 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
settings.value.grayscale = isGrayscale;
}
/**
* Set colourWeakness value
*
* @param isColourWeakness
*/
function setColourWeakness(isColourWeakness: boolean) {
settings.value.colourWeakness = isColourWeakness;
}
/** Toggle theme scheme */
function toggleThemeScheme() {
const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto'];
@@ -123,10 +135,22 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
settings.value.layout.mode = mode;
}
/** Setup theme vars to html */
function setupThemeVarsToHtml() {
const { themeTokens, darkThemeTokens } = createThemeToken(themeColors.value, settings.value.recommendColor);
addThemeVarsToHtml(themeTokens, darkThemeTokens);
/** Setup theme vars to global */
function setupThemeVarsToGlobal() {
const { themeTokens, darkThemeTokens } = createThemeToken(
themeColors.value,
settings.value.tokens,
settings.value.recommendColor
);
addThemeVarsToGlobal(themeTokens, darkThemeTokens);
}
/**
* Set layout reverse horizontal mix
*
* @param reverse Reverse horizontal mix
*/
function setLayoutReverseHorizontalMix(reverse: boolean) {
settings.value.layout.reverseHorizontalMix = reverse;
}
/** Cache theme settings */
@@ -155,9 +179,9 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
);
watch(
grayscaleMode,
[grayscaleMode, colourWeaknessMode],
val => {
toggleGrayscaleMode(val);
toggleAuxiliaryColorModes(val[0], val[1]);
},
{ immediate: true }
);
@@ -166,7 +190,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
watch(
themeColors,
val => {
setupThemeVarsToHtml();
setupThemeVarsToGlobal();
localStg.set('themeColor', val.primary);
},
{ immediate: true }
@@ -185,10 +209,12 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
naiveTheme,
settingsJson,
setGrayscale,
setColourWeakness,
resetStore,
setThemeScheme,
toggleThemeScheme,
updateThemeColors,
setThemeLayout
setThemeLayout,
setLayoutReverseHorizontalMix
};
});

View File

@@ -30,39 +30,40 @@ export function initThemeSettings() {
}
/**
* Create theme token
* create theme token css vars value by theme settings
*
* @param colors Theme colors
* @param tokens Theme setting tokens
* @param [recommended=false] Use recommended color. Default is `false`
*/
export function createThemeToken(colors: App.Theme.ThemeColor, recommended = false) {
export function createThemeToken(
colors: App.Theme.ThemeColor,
tokens?: App.Theme.ThemeSetting['tokens'],
recommended = false
) {
const paletteColors = createThemePaletteColors(colors, recommended);
const themeTokens: App.Theme.ThemeToken = {
const { light, dark } = tokens || themeSettings.tokens;
const themeTokens: App.Theme.ThemeTokenCSSVars = {
colors: {
...paletteColors,
nprogress: paletteColors.primary,
container: 'rgb(255, 255, 255)',
layout: 'rgb(247, 250, 252)',
inverted: 'rgb(0, 20, 40)',
base_text: 'rgb(31, 31, 31)'
...light.colors
},
boxShadow: {
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
tab: '0 1px 2px rgb(0, 21, 41, 0.08)'
...light.boxShadow
}
};
const darkThemeTokens: App.Theme.ThemeToken = {
const darkThemeTokens: App.Theme.ThemeTokenCSSVars = {
colors: {
...themeTokens.colors,
container: 'rgb(28, 28, 28)',
layout: 'rgb(18, 18, 18)',
base_text: 'rgb(224, 224, 224)'
...dark?.colors
},
boxShadow: {
...themeTokens.boxShadow
...themeTokens.boxShadow,
...dark?.boxShadow
}
};
@@ -132,16 +133,16 @@ function getCssVarByTokens(tokens: App.Theme.BaseToken) {
}
/**
* Add theme vars to html
* Add theme vars to global
*
* @param tokens
*/
export function addThemeVarsToHtml(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) {
export function addThemeVarsToGlobal(tokens: App.Theme.BaseToken, darkTokens: App.Theme.BaseToken) {
const cssVarStr = getCssVarByTokens(tokens);
const darkCssVarStr = getCssVarByTokens(darkTokens);
const css = `
html {
:root {
${cssVarStr}
}
`;
@@ -179,20 +180,16 @@ export function toggleCssDarkMode(darkMode = false) {
}
/**
* Toggle grayscale mode
* Toggle auxiliary color modes
*
* @param grayscaleMode Is grayscale mode
* @param grayscaleMode
* @param colourWeakness
*/
export function toggleGrayscaleMode(grayscaleMode = false) {
const GRAYSCALE_CLASS = 'grayscale';
const { add, remove } = toggleHtmlClass(GRAYSCALE_CLASS);
if (grayscaleMode) {
add();
} else {
remove();
}
export function toggleAuxiliaryColorModes(grayscaleMode = false, colourWeakness = false) {
const htmlElement = document.documentElement;
htmlElement.style.filter = [grayscaleMode ? 'grayscale(100%)' : '', colourWeakness ? 'invert(80%)' : '']
.filter(Boolean)
.join(' ');
}
type NaiveColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active';

View File

@@ -1,5 +1,5 @@
import type { PiniaPluginContext } from 'pinia';
import { cloneDeep } from 'lodash-es';
import { jsonClone } from '@sa/utils';
import { SetupStoreId } from '@/enum';
/**
@@ -13,7 +13,7 @@ export function resetSetupStore(context: PiniaPluginContext) {
if (setupSyntaxIds.includes(context.store.$id)) {
const { $state } = context.store;
const defaultStore = cloneDeep($state);
const defaultStore = jsonClone($state);
context.store.$reset = () => {
context.store.$patch(defaultStore);

View File

@@ -11,7 +11,3 @@ body,
html {
overflow-x: hidden;
}
html.grayscale {
filter: grayscale(100%);
}

View File

@@ -2,6 +2,7 @@
export const themeSettings: App.Theme.ThemeSetting = {
themeScheme: 'light',
grayscale: false,
colourWeakness: false,
recommendColor: false,
themeColor: '#646cff',
otherColor: {
@@ -13,7 +14,8 @@ export const themeSettings: App.Theme.ThemeSetting = {
isInfoFollowPrimary: true,
layout: {
mode: 'vertical',
scrollMode: 'content'
scrollMode: 'content',
reverseHorizontalMix: false
},
page: {
animate: true,
@@ -46,6 +48,32 @@ export const themeSettings: App.Theme.ThemeSetting = {
fixed: false,
height: 48,
right: true
},
watermark: {
visible: false,
text: 'SoybeanAdmin'
},
tokens: {
light: {
colors: {
container: 'rgb(255, 255, 255)',
layout: 'rgb(247, 250, 252)',
inverted: 'rgb(0, 20, 40)',
'base-text': 'rgb(31, 31, 31)'
},
boxShadow: {
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
tab: '0 1px 2px rgb(0, 21, 41, 0.08)'
}
},
dark: {
colors: {
container: 'rgb(28, 28, 28)',
layout: 'rgb(18, 18, 18)',
'base-text': 'rgb(224, 224, 224)'
}
}
}
};

View File

@@ -18,14 +18,14 @@ function createColorPaletteVars() {
const colorPaletteVars = createColorPaletteVars();
/** Theme vars */
export const themeVars: App.Theme.ThemeToken = {
export const themeVars: App.Theme.ThemeTokenCSSVars = {
colors: {
...colorPaletteVars,
nprogress: 'rgb(var(--nprogress-color))',
container: 'rgb(var(--container-bg-color))',
layout: 'rgb(var(--layout-bg-color))',
inverted: 'rgb(var(--inverted-bg-color))',
base_text: 'rgb(var(--base-text-color))'
'base-text': 'rgb(var(--base-text-color))'
},
boxShadow: {
header: 'var(--header-box-shadow)',

138
src/typings/api.d.ts vendored
View File

@@ -81,142 +81,4 @@ declare namespace Api {
home: import('@elegant-router/types').LastLevelRouteKey;
}
}
/**
* namespace SystemManage
*
* backend api module: "systemManage"
*/
namespace SystemManage {
type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>;
/** role */
type Role = Common.CommonRecord<{
/** role name */
roleName: string;
/** role code */
roleCode: string;
/** role description */
roleDesc: string;
}>;
/** role search params */
type RoleSearchParams = CommonType.RecordNullable<
Pick<Api.SystemManage.Role, 'roleName' | 'roleCode' | 'status'> & CommonSearchParams
>;
/** role list */
type RoleList = Common.PaginatingQueryRecord<Role>;
/** all role */
type AllRole = Pick<Role, 'id' | 'roleName' | 'roleCode'>;
/**
* user gender
*
* - "1": "male"
* - "2": "female"
*/
type UserGender = '1' | '2';
/** user */
type User = Common.CommonRecord<{
/** user name */
userName: string;
/** user gender */
userGender: UserGender | null;
/** user nick name */
nickName: string;
/** user phone */
userPhone: string;
/** user email */
userEmail: string;
/** user role code collection */
userRoles: string[];
}>;
/** user search params */
type UserSearchParams = CommonType.RecordNullable<
Pick<Api.SystemManage.User, 'userName' | 'userGender' | 'nickName' | 'userPhone' | 'userEmail' | 'status'> &
CommonSearchParams
>;
/** user list */
type UserList = Common.PaginatingQueryRecord<User>;
/**
* menu type
*
* - "1": directory
* - "2": menu
*/
type MenuType = '1' | '2';
type MenuButton = {
/**
* button code
*
* it can be used to control the button permission
*/
code: string;
/** button description */
desc: string;
};
/**
* icon type
*
* - "1": iconify icon
* - "2": local icon
*/
type IconType = '1' | '2';
type MenuPropsOfRoute = Pick<
import('vue-router').RouteMeta,
| 'i18nKey'
| 'keepAlive'
| 'constant'
| 'order'
| 'href'
| 'hideInMenu'
| 'activeMenu'
| 'multiTab'
| 'fixedIndexInTab'
| 'query'
>;
type Menu = Common.CommonRecord<{
/** parent menu id */
parentId: number;
/** menu type */
menuType: MenuType;
/** menu name */
menuName: string;
/** route name */
routeName: string;
/** route path */
routePath: string;
/** component */
component?: string;
/** iconify icon name or local icon name */
icon: string;
/** icon type */
iconType: IconType;
/** buttons */
buttons?: MenuButton[] | null;
/** children menu */
children?: Menu[] | null;
}> &
MenuPropsOfRoute;
/** menu list */
type MenuList = Common.PaginatingQueryRecord<Menu>;
type MenuTree = {
id: number;
label: string;
pId: number;
children?: MenuTree[];
};
}
}

243
src/typings/app.d.ts vendored
View File

@@ -4,22 +4,14 @@ declare namespace App {
namespace Theme {
type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber;
/** Theme token */
type ThemeToken = {
colors: ThemeTokenColor;
boxShadow: {
header: string;
sider: string;
tab: string;
};
};
/** Theme setting */
interface ThemeSetting {
/** Theme scheme */
themeScheme: UnionKey.ThemeScheme;
/** grayscale mode */
grayscale: boolean;
/** colour weakness mode */
colourWeakness: boolean;
/** Whether to recommend color */
recommendColor: boolean;
/** Theme color */
@@ -34,6 +26,12 @@ declare namespace App {
mode: UnionKey.ThemeLayoutMode;
/** Scroll mode */
scrollMode: UnionKey.ThemeScrollMode;
/**
* Whether to reverse the horizontal mix
*
* if true, the vertical child level menus in left and horizontal first level menus in top
*/
reverseHorizontalMix?: boolean;
};
/** Page */
page: {
@@ -97,6 +95,20 @@ declare namespace App {
/** Whether float the footer to the right when the layout is 'horizontal-mix' */
right: boolean;
};
/** Watermark */
watermark?: {
/** Whether to show the watermark */
visible: boolean;
/** Watermark text */
text: string;
};
/** define some theme settings tokens, will transform to css variables */
tokens: {
light: ThemeSettingToken;
dark?: {
[K in keyof ThemeSettingToken]?: Partial<ThemeSettingToken[K]>;
};
};
}
interface OtherColor {
@@ -118,14 +130,33 @@ declare namespace App {
type BaseToken = Record<string, Record<string, string>>;
interface ThemeTokenColor extends ThemePaletteColor {
nprogress: string;
interface ThemeSettingTokenColor {
/** the progress bar color, if not set, will use the primary color */
nprogress?: string;
container: string;
layout: string;
inverted: string;
base_text: string;
[key: string]: string;
'base-text': string;
}
interface ThemeSettingTokenBoxShadow {
header: string;
sider: string;
tab: string;
}
interface ThemeSettingToken {
colors: ThemeSettingTokenColor;
boxShadow: ThemeSettingTokenBoxShadow;
}
type ThemeTokenColor = ThemePaletteColor & ThemeSettingTokenColor;
/** Theme token CSS variables */
type ThemeTokenCSSVars = {
colors: ThemeTokenColor & { [key: string]: string };
boxShadow: ThemeSettingTokenBoxShadow & { [key: string]: string };
};
}
/** Global namespace */
@@ -148,7 +179,7 @@ declare namespace App {
}
/** The global menu */
interface Menu {
type Menu = {
/**
* The menu key
*
@@ -167,7 +198,7 @@ declare namespace App {
icon?: () => VNode;
/** The menu children */
children?: Menu[];
}
};
type Breadcrumb = Omit<Menu, 'children'> & {
options?: Breadcrumb[];
@@ -273,6 +304,8 @@ declare namespace App {
deleteSuccess: string;
confirmDelete: string;
edit: string;
warning: string;
error: string;
index: string;
keywordSearch: string;
logout: string;
@@ -308,7 +341,8 @@ declare namespace App {
theme: {
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
grayscale: string;
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string>;
colourWeakness: string;
layoutMode: { title: string; reverseHorizontalMix: string } & Record<UnionKey.ThemeLayoutMode, string>;
recommendColor: string;
recommendColorDesc: string;
themeColor: {
@@ -348,6 +382,10 @@ declare namespace App {
height: string;
right: string;
};
watermark: {
visible: string;
text: string;
};
themeDrawerTitle: string;
pageFunTitle: string;
configOperation: {
@@ -405,20 +443,8 @@ declare namespace App {
title: string;
};
};
about: {
title: string;
introduction: string;
projectInfo: {
title: string;
version: string;
latestBuildTime: string;
githubLink: string;
previewLink: string;
};
prdDep: string;
devDep: string;
};
home: {
branchDesc: string;
greeting: string;
weatherDesc: string;
projectCount: string;
@@ -445,163 +471,6 @@ declare namespace App {
};
creativity: string;
};
function: {
tab: {
tabOperate: {
title: string;
addTab: string;
addTabDesc: string;
closeTab: string;
closeCurrentTab: string;
closeAboutTab: string;
addMultiTab: string;
addMultiTabDesc1: string;
addMultiTabDesc2: string;
};
tabTitle: {
title: string;
changeTitle: string;
change: string;
resetTitle: string;
reset: string;
};
};
multiTab: {
routeParam: string;
backTab: string;
};
toggleAuth: {
toggleAccount: string;
authHook: string;
superAdminVisible: string;
adminVisible: string;
adminOrUserVisible: string;
};
request: {
repeatedErrorOccurOnce: string;
repeatedError: string;
repeatedErrorMsg1: string;
repeatedErrorMsg2: string;
};
};
manage: {
common: {
status: {
enable: string;
disable: string;
};
};
role: {
title: string;
roleName: string;
roleCode: string;
roleStatus: string;
roleDesc: string;
form: {
roleName: string;
roleCode: string;
roleStatus: string;
roleDesc: string;
};
addRole: string;
editRole: string;
menuAuth: string;
buttonAuth: string;
};
user: {
title: string;
userName: string;
userGender: string;
nickName: string;
userPhone: string;
userEmail: string;
userStatus: string;
userRole: string;
form: {
userName: string;
userGender: string;
nickName: string;
userPhone: string;
userEmail: string;
userStatus: string;
userRole: string;
};
addUser: string;
editUser: string;
gender: {
male: string;
female: string;
};
};
menu: {
home: string;
title: string;
id: string;
parentId: string;
menuType: string;
menuName: string;
routeName: string;
routePath: string;
pathParam: string;
layout: string;
page: string;
i18nKey: string;
icon: string;
localIcon: string;
iconTypeTitle: string;
order: string;
constant: string;
keepAlive: string;
href: string;
hideInMenu: string;
activeMenu: string;
multiTab: string;
fixedIndexInTab: string;
query: string;
button: string;
buttonCode: string;
buttonDesc: string;
menuStatus: string;
form: {
home: string;
menuType: string;
menuName: string;
routeName: string;
routePath: string;
pathParam: string;
layout: string;
page: string;
i18nKey: string;
icon: string;
localIcon: string;
order: string;
keepAlive: string;
href: string;
hideInMenu: string;
activeMenu: string;
multiTab: string;
fixedInTab: string;
fixedIndexInTab: string;
queryKey: string;
queryValue: string;
button: string;
buttonCode: string;
buttonDesc: string;
menuStatus: string;
};
addMenu: string;
editMenu: string;
addChildMenu: string;
type: {
directory: string;
menu: string;
};
iconType: {
iconify: string;
local: string;
};
};
};
};
form: {
required: string;

View File

@@ -37,6 +37,7 @@ declare module 'vue' {
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
@@ -83,6 +84,7 @@ declare module 'vue' {
NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip']
NTree: typeof import('naive-ui')['NTree']
NWatermark: typeof import('naive-ui')['NWatermark']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']

View File

@@ -17,47 +17,12 @@ declare module "@elegant-router/types" {
export type RouteMap = {
"root": "/";
"not-found": "/:pathMatch(.*)*";
"exception": "/exception";
"exception_403": "/exception/403";
"exception_404": "/exception/404";
"exception_500": "/exception/500";
"document": "/document";
"document_project": "/document/project";
"document_project-link": "/document/project-link";
"document_vue": "/document/vue";
"document_vite": "/document/vite";
"document_unocss": "/document/unocss";
"document_naive": "/document/naive";
"document_antd": "/document/antd";
"403": "/403";
"404": "/404";
"500": "/500";
"about": "/about";
"function": "/function";
"function_hide-child": "/function/hide-child";
"function_hide-child_one": "/function/hide-child/one";
"function_hide-child_three": "/function/hide-child/three";
"function_hide-child_two": "/function/hide-child/two";
"function_multi-tab": "/function/multi-tab";
"function_request": "/function/request";
"function_super-page": "/function/super-page";
"function_tab": "/function/tab";
"function_toggle-auth": "/function/toggle-auth";
"home": "/home";
"iframe-page": "/iframe-page/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"manage": "/manage";
"manage_menu": "/manage/menu";
"manage_role": "/manage/role";
"manage_user": "/manage/user";
"manage_user-detail": "/manage/user-detail/:id";
"multi-menu": "/multi-menu";
"multi-menu_first": "/multi-menu/first";
"multi-menu_first_child": "/multi-menu/first/child";
"multi-menu_second": "/multi-menu/second";
"multi-menu_second_child": "/multi-menu/second/child";
"multi-menu_second_child_home": "/multi-menu/second/child/home";
"user-center": "/user-center";
};
/**
@@ -72,28 +37,16 @@ declare module "@elegant-router/types" {
/**
* custom route key
*/
*/
export type CustomRouteKey = Extract<
RouteKey,
| "root"
| "not-found"
| "exception"
| "exception_403"
| "exception_404"
| "exception_500"
| "document"
| "document_project"
| "document_project-link"
| "document_vue"
| "document_vite"
| "document_unocss"
| "document_naive"
| "document_antd"
>;
/**
* the generated route key
*/
*/
export type GeneratedRouteKey = Exclude<RouteKey, CustomRouteKey>;
/**
@@ -104,14 +57,9 @@ declare module "@elegant-router/types" {
| "403"
| "404"
| "500"
| "about"
| "function"
| "home"
| "iframe-page"
| "login"
| "manage"
| "multi-menu"
| "user-center"
>;
/**
@@ -121,8 +69,6 @@ declare module "@elegant-router/types" {
CustomRouteKey,
| "root"
| "not-found"
| "exception"
| "document"
>;
/**
@@ -135,23 +81,7 @@ declare module "@elegant-router/types" {
| "500"
| "iframe-page"
| "login"
| "about"
| "function_hide-child_one"
| "function_hide-child_three"
| "function_hide-child_two"
| "function_multi-tab"
| "function_request"
| "function_super-page"
| "function_tab"
| "function_toggle-auth"
| "home"
| "manage_menu"
| "manage_role"
| "manage_user-detail"
| "manage_user"
| "multi-menu_first_child"
| "multi-menu_second_child_home"
| "user-center"
>;
/**
@@ -161,16 +91,6 @@ declare module "@elegant-router/types" {
CustomRouteKey,
| "root"
| "not-found"
| "exception_403"
| "exception_404"
| "exception_500"
| "document_project"
| "document_project-link"
| "document_vue"
| "document_vite"
| "document_unocss"
| "document_naive"
| "document_antd"
>;
/**

View File

@@ -105,3 +105,7 @@ declare namespace Env {
readonly VITE_STORAGE_PREFIX?: string;
}
}
interface ImportMeta {
readonly env: Env.ImportMeta;
}

View File

@@ -1,27 +1,27 @@
interface Window {
/** NProgress instance */
NProgress?: import('nprogress').NProgress;
/** Loading bar instance */
$loadingBar?: import('naive-ui').LoadingBarProviderInst;
/** Dialog instance */
$dialog?: import('naive-ui').DialogProviderInst;
/** Message instance */
$message?: import('naive-ui').MessageProviderInst;
/** Notification instance */
$notification?: import('naive-ui').NotificationProviderInst;
}
export {};
interface ViewTransition {
ready: Promise<void>;
}
declare global {
export interface Window {
/** NProgress instance */
NProgress?: import('nprogress').NProgress;
/** Loading bar instance */
$loadingBar?: import('naive-ui').LoadingBarProviderInst;
/** Dialog instance */
$dialog?: import('naive-ui').DialogProviderInst;
/** Message instance */
$message?: import('naive-ui').MessageProviderInst;
/** Notification instance */
$notification?: import('naive-ui').NotificationProviderInst;
}
interface Document {
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition;
}
interface ViewTransition {
ready: Promise<void>;
}
interface ImportMeta {
readonly env: Env.ImportMeta;
}
export interface Document {
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition;
}
/** Build time of the project */
declare const BUILD_TIME: string;
/** Build time of the project */
export const BUILD_TIME: string;
}

View File

@@ -42,6 +42,8 @@ declare module 'vue-router' {
* In "src/assets/svg-icon", if it is set, the icon will be ignored
*/
localIcon?: string;
/** Icon size. width and height are the same. */
iconFontSize?: number;
/** Router order */
order?: number | null;
/** The outer link of the route */

View File

@@ -20,7 +20,7 @@ declare namespace UnionKey {
* - vertical: the vertical menu in left
* - horizontal: the horizontal menu in top
* - vertical-mix: two vertical mixed menus in left
* - horizontal-mix: the vertical menu in left and horizontal menu in top
* - horizontal-mix: the vertical first level menus in left and horizontal child level menus in top
*/
type ThemeLayoutMode = 'vertical' | 'horizontal' | 'vertical-mix' | 'horizontal-mix';

View File

@@ -1,85 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import pkg from '~/package.json';
const appStore = useAppStore();
const column = computed(() => (appStore.isMobile ? 1 : 2));
interface PkgJson {
name: string;
version: string;
dependencies: PkgVersionInfo[];
devDependencies: PkgVersionInfo[];
}
interface PkgVersionInfo {
name: string;
version: string;
}
const { name, version, dependencies, devDependencies } = pkg;
function transformVersionData(tuple: [string, string]): PkgVersionInfo {
const [$name, $version] = tuple;
return {
name: $name,
version: $version
};
}
const pkgJson: PkgJson = {
name,
version,
dependencies: Object.entries(dependencies).map(item => transformVersionData(item)),
devDependencies: Object.entries(devDependencies).map(item => transformVersionData(item))
};
const latestBuildTime = BUILD_TIME;
</script>
<template>
<NSpace vertical :size="16">
<NCard :title="$t('page.about.title')" :bordered="false" size="small" segmented class="card-wrapper">
<p>{{ $t('page.about.introduction') }}</p>
</NCard>
<NCard :title="$t('page.about.projectInfo.title')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem :label="$t('page.about.projectInfo.version')">
<NTag type="primary">{{ pkgJson.version }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.about.projectInfo.latestBuildTime')">
<NTag type="primary">{{ latestBuildTime }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.about.projectInfo.githubLink')">
<a class="text-primary" :href="pkg.homepage" target="_blank" rel="noopener noreferrer">
{{ $t('page.about.projectInfo.githubLink') }}
</a>
</NDescriptionsItem>
<NDescriptionsItem :label="$t('page.about.projectInfo.previewLink')">
<a class="text-primary" :href="pkg.website" target="_blank" rel="noopener noreferrer">
{{ $t('page.about.projectInfo.previewLink') }}
</a>
</NDescriptionsItem>
</NDescriptions>
</NCard>
<NCard :title="$t('page.about.prdDep')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem v-for="item in pkgJson.dependencies" :key="item.name" :label="item.name">
{{ item.version }}
</NDescriptionsItem>
</NDescriptions>
</NCard>
<NCard :title="$t('page.about.devDep')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem v-for="item in pkgJson.devDependencies" :key="item.name" :label="item.name">
{{ item.version }}
</NDescriptionsItem>
</NDescriptions>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
const route = useRoute();
const { routerPushByKey } = useRouterPush();
const routeQuery = computed(() => JSON.stringify(route.query));
</script>
<template>
<div>
<LookForward>
<div>
<NButton @click="routerPushByKey('function_tab')">{{ $t('page.function.multiTab.backTab') }}</NButton>
<div class="py-24px">{{ $t('page.function.multiTab.routeParam') }}: {{ routeQuery }}</div>
</div>
</LookForward>
</div>
</template>
<style scoped></style>

View File

@@ -1,63 +0,0 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { fetchCustomBackendError } from '@/service/api';
async function logout() {
await fetchCustomBackendError('8888', $t('request.logoutMsg'));
}
async function logoutWithModal() {
await fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'));
}
async function refreshToken() {
await fetchCustomBackendError('9999', $t('request.tokenExpired'));
}
async function handleRepeatedMessageError() {
await Promise.all([
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2'))
]);
}
async function handleRepeatedModalError() {
await Promise.all([
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'))
]);
}
</script>
<template>
<NSpace vertical :size="16">
<NCard :title="$t('request.logout')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="logout">{{ $t('common.trigger') }}</NButton>
</NCard>
<NCard :title="$t('request.logoutWithModal')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="logoutWithModal">{{ $t('common.trigger') }}</NButton>
</NCard>
<NCard :title="$t('request.refreshToken')" :bordered="false" size="small" segmented class="card-wrapper">
<NButton @click="refreshToken">{{ $t('common.trigger') }}</NButton>
</NCard>
<NCard
:title="$t('page.function.request.repeatedErrorOccurOnce')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NButton @click="handleRepeatedMessageError">{{ $t('page.function.request.repeatedError') }}(Message)</NButton>
<NButton class="ml-12px" @click="handleRepeatedModalError">
{{ $t('page.function.request.repeatedError') }}(Modal)
</NButton>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<LookForward />
</template>
<style scoped></style>

View File

@@ -1,71 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
import { useTabStore } from '@/store/modules/tab';
const tabStore = useTabStore();
const { routerPushByKey } = useRouterPush();
const tabLabel = ref('');
function changeTabLabel() {
tabStore.setTabLabel(tabLabel.value);
}
function resetTabLabel() {
tabStore.resetTabLabel();
}
</script>
<template>
<NSpace vertical :size="16">
<NCard
:title="$t('page.function.tab.tabOperate.title')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NDivider title-placement="left">{{ $t('page.function.tab.tabOperate.addTab') }}</NDivider>
<NButton @click="routerPushByKey('about')">{{ $t('page.function.tab.tabOperate.addTabDesc') }}</NButton>
<NDivider title-placement="left">{{ $t('page.function.tab.tabOperate.closeTab') }}</NDivider>
<NSpace>
<NButton @click="tabStore.removeActiveTab">
{{ $t('page.function.tab.tabOperate.closeCurrentTab') }}
</NButton>
<NButton @click="tabStore.removeTabByRouteName('about')">
{{ $t('page.function.tab.tabOperate.closeAboutTab') }}
</NButton>
</NSpace>
<NDivider title-placement="left">{{ $t('page.function.tab.tabOperate.addMultiTab') }}</NDivider>
<NSpace>
<NButton @click="routerPushByKey('function_multi-tab')">
{{ $t('page.function.tab.tabOperate.addMultiTabDesc1') }}
</NButton>
<NButton @click="routerPushByKey('function_multi-tab', { query: { a: '1' } })">
{{ $t('page.function.tab.tabOperate.addMultiTabDesc2') }}
</NButton>
</NSpace>
</NCard>
<NCard
:title="$t('page.function.tab.tabTitle.title')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NDivider title-placement="left">{{ $t('page.function.tab.tabTitle.changeTitle') }}</NDivider>
<NInputGroup class="max-w-240px">
<NInput v-model:value="tabLabel" />
<NButton type="primary" @click="changeTabLabel">{{ $t('page.function.tab.tabTitle.change') }}</NButton>
</NInputGroup>
<NDivider title-placement="left">{{ $t('page.function.tab.tabTitle.resetTitle') }}</NDivider>
<NButton type="error" ghost class="w-80px" @click="resetTabLabel">
{{ $t('page.function.tab.tabTitle.reset') }}
</NButton>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -1,103 +0,0 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useLoading } from '@sa/hooks';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useAuthStore } from '@/store/modules/auth';
import { useTabStore } from '@/store/modules/tab';
import { useAuth } from '@/hooks/business/auth';
const route = useRoute();
const appStore = useAppStore();
const authStore = useAuthStore();
const tabStore = useTabStore();
const { hasAuth } = useAuth();
const { loading, startLoading, endLoading } = useLoading();
type AccountKey = 'super' | 'admin' | 'user';
interface Account {
key: AccountKey;
label: string;
userName: string;
password: string;
}
const accounts = computed<Account[]>(() => [
{
key: 'super',
label: $t('page.login.pwdLogin.superAdmin'),
userName: 'Super',
password: '123456'
},
{
key: 'admin',
label: $t('page.login.pwdLogin.admin'),
userName: 'Admin',
password: '123456'
},
{
key: 'user',
label: $t('page.login.pwdLogin.user'),
userName: 'User',
password: '123456'
}
]);
const loginAccount = ref<AccountKey>('super');
async function handleToggleAccount(account: Account) {
loginAccount.value = account.key;
startLoading();
await authStore.login(account.userName, account.password, false);
tabStore.initTabStore(route);
endLoading();
appStore.reloadPage();
}
</script>
<template>
<NSpace vertical :size="16">
<NCard :title="$t('route.function_toggle-auth')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions bordered :column="1">
<NDescriptionsItem :label="$t('page.manage.user.userRole')">
<NSpace>
<NTag v-for="role in authStore.userInfo.roles" :key="role">{{ role }}</NTag>
</NSpace>
</NDescriptionsItem>
<NDescriptionsItem ions-item :label="$t('page.function.toggleAuth.toggleAccount')">
<NSpace>
<NButton
v-for="account in accounts"
:key="account.key"
:loading="loading && loginAccount === account.key"
:disabled="loading && loginAccount !== account.key"
@click="handleToggleAccount(account)"
>
{{ account.label }}
</NButton>
</NSpace>
</NDescriptionsItem>
</NDescriptions>
</NCard>
<NCard
:title="$t('page.function.toggleAuth.authHook')"
:bordered="false"
size="small"
segmented
class="card-wrapper"
>
<NSpace>
<NButton v-if="hasAuth('B_CODE1')">{{ $t('page.function.toggleAuth.superAdminVisible') }}</NButton>
<NButton v-if="hasAuth('B_CODE2')">{{ $t('page.function.toggleAuth.adminVisible') }}</NButton>
<NButton v-if="hasAuth('B_CODE3')">
{{ $t('page.function.toggleAuth.adminOrUserVisible') }}
</NButton>
</NSpace>
</NCard>
</NSpace>
</template>
<style scoped></style>

View File

@@ -15,6 +15,9 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16));
<template>
<NSpace vertical :size="16">
<NAlert :title="$t('common.warning')" type="warning">
{{ $t('page.home.branchDesc') }}
</NAlert>
<HeaderBanner />
<CardData />
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>

View File

@@ -1,266 +0,0 @@
<script setup lang="tsx">
import { ref } from 'vue';
import type { Ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { fetchGetAllPages, fetchGetMenuList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import { yesOrNoRecord } from '@/constants/common';
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue';
import MenuOperateModal, { type OperateType } from './modules/menu-operate-modal.vue';
const appStore = useAppStore();
const { bool: visible, setTrue: openModal } = useBoolean();
const wrapperRef = ref<HTMLElement | null>(null);
const { columns, columnChecks, data, loading, pagination, getData } = useTable({
apiFn: fetchGetMenuList,
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'id',
title: $t('page.manage.menu.id'),
align: 'center'
},
{
key: 'menuType',
title: $t('page.manage.menu.menuType'),
align: 'center',
width: 80,
render: row => {
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'default',
2: 'primary'
};
const label = $t(menuTypeRecord[row.menuType]);
return <NTag type={tagMap[row.menuType]}>{label}</NTag>;
}
},
{
key: 'menuName',
title: $t('page.manage.menu.menuName'),
align: 'center',
minWidth: 120,
render: row => {
const { i18nKey, menuName } = row;
const label = i18nKey ? $t(i18nKey) : menuName;
return <span>{label}</span>;
}
},
{
key: 'icon',
title: $t('page.manage.menu.icon'),
align: 'center',
width: 60,
render: row => {
const icon = row.iconType === '1' ? row.icon : undefined;
const localIcon = row.iconType === '2' ? row.icon : undefined;
return (
<div class="flex-center">
<SvgIcon icon={icon} localIcon={localIcon} class="text-icon" />
</div>
);
}
},
{
key: 'routeName',
title: $t('page.manage.menu.routeName'),
align: 'center',
minWidth: 120
},
{
key: 'routePath',
title: $t('page.manage.menu.routePath'),
align: 'center',
minWidth: 120
},
{
key: 'status',
title: $t('page.manage.menu.menuStatus'),
align: 'center',
width: 80,
render: row => {
if (row.status === null) {
return null;
}
const tagMap: Record<Api.Common.EnableStatus, NaiveUI.ThemeColor> = {
1: 'success',
2: 'warning'
};
const label = $t(enableStatusRecord[row.status]);
return <NTag type={tagMap[row.status]}>{label}</NTag>;
}
},
{
key: 'hideInMenu',
title: $t('page.manage.menu.hideInMenu'),
align: 'center',
width: 80,
render: row => {
const hide: CommonType.YesOrNo = row.hideInMenu ? 'Y' : 'N';
const tagMap: Record<CommonType.YesOrNo, NaiveUI.ThemeColor> = {
Y: 'error',
N: 'default'
};
const label = $t(yesOrNoRecord[hide]);
return <NTag type={tagMap[hide]}>{label}</NTag>;
}
},
{
key: 'parentId',
title: $t('page.manage.menu.parentId'),
width: 90,
align: 'center'
},
{
key: 'order',
title: $t('page.manage.menu.order'),
align: 'center',
width: 60
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 230,
render: row => (
<div class="flex-center justify-end gap-8px">
{row.menuType === '1' && (
<NButton type="primary" ghost size="small" onClick={() => handleAddChildMenu(row)}>
{$t('page.manage.menu.addChildMenu')}
</NButton>
)}
<NButton type="primary" ghost size="small" onClick={() => handleEdit(row)}>
{$t('common.edit')}
</NButton>
<NPopconfirm onPositiveClick={() => handleDelete(row.id)}>
{{
default: () => $t('common.confirmDelete'),
trigger: () => (
<NButton type="error" ghost size="small">
{$t('common.delete')}
</NButton>
)
}}
</NPopconfirm>
</div>
)
}
]
});
const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData);
const operateType = ref<OperateType>('add');
function handleAdd() {
operateType.value = 'add';
openModal();
}
async function handleBatchDelete() {
// request
console.log(checkedRowKeys.value);
onBatchDeleted();
}
function handleDelete(id: number) {
// request
console.log(id);
onDeleted();
}
/** the edit menu data or the parent menu data when adding a child menu */
const editingData: Ref<Api.SystemManage.Menu | null> = ref(null);
function handleEdit(item: Api.SystemManage.Menu) {
operateType.value = 'edit';
editingData.value = { ...item };
openModal();
}
function handleAddChildMenu(item: Api.SystemManage.Menu) {
operateType.value = 'addChild';
editingData.value = { ...item };
openModal();
}
const allPages = ref<string[]>([]);
async function getAllPages() {
const { data: pages } = await fetchGetAllPages();
allPages.value = pages || [];
}
function init() {
getAllPages();
}
// init
init();
</script>
<template>
<div ref="wrapperRef" class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<NCard :title="$t('page.manage.menu.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper">
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
@add="handleAdd"
@delete="handleBatchDelete"
@refresh="getData"
/>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="1088"
:loading="loading"
:row-key="row => row.id"
remote
:pagination="pagination"
class="sm:h-full"
/>
<MenuOperateModal
v-model:visible="visible"
:operate-type="operateType"
:row-data="editingData"
:all-pages="allPages"
@submitted="getData"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@@ -1,467 +0,0 @@
<script setup lang="tsx">
import { computed, reactive, ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue';
import { getLocalIcons } from '@/utils/icon';
import { fetchGetAllRoles } from '@/service/api';
import {
getLayoutAndPage,
getPathParamFromRoutePath,
getRoutePathByRouteName,
getRoutePathWithParam,
transformLayoutAndPageToComponent
} from './shared';
defineOptions({
name: 'MenuOperateModal'
});
export type OperateType = NaiveUI.TableOperateType | 'addChild';
interface Props {
/** the type of operation */
operateType: OperateType;
/** the edit menu data or the parent menu data when adding a child menu */
rowData?: Api.SystemManage.Menu | null;
/** all pages */
allPages: string[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { defaultRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<OperateType, string> = {
add: $t('page.manage.menu.addMenu'),
addChild: $t('page.manage.menu.addChildMenu'),
edit: $t('page.manage.menu.editMenu')
};
return titles[props.operateType];
});
type Model = Pick<
Api.SystemManage.Menu,
| 'menuType'
| 'menuName'
| 'routeName'
| 'routePath'
| 'component'
| 'order'
| 'i18nKey'
| 'icon'
| 'iconType'
| 'status'
| 'parentId'
| 'keepAlive'
| 'constant'
| 'href'
| 'hideInMenu'
| 'activeMenu'
| 'multiTab'
| 'fixedIndexInTab'
> & {
query: NonNullable<Api.SystemManage.Menu['query']>;
buttons: NonNullable<Api.SystemManage.Menu['buttons']>;
layout: string;
page: string;
pathParam: string;
};
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
menuType: '1',
menuName: '',
routeName: '',
routePath: '',
pathParam: '',
component: '',
layout: '',
page: '',
i18nKey: null,
icon: '',
iconType: '1',
parentId: 0,
status: '1',
keepAlive: false,
constant: false,
order: 0,
href: null,
hideInMenu: false,
activeMenu: null,
multiTab: false,
fixedIndexInTab: null,
query: [],
buttons: []
};
}
type RuleKey = Extract<keyof Model, 'menuName' | 'status' | 'routeName' | 'routePath'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
menuName: defaultRequiredRule,
status: defaultRequiredRule,
routeName: defaultRequiredRule,
routePath: defaultRequiredRule
};
const disabledMenuType = computed(() => props.operateType === 'edit');
const localIcons = getLocalIcons();
const localIconOptions = localIcons.map<SelectOption>(item => ({
label: () => (
<div class="flex-y-center gap-16px">
<SvgIcon localIcon={item} class="text-icon" />
<span>{item}</span>
</div>
),
value: item
}));
const showLayout = computed(() => model.parentId === 0);
const showPage = computed(() => model.menuType === '2');
const pageOptions = computed(() => {
const allPages = [...props.allPages];
if (model.routeName && !allPages.includes(model.routeName)) {
allPages.unshift(model.routeName);
}
const opts: CommonType.Option[] = allPages.map(page => ({
label: page,
value: page
}));
return opts;
});
const layoutOptions: CommonType.Option[] = [
{
label: 'base',
value: 'base'
},
{
label: 'blank',
value: 'blank'
}
];
/** the enabled role options */
const roleOptions = ref<CommonType.Option<string>[]>([]);
async function getRoleOptions() {
const { error, data } = await fetchGetAllRoles();
if (!error) {
const options = data.map(item => ({
label: item.roleName,
value: item.roleCode
}));
roleOptions.value = [...options];
}
}
function handleInitModel() {
Object.assign(model, createDefaultModel());
if (!props.rowData) return;
if (props.operateType === 'addChild') {
const { id } = props.rowData;
Object.assign(model, { parentId: id });
}
if (props.operateType === 'edit') {
const { component, ...rest } = props.rowData;
const { layout, page } = getLayoutAndPage(component);
const { path, param } = getPathParamFromRoutePath(rest.routePath);
Object.assign(model, rest, { layout, page, routePath: path, pathParam: param });
}
if (!model.query) {
model.query = [];
}
if (!model.buttons) {
model.buttons = [];
}
}
function closeDrawer() {
visible.value = false;
}
function handleUpdateRoutePathByRouteName() {
if (model.routeName) {
model.routePath = getRoutePathByRouteName(model.routeName);
} else {
model.routePath = '';
}
}
function handleUpdateI18nKeyByRouteName() {
if (model.routeName) {
model.i18nKey = `route.${model.routeName}` as App.I18n.I18nKey;
} else {
model.i18nKey = null;
}
}
function handleCreateButton() {
const buttonItem: Api.SystemManage.MenuButton = {
code: '',
desc: ''
};
return buttonItem;
}
function getSubmitParams() {
const { layout, page, pathParam, ...params } = model;
const component = transformLayoutAndPageToComponent(layout, page);
const routePath = getRoutePathWithParam(model.routePath, pathParam);
params.component = component;
params.routePath = routePath;
return params;
}
async function handleSubmit() {
await validate();
const params = getSubmitParams();
console.log('params: ', params);
// request
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
watch(visible, () => {
if (visible.value) {
handleInitModel();
restoreValidation();
getRoleOptions();
}
});
watch(
() => model.routeName,
() => {
handleUpdateRoutePathByRouteName();
handleUpdateI18nKeyByRouteName();
}
);
</script>
<template>
<NModal v-model:show="visible" :title="title" preset="card" class="w-800px">
<NScrollbar class="h-480px pr-20px">
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuType')" path="menuType">
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuName')" path="menuName">
<NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routeName')" path="routeName">
<NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routePath')" path="routePath">
<NInput v-model:value="model.routePath" disabled :placeholder="$t('page.manage.menu.form.routePath')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.pathParam')" path="pathParam">
<NInput v-model:value="model.pathParam" :placeholder="$t('page.manage.menu.form.pathParam')" />
</NFormItemGi>
<NFormItemGi v-if="showLayout" span="24 m:12" :label="$t('page.manage.menu.layout')" path="layout">
<NSelect
v-model:value="model.layout"
:options="layoutOptions"
:placeholder="$t('page.manage.menu.form.layout')"
/>
</NFormItemGi>
<NFormItemGi v-if="showPage" span="24 m:12" :label="$t('page.manage.menu.page')" path="page">
<NSelect
v-model:value="model.page"
:options="pageOptions"
:placeholder="$t('page.manage.menu.form.page')"
/>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.i18nKey')" path="i18nKey">
<NInput v-model:value="model.i18nKey" :placeholder="$t('page.manage.menu.form.i18nKey')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.order')" path="order">
<NInputNumber v-model:value="model.order" class="w-full" :placeholder="$t('page.manage.menu.form.order')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.iconTypeTitle')" path="iconType">
<NRadioGroup v-model:value="model.iconType">
<NRadio
v-for="item in menuIconTypeOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.icon')" path="icon">
<template v-if="model.iconType === '1'">
<NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1">
<template #suffix>
<SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
</template>
</NInput>
</template>
<template v-if="model.iconType === '2'">
<NSelect
v-model:value="model.icon"
:placeholder="$t('page.manage.menu.form.localIcon')"
:options="localIconOptions"
/>
</template>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuStatus')" path="status">
<NRadioGroup v-model:value="model.status">
<NRadio
v-for="item in enableStatusOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.keepAlive')" path="keepAlive">
<NRadioGroup v-model:value="model.keepAlive">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.constant')" path="constant">
<NRadioGroup v-model:value="model.constant">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.href')" path="href">
<NInput v-model:value="model.href" :placeholder="$t('page.manage.menu.form.href')" />
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu">
<NRadioGroup v-model:value="model.hideInMenu">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi
v-if="model.hideInMenu"
span="24 m:12"
:label="$t('page.manage.menu.activeMenu')"
path="activeMenu"
>
<NSelect
v-model:value="model.activeMenu"
:options="pageOptions"
clearable
:placeholder="$t('page.manage.menu.form.activeMenu')"
/>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.multiTab')" path="multiTab">
<NRadioGroup v-model:value="model.multiTab">
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
</NRadioGroup>
</NFormItemGi>
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.fixedIndexInTab')" path="fixedIndexInTab">
<NInputNumber
v-model:value="model.fixedIndexInTab"
class="w-full"
clearable
:placeholder="$t('page.manage.menu.form.fixedIndexInTab')"
/>
</NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.query')">
<NDynamicInput
v-model:value="model.query"
preset="pair"
:key-placeholder="$t('page.manage.menu.form.queryKey')"
:value-placeholder="$t('page.manage.menu.form.queryValue')"
>
<template #action="{ index, create, remove }">
<NSpace class="ml-12px">
<NButton size="medium" @click="() => create(index)">
<icon-ic:round-plus class="text-icon" />
</NButton>
<NButton size="medium" @click="() => remove(index)">
<icon-ic-round-remove class="text-icon" />
</NButton>
</NSpace>
</template>
</NDynamicInput>
</NFormItemGi>
<NFormItemGi span="24" :label="$t('page.manage.menu.button')">
<NDynamicInput v-model:value="model.buttons" :on-create="handleCreateButton">
<template #default="{ value }">
<div class="ml-8px flex-y-center flex-1 gap-12px">
<NInput
v-model:value="value.code"
:placeholder="$t('page.manage.menu.form.buttonCode')"
class="flex-1"
/>
<NInput
v-model:value="value.desc"
:placeholder="$t('page.manage.menu.form.buttonDesc')"
class="flex-1"
/>
</div>
</template>
<template #action="{ index, create, remove }">
<NSpace class="ml-12px">
<NButton size="medium" @click="() => create(index)">
<icon-ic:round-plus class="text-icon" />
</NButton>
<NButton size="medium" @click="() => remove(index)">
<icon-ic-round-remove class="text-icon" />
</NButton>
</NSpace>
</template>
</NDynamicInput>
</NFormItemGi>
</NGrid>
</NForm>
</NScrollbar>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NModal>
</template>
<style scoped></style>

View File

@@ -1,79 +0,0 @@
const LAYOUT_PREFIX = 'layout.';
const VIEW_PREFIX = 'view.';
const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
export function getLayoutAndPage(component?: string | null) {
let layout = '';
let page = '';
const [layoutOrPage = '', pageItem = ''] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || [];
layout = getLayout(layoutOrPage);
page = getPage(pageItem || layoutOrPage);
return { layout, page };
}
function getLayout(layout: string) {
return layout.startsWith(LAYOUT_PREFIX) ? layout.replace(LAYOUT_PREFIX, '') : '';
}
function getPage(page: string) {
return page.startsWith(VIEW_PREFIX) ? page.replace(VIEW_PREFIX, '') : '';
}
export function transformLayoutAndPageToComponent(layout: string, page: string) {
const hasLayout = Boolean(layout);
const hasPage = Boolean(page);
if (hasLayout && hasPage) {
return `${LAYOUT_PREFIX}${layout}${FIRST_LEVEL_ROUTE_COMPONENT_SPLIT}${VIEW_PREFIX}${page}`;
}
if (hasLayout) {
return `${LAYOUT_PREFIX}${layout}`;
}
if (hasPage) {
return `${VIEW_PREFIX}${page}`;
}
return '';
}
/**
* Get route name by route path
*
* @param routeName
*/
export function getRoutePathByRouteName(routeName: string) {
return `/${routeName.replace(/_/g, '/')}`;
}
/**
* Get path param from route path
*
* @param routePath route path
*/
export function getPathParamFromRoutePath(routePath: string) {
const [path, param = ''] = routePath.split('/:');
return {
path,
param
};
}
/**
* Get route path with param
*
* @param routePath route path
* @param param path param
*/
export function getRoutePathWithParam(routePath: string, param: string) {
if (param.trim()) {
return `${routePath}/:${param}`;
}
return routePath;
}

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