Compare commits

..

48 Commits

Author SHA1 Message Date
Soybean
7710bcdb0a optimize(projects): optimize code 2024-08-11 23:35:45 +08:00
Ohh
b36d914e48 feat(projects): add plugin typeit (#591) 2024-08-11 23:30:12 +08:00
Soybean
bbacff0581 optimize(projects): optimize plugin gantt 2024-08-06 10:21:46 +08:00
Ohh
f86bca063a feat(projects): add plugin gantt preview (#578) 2024-08-05 10:26:32 +08:00
Soybean
48e005bcaf Merge branch 'main' into example 2024-08-01 16:06:59 +08:00
Soybean
d825b6e260 feat(projects): add watermark override theme settings 2024-07-30 21:50:22 +08:00
Soybean
05b0147b47 Merge branch 'main' into example 2024-07-30 18:04:14 +08:00
Soybean
e7cd34ec37 chore(projects): Merge branch 'main' into example 2024-07-30 17:26:42 +08:00
Ohh
a9dce21878 feat(projects): add plugin pdf preview (#568) 2024-07-27 03:06:11 +08:00
Soybean
24320ab899 Merge branch 'main' into example 2024-07-22 19:05:11 +08:00
青菜白玉汤
2f1f2c2b2a fix(projects): fix the issue of abnormal width of the sidebar in the top menu mix and reverse mode (#562)
(cherry picked from commit c469512bd4)
2024-07-22 13:34:35 +08:00
Soybean
3a970a090d fix(components): fix component name and iconify icon name conflict 2024-07-22 01:12:00 +08:00
Soybean
4a7e9c2669 chore(projects): merge branch main into example 2024-07-22 00:57:11 +08:00
Soybean
d6f3effce0 chore(deps): update deps 2024-07-22 00:35:53 +08:00
Soybean
fa4367a7b9 optimize(projects): optimize page code: plugin_excel 2024-07-22 00:34:13 +08:00
Ohh
c425822b3a feat(projects): add plugin excel export (#561) 2024-07-21 20:54:18 +08:00
Soybean
447bda2b48 chore(projects): merge branch main into example 2024-07-20 02:35:42 +08:00
dodu2014
c0303a8637 fix(types): Fixed the reference type error (#551) 2024-07-19 18:12:29 +08:00
Ohh
c7ab61bbd4 feat(projects): add plugin pinyin-pro (#557) 2024-07-19 16:20:36 +08:00
Soybean
ec6a497452 fix(projects): fix page manage-role default-expanded-names 2024-07-19 10:19:33 +08:00
paynezhuang
074077dfaa feat(projects): Manage search area using collapsed components , close #537 (#549) 2024-07-15 14:46:04 +08:00
Soybean
fcf866783c chore(projects): merge branch main into example 2024-07-12 10:28:10 +08:00
Soybean
1c9e36c47b chore(projects): merge branch main into example 2024-06-21 10:15:20 +08:00
Soybean
d3210edc6a chore(projects): merge branch main into example 2024-06-15 01:13:36 +08:00
Soybean
799ebcc714 Merge branch 'main' into example 2024-06-14 00:05:12 +08:00
Soybean
4a8b9032cd chore(projects): merge branch main into example 2024-06-13 23:30:33 +08:00
Soybean
8fea803fca Merge branch 'main' into example 2024-06-12 11:10:08 +08:00
Soybean
599d0f10e8 Merge branch 'main' into example 2024-06-07 11:55:56 +08:00
Soybean
781f88d227 Merge branch 'main' into example 2024-06-06 23:33:41 +08:00
Soybean
5739346484 chore(projects): merge main to example 2024-06-06 10:36:31 +08:00
Soybean
d194f0b1b6 chore(projects): merge main to example 2024-06-04 20:24:34 +08:00
Soybean
19bea283c9 chore(projects): merge main to example 2024-06-02 01:30:02 +08:00
Soybean
53a8e5678a chore(projects): merge main to example 2024-05-24 02:28:43 +08:00
Soybean
10b231918a chore(projects): merge main to example 2024-05-20 01:24:46 +08:00
Soybean
81d51a9630 fix(projects): fix baidu map request protocol 2024-05-08 23:19:49 +08:00
Soybean
fbebef3acb fix(projects): fix page: plugin_video style 2024-05-08 23:01:38 +08:00
Soybean
12d7bf94ee optimize(projects): optimize code 2024-05-08 22:57:22 +08:00
Soybean
0478154db8 chore(projects): update pnpm-lock.yaml 2024-05-08 22:39:16 +08:00
Soybean
994ef2b9b1 merge main to example (#433)
* docs(projects): update CHANGELOG

* fix(styles): fix useTable type
2024-05-08 22:37:18 +08:00
paynezhuang
ef32eecfdb feat(projects): add page plugin_barcode (#432) 2024-05-08 22:35:12 +08:00
Soybean
83ae0bad0f docs(projects): update CHANGELOG (#431) 2024-05-07 22:02:11 +08:00
Soybean
9a9e2d144a chore(projects): lock deps version 2024-05-07 21:59:23 +08:00
lisong
a2110a98cf feat(projects): add page plugin_editor、plugin_icon、plugin_map、plugin_print、plugin_swiper and plugin_video (#430) 2024-05-07 21:56:23 +08:00
Soybean
daf6600c21 feat(projects): add page plugin_charts_echarts 2024-05-07 19:51:54 +08:00
Soybean
a9b3a961af Merge branch 'main' into example 2024-05-07 19:18:27 +08:00
Soybean
3a35d62e6f Merge branch 'main' into example 2024-05-07 18:22:22 +08:00
Soybean
aaf28a42c2 optimize(projects): optimize plugin_copy 2024-05-07 18:20:23 +08:00
恕瑞玛的皇帝
019f80faef feat(projects): 添加插件示例:剪贴板 2024-05-07 17:24:16 +08:00
156 changed files with 11955 additions and 4372 deletions

10
.env
View File

@@ -1,5 +1,3 @@
# the base url of the application, the default is "/"
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
VITE_BASE_URL=/
VITE_APP_TITLE=SoybeanAdmin
@@ -38,7 +36,7 @@ VITE_SERVICE_LOGOUT_CODES=8888,8889
VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778
# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998,3333
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998
# when the route mode is static, the defined super role
VITE_STATIC_SUPER_ROLE=R_SUPER
@@ -48,9 +46,3 @@ VITE_SOURCE_MAP=N
# Used to differentiate storage across different domains
VITE_STORAGE_PREFIX=SOY_
# used to control whether the program automatically detects updates
VITE_AUTOMATICALLY_DETECT_UPDATE=Y
# show proxy url log in terminal
VITE_PROXY_LOG=Y

View File

@@ -6,6 +6,9 @@
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"formulahendry.auto-close-tag",
"formulahendry.auto-complete-tag",
"formulahendry.auto-rename-tag",
"lokalise.i18n-ally",
"mhutchie.git-graph",
"mikestead.dotenv",

View File

@@ -1,281 +1,6 @@
# Changelog
## [v1.3.12](https://github.com/soybeanjs/soybean-admin/compare/v1.3.11...v1.3.12) (2025-03-12)
###    🚀 Features
- **projects**:
- support loading page dark mode adaptation. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/702 [<samp>(9b945)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9b9455d9)
- tab support touch event &nbsp;-&nbsp; by @soybeanjs [<samp>(a03be)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a03becda)
- support proxy log in terminal &nbsp;-&nbsp; by @soybeanjs [<samp>(4cc14)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4cc1487f)
- **projects): feat(projects**:
- TableColumnCheck title support VNode &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/716 [<samp>(a1a5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a1a5c74c)
- **utils**:
- support replaceTab. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/713 [<samp>(be608)</samp>](https://github.com/soybeanjs/soybean-admin/commit/be6080ba)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**:
- hidden multi-language button in login page. fix #694 &nbsp;-&nbsp; by **Azir** in https://github.com/soybeanjs/soybean-admin/issues/694 [<samp>(54e7d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/54e7d6d0)
- fix multiple calls to the login API when clicking quickly. fixed #697 &nbsp;-&nbsp; by @zsdycs in https://github.com/soybeanjs/soybean-admin/issues/698 and https://github.com/soybeanjs/soybean-admin/issues/697 [<samp>(86da7)</samp>](https://github.com/soybeanjs/soybean-admin/commit/86da767e)
- fix multiple calls to the login API when clicking quickly. fixed #697 " &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/698 and https://github.com/soybeanjs/soybean-admin/issues/697 [<samp>(15163)</samp>](https://github.com/soybeanjs/soybean-admin/commit/15163d70)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(132e1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/132e1012)
- update deps &nbsp;-&nbsp; by **Azir** [<samp>(52c33)</samp>](https://github.com/soybeanjs/soybean-admin/commit/52c336d7)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(b8112)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b8112613)
- **projects**:
- update unocss preset &nbsp;-&nbsp; by @Wangijun in https://github.com/soybeanjs/soybean-admin/issues/712 [<samp>(3e007)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e0076d4)
### &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;[![Wangijun](https://github.com/Wangijun.png?size=48)](https://github.com/Wangijun)&nbsp;&nbsp;[![zsdycs](https://github.com/zsdycs.png?size=48)](https://github.com/zsdycs)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.11](https://github.com/soybeanjs/soybean-admin/compare/v1.3.10...v1.3.11) (2025-01-19)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**: multi language buttons support hiding. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/683 [<samp>(d7aeb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d7aebb7)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **hooks**:
- The total number before assigning a value to the table is incorrect. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/687 [<samp>(56760)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5676024)
- **projects**:
- fix login success notification. fixed #688 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/688 [<samp>(60dd2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/60dd226)
- fix update notifications. fixed #691, fixed #692 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/691 and https://github.com/soybeanjs/soybean-admin/issues/692 [<samp>(ac862)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ac86247)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: optimize code &nbsp;-&nbsp; by @soybeanjs [<samp>(6489e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/6489ec4)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update README &nbsp;-&nbsp; by @soybeanjs [<samp>(21434)</samp>](https://github.com/soybeanjs/soybean-admin/commit/214341e)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(c9433)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c9433e1)
### &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;
## [v1.3.10](https://github.com/honghuangdc/soybean-admin/compare/v1.3.9...v1.3.10) (2024-12-16)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**: support show tab when not loggedIn &nbsp;-&nbsp; by @soybeanjs [<samp>(ba381)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ba38119)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **deps**:
- reduced eslint version to 9.14.0 &nbsp;-&nbsp; by @soybeanjs [<samp>(af417)</samp>](https://github.com/honghuangdc/soybean-admin/commit/af4177e)
- **projects**:
- fix route guard hook `onRouteSwitchWhenLoggedIn`. fixed #680 &nbsp;-&nbsp; by @soybeanjs in https://github.com/honghuangdc/soybean-admin/issues/680 [<samp>(ab985)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ab9858c)
- check if init userInfo when initAuthRoute. fixed #680 &nbsp;-&nbsp; by @soybeanjs in https://github.com/honghuangdc/soybean-admin/issues/680 [<samp>(9f4fb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/9f4fb8c)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**:
- optimize router guard &nbsp;-&nbsp; by @soybeanjs [<samp>(0dfcf)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0dfcf18)
- use `defu` to fill added theme config &nbsp;-&nbsp; by @soybeanjs [<samp>(101b6)</samp>](https://github.com/honghuangdc/soybean-admin/commit/101b6f9)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: ✏️ add element-plus version link &nbsp;-&nbsp; by **一寸灰** in https://github.com/honghuangdc/soybean-admin/issues/679 [<samp>(5c6ab)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5c6ab0b)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- rename env.d.ts to vite-env.d.ts &nbsp;-&nbsp; by @wzc520pyfm in https://github.com/honghuangdc/soybean-admin/issues/675 [<samp>(b93c2)</samp>](https://github.com/honghuangdc/soybean-admin/commit/b93c203)
- **projects**: update deps & fix vite config &nbsp;-&nbsp; by @soybeanjs [<samp>(3e0eb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3e0eb72)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![wzc520pyfm](https://github.com/wzc520pyfm.png?size=48)](https://github.com/wzc520pyfm)&nbsp;&nbsp;
[一寸灰](mailto:webzhangfei@163.com),&nbsp;
## [v1.3.9](https://github.com/soybeanjs/soybean-admin/compare/v1.3.8...v1.3.9) (2024-11-17)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**:
- support scheduled detection and update system. close #657 &nbsp;-&nbsp; by **青菜白玉汤** in https://github.com/soybeanjs/soybean-admin/issues/669 and https://github.com/soybeanjs/soybean-admin/issues/657 [<samp>(d088f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d088f81)
- app version notification plugin support sub deploy path. close #668 &nbsp;-&nbsp; by **Soybean** in https://github.com/soybeanjs/soybean-admin/issues/668 [<samp>(a53eb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a53eb10)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: optimize code &nbsp;-&nbsp; by **Soybean** [<samp>(9ad5d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9ad5d71)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **projects**: refactor router guard. fix #655 &nbsp;-&nbsp; by **Soybean** in https://github.com/soybeanjs/soybean-admin/issues/667 and https://github.com/soybeanjs/soybean-admin/issues/655 [<samp>(09144)</samp>](https://github.com/soybeanjs/soybean-admin/commit/09144df)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: set the Chinese version of README as default &nbsp;-&nbsp; by **Soybean** [<samp>(9d28b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9d28b31)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by **Soybean** [<samp>(91de4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/91de4a8)
- update deps &nbsp;-&nbsp; by **Soybean** [<samp>(0c809)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0c809de)
- **projects**:
- add desc for base url of app &nbsp;-&nbsp; by **Soybean** [<samp>(17d7e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/17d7e52)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[Soybean](mailto:soybeanjs@outlook.com),&nbsp;[青菜白玉汤](mailto:79054161+Azir-11@users.noreply.github.com)
## [v1.3.8](https://github.com/soybeanjs/soybean-admin/compare/v1.3.7...v1.3.8) (2024-10-25)
### &nbsp;&nbsp;&nbsp;🚨 Breaking Changes
- **projects**: refactor route cache & support reset route cache strategy &nbsp;-&nbsp; by @soybeanjs [<samp>(b667e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b667eab)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **packages**:
- add subpackage `@sa/alova` &nbsp;-&nbsp; by @JOU-amjs in https://github.com/soybeanjs/soybean-admin/issues/640 [<samp>(2072f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2072f58)
- optimistic subpackage `@sa/alova` &nbsp;-&nbsp; by @JOU-amjs in https://github.com/soybeanjs/soybean-admin/issues/646 [<samp>(4b3ac)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4b3ac11)
- **projects**:
- login supports accessible operation. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/637 [<samp>(cfaab)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cfaab85)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **utils**: fix `isPC`. fixed #644 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/644 [<samp>(47264)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4726498)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(8dcda)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8dcda38)
- **projects**:
- update vscode extensions &nbsp;-&nbsp; by @soybeanjs [<samp>(24bb6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/24bb6d9)
- update deps & fix sass usage &nbsp;-&nbsp; by @soybeanjs [<samp>(71e63)</samp>](https://github.com/soybeanjs/soybean-admin/commit/71e6307)
- **types**:
- remove type declaration for document.startViewTransition (TypeScript 5.6 includes it) &nbsp;-&nbsp; by @NHZEX in https://github.com/soybeanjs/soybean-admin/issues/633 [<samp>(83ba7)</samp>](https://github.com/soybeanjs/soybean-admin/commit/83ba798)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**: reduce ambiguity in theme configuration instructions. &nbsp;-&nbsp; by @Azir-11 [<samp>(75cbf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/75cbfbb)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![JOU-amjs](https://github.com/JOU-amjs.png?size=48)](https://github.com/JOU-amjs)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;[![NHZEX](https://github.com/NHZEX.png?size=48)](https://github.com/NHZEX)&nbsp;&nbsp;
## [v1.3.7](https://github.com/soybeanjs/soybean-admin/compare/v1.3.6...v1.3.7) (2024-09-21)
### &nbsp;&nbsp;&nbsp;🚨 Breaking Changes
- **projects**: update scss config &nbsp;-&nbsp; by @soybeanjs [<samp>(24e9e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/24e9e57)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **projects**: fix global-tab click conflict with contextmenu &nbsp;-&nbsp; by @soybeanjs [<samp>(3e72c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e72c3b)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **packages**: @sa/materials: remove tab close shortcut by mouse &nbsp;-&nbsp; by @soybeanjs [<samp>(4da58)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4da588c)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(baefd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/baefdfd)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;
## [v1.3.6](https://github.com/soybeanjs/soybean-admin/compare/v1.3.5...v1.3.6) (2024-09-20)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **components**:
- fix VerticalMixMenu name &nbsp;-&nbsp; by @soybeanjs [<samp>(20f8e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/20f8ed3)
- **projects**:
- fix click global-tab in iPad. fixed #624 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/624 [<samp>(04d05)</samp>](https://github.com/soybeanjs/soybean-admin/commit/04d0564)
- when the roles filter submenu is empty, the parent menu is not excluded. fixed #621. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/626 and https://github.com/soybeanjs/soybean-admin/issues/621 [<samp>(0ac95)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0ac95bd)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**:
- optimize code &nbsp;-&nbsp; by @soybeanjs [<samp>(6561f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/6561f0b)
- optimize code &nbsp;-&nbsp; by @soybeanjs [<samp>(38eeb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/38eeb67)
- remove defineModel settingenabled by default &nbsp;-&nbsp; by @yanbowe in https://github.com/soybeanjs/soybean-admin/issues/620 [<samp>(60bbd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/60bbd2d)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: update CHANGELOG &nbsp;-&nbsp; by @soybeanjs [<samp>(5baf1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5baf19d)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(207d6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/207d6eb)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(f3562)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f35627e)
### &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;[![yanbowe](https://github.com/yanbowe.png?size=48)](https://github.com/yanbowe)&nbsp;&nbsp;
## [v1.3.5](https://github.com/soybeanjs/soybean-admin/compare/v1.3.4...v1.3.5) (2024-09-07)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **packages**:
- @sa/axios: add response to flatRequest when success &nbsp;-&nbsp; by @soybeanjs [<samp>(c4e16)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c4e1610)
- **projects**:
- README.zh_CN.md 添加合作推广 &nbsp;-&nbsp; by @PZ-18664918826 in https://github.com/soybeanjs/soybean-admin/issues/601 [<samp>(2fa40)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2fa400b)
- Add more commit types according to Apache specifications &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/610 [<samp>(878d9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/878d9c3)
- does the configuration support automatic updates. close#612 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/617 and https://github.com/soybeanjs/soybean-admin/issues/612 [<samp>(4c9f4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4c9f4e0)
- add app error handler. close #587 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/587 [<samp>(be855)</samp>](https://github.com/soybeanjs/soybean-admin/commit/be8556c)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **deps**:
- move json5 from devDependencies to dependencies to support production usage &nbsp;-&nbsp; by @mufeng889 in https://github.com/soybeanjs/soybean-admin/issues/618 [<samp>(7cb43)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7cb43fc)
- **projects**:
- avoid retrieving cached HTML &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/605 [<samp>(ef6cf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ef6cf93)
- fix login redirect &nbsp;-&nbsp; by @soybeanjs [<samp>(3830e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3830ec7)
- fix vertical-mix-menu when sider collapse. fixed #608 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/608 [<samp>(c3f1f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c3f1f69)
- fix breadcrumb when activeMenu is parent menu. fixed #589 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/589 [<samp>(79b2a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/79b2a28)
- fix refresh token when meet multi requests. fixed #581 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/581 [<samp>(27b52)</samp>](https://github.com/soybeanjs/soybean-admin/commit/27b5222)
- **types**:
- fix the type of TableApiFn &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/599 [<samp>(26c93)</samp>](https://github.com/soybeanjs/soybean-admin/commit/26c93df)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: optimize menu selectedKey &nbsp;-&nbsp; by @soybeanjs [<samp>(531bf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/531bfaf)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**:
- update README &nbsp;-&nbsp; by @mufeng889 in https://github.com/soybeanjs/soybean-admin/issues/594 [<samp>(a8f92)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a8f923e)
- update README &nbsp;-&nbsp; by @soybeanjs [<samp>(e9a2e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e9a2ee4)
- update README &nbsp;-&nbsp; by @soybeanjs [<samp>(73e91)</samp>](https://github.com/soybeanjs/soybean-admin/commit/73e917a)
- update the location of important information in the document &nbsp;-&nbsp; by **Azir** [<samp>(9c012)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9c012c7)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(a1c14)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a1c14a1)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(7fa55)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7fa5590)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(a44ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a44ea62)
- **projects**:
- use json5 resolve env `VITE_OTHER_SERVICE_BASE_URL` & fix proxy enable &nbsp;-&nbsp; by @soybeanjs [<samp>(b16a9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b16a963)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**: rename script czh to commit:zh &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/597 [<samp>(5094f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5094f0e)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![mufeng889](https://github.com/mufeng889.png?size=48)](https://github.com/mufeng889)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;[![PZ-18664918826](https://github.com/PZ-18664918826.png?size=48)](https://github.com/PZ-18664918826)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.4](https://github.com/honghuangdc/soybean-admin/compare/v1.3.3...v1.3.4) (2024-08-01)
### &nbsp;&nbsp;&nbsp;🚨 Breaking Changes

View File

@@ -1,92 +1,5 @@
# 更新日志
## [v1.3.5](https://github.com/soybeanjs/soybean-admin/compare/v1.3.4...v1.3.5) (2024-09-07)
### &nbsp;&nbsp;&nbsp;🚀 新功能
- **packages**:
- @sa/axios: 成功时将响应添加到 flatRequest &nbsp;-&nbsp; by @soybeanjs [<samp>(c4e16)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c4e1610)
- **projects**:
- README.zh_CN.md 添加合作推广 &nbsp;-&nbsp; by @PZ-18664918826 in https://github.com/soybeanjs/soybean-admin/issues/601 [<samp>(2fa40)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2fa400b)
- 根据 Apache 规范添加更多提交类型 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/610 [<samp>(878d9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/878d9c3)
- 配置是否支持自动更新。关闭#612 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/617 和 https://github.com/soybeanjs/soybean-admin/issues/612 [<samp>(4c9f4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4c9f4e0)
- 添加应用错误处理程序。关闭 #587 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/587 [<samp>(be855)</samp>](https://github.com/soybeanjs/soybean-admin/commit/be8556c)
### &nbsp;&nbsp;&nbsp;🐞 Bug 修复
- **deps**:
- 将 json5 从 devDependencies 移到 dependencies 以支持生产环境使用 &nbsp;-&nbsp; by @mufeng889 in https://github.com/soybeanjs/soybean-admin/issues/618 [<samp>(7cb43)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7cb43fc)
- **projects**:
- 避免检索缓存的 HTML &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/605 [<samp>(ef6cf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ef6cf93)
- 修复登录重定向 &nbsp;-&nbsp; by @soybeanjs [<samp>(3830e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3830ec7)
- 修复侧边栏折叠时的 vertical-mix-menu。修复 #608 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/608 [<samp>(c3f1f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c3f1f69)
- 修复 activeMenu 为父菜单时的面包屑。修复 #589 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/589 [<samp>(79b2a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/79b2a28)
- 修复遇到多请求时的刷新令牌。修复 #581 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/581 [<samp>(27b52)</samp>](https://github.com/soybeanjs/soybean-admin/commit/27b5222)
- **types**:
- 修复 TableApiFn 的类型 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/599 [<samp>(26c93)</samp>](https://github.com/soybeanjs/soybean-admin/commit/26c93df)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **projects**: 优化菜单 selectedKey &nbsp;-&nbsp; by @soybeanjs [<samp>(531bf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/531bfaf)
### &nbsp;&nbsp;&nbsp;📖 文档
- **projects**:
- 更新 README &nbsp;-&nbsp; by @mufeng889 in https://github.com/soybeanjs/soybean-admin/issues/594 [<samp>(a8f92)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a8f923e)
- 更新 README &nbsp;-&nbsp; by @soybeanjs [<samp>(e9a2e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e9a2ee4)
- 更新 README &nbsp;-&nbsp; by @soybeanjs [<samp>(73e91)</samp>](https://github.com/soybeanjs/soybean-admin/commit/73e917a)
- 更新文档中重要信息的位置 &nbsp;-&nbsp; by **Azir** [<samp>(9c012)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9c012c7)
### &nbsp;&nbsp;&nbsp;🏡 杂项
- **deps**:
- 更新依赖 &nbsp;-&nbsp; by @soybeanjs [<samp>(a1c14)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a1c14a1)
- 更新依赖 &nbsp;-&nbsp; by @soybeanjs [<samp>(7fa55)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7fa5590)
- 更新依赖 &nbsp;-&nbsp; by @soybeanjs [<samp>(a44ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a44ea62)
- **projects**:
- 使用 json5 解析环境变量 `VITE_OTHER_SERVICE_BASE_URL` 并修复代理启用 &nbsp;-&nbsp; by @soybeanjs [<samp>(b16a9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b16a963)
### &nbsp;&nbsp;&nbsp;🎨 样式
- **projects**: 将脚本 czh 重命名为 commit:zh &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/597 [<samp>(5094f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5094f0e)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![mufeng889](https://github.com/mufeng889.png?size=48)](https://github.com/mufeng889)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;[![PZ-18664918826](https://github.com/PZ-18664918826.png?size=48)](https://github.com/PZ-18664918826)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;
## [v1.3.4](https://github.com/honghuangdc/soybean-admin/compare/v1.3.3...v1.3.4) (2024-08-01)
### &nbsp;&nbsp;&nbsp;🚨 重大变更
- **projects**: 当 vertical-mix 布局为 mixSiderFixed 时,不重置活动菜单 &nbsp;-&nbsp; by @honghuangdc [<samp>(939c5)</samp>](https://github.com/honghuangdc/soybean-admin/commit/939c512)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **projects**: 优化代码 &nbsp;-&nbsp; by @honghuangdc [<samp>(cb1d4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cb1d445)
### &nbsp;&nbsp;&nbsp;🏡 杂项
- **projects**: 更新依赖并修复 vue-router 类型 &nbsp;-&nbsp; by @honghuangdc [<samp>(96837)</samp>](https://github.com/honghuangdc/soybean-admin/commit/968370b)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![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 修复
- **projects**: 修复水印设置 &nbsp;-&nbsp; by @honghuangdc [<samp>(5646a)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5646a50)
### &nbsp;&nbsp;&nbsp;📖 文档
- **projects**: 更新 CHANGELOG &nbsp;-&nbsp; by @honghuangdc [<samp>(ebc83)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ebc838c)
### &nbsp;&nbsp;&nbsp;❤️ 贡献者
[![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;🚀 新功能

View File

@@ -1,184 +0,0 @@
<div align="center">
<img src="./public/favicon.svg" width="160" />
<h1>SoybeanAdmin</h1>
<span><a href="./README.md">中文</a> | English</span>
</div>
---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
[![github stars](https://img.shields.io/github/stars/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> [!NOTE]
> If you think `SoybeanAdmin` is helpful to you, or you like our project, please give us a ⭐️ on GitHub. Your support is the driving force for us to continue to improve and add new features! Thank you for your support!
## Introduction
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful 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.
## Features
- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite5, TypeScript, Pinia and UnoCSS.
- **Clear project architecture**: using pnpm monorepo architecture, clear structure, elegant and easy to understand.
- **Strict code specifications**: follow the [SoybeanJS specification](https://docs.soybeanjs.cn/standard), integrate eslint, prettier and simple-git-hooks to ensure the code is standardized.
- **TypeScript**: support strict type checking to improve code maintainability.
- **Rich theme configuration**: built-in a variety of theme configurations, perfectly integrated with UnoCSS.
- **Built-in internationalization solution**: easily realize multi-language support.
- **Automated file routing system**: automatically generate route import, declaration and type. For more details, please refer to [Elegant Router](https://github.com/soybeanjs/elegant-router).
- **Flexible permission routing**: support both front-end static routing and back-end dynamic routing.
- **Rich page components**: built-in a variety of pages and components, including 403, 404, 500 pages, as well as layout components, tag components, theme configuration components, etc.
- **Command line tool**: built-in efficient command line tool, git commit, delete file, release, etc.
- **Mobile adaptation**: perfectly support mobile terminal to realize adaptive layout.
## Version
- **NaiveUI Version:**
- [Preview Link](https://naive.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin)
- **AntDesignVue Version:**
- [Preview Link](https://antd.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin-antd)
- **ElementPlus Version:**
- [Preview Link](https://elp.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-element-plus)
- **Legacy Version:**
- [Preview Link](https://legacy.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin/tree/legacy)
## Documentation
- [Link](https://docs.soybeanjs.cn)
- [Legacy Docs](https://legacy-docs.soybeanjs.cn)
## Example Images
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-01.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-02.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-03.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-04.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-05.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-06.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-07.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-08.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-09.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-10.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-mobile.png)
## Usage
**Environment Preparation**
Make sure your environment meets the following requirements:
- **git**: you need git to clone and manage project versions.
- **NodeJS**: >=18.12.0, recommended 18.19.0 or higher.
- **pnpm**: >= 8.7.0, recommended 8.14.0 or higher.
**Clone Project**
```bash
git clone https://github.com/soybeanjs/soybean-admin.git
```
**Install Dependencies**
```bash
pnpm i
```
> Since this project uses the pnpm monorepo management method, please do not use npm or yarn to install dependencies.
**Start Project**
```bash
pnpm dev
```
**Build Project**
```bash
pnpm build
```
**Code Synchronization**
Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) document.
## Ecosystem
- [react-soybean-admin](https://github.com/mufeng889/react-soybean-admin): SoybeanAdmin based version of React.
- [electron-mock-admin](https://github.com/lixin59/electron-mock-api): A Mock Api management system that helps front-end developers quickly implement interface mocks.
- [T-Shell](https://github.com/TheBlindM/T-Shell): A terminal emulator and SSH client with configurable command prompts.
- [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin).
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): A backend management framework developed based on Vue3/TypeScript/NaiveUI and NET7 & Sqlsugar. It is implemented in the most original and simplest way, with a fresh and elegant front-end, a clear and elegant backend structure, and powerful functions.
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): Adopting SpringBoot 3, SaToken, MySQL and other frameworks to develop and modify [soybean-admin](https://github.com/soybeanjs/soybean-admin) for the second time, adapting dynamic menu/button-level authorization. Retaining the original flavor, fresh and elegant, high-value back-end management system scaffold.
- [snail-job](https://github.com/aizuda/snail-job): A distributed task retry and task scheduling platform with "high performance, high value and high activity".
- [SuperApi](https://github.com/TmmTop/SuperApi): Quickly turn your idea into an online stable product! Entity-less library and table building, add, delete, change and check entity-less library table, support 15 kinds of condition query, as well as paging, list, unlimited tree list and other functions of the API deployment! With interface documentation, Auth authorisation, interface flow restriction, access to the client's real IP, advanced server caching components, dynamic APIs and other features, we look forward to your experience!
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): A modern Management Platform based on FastAPI+Vue3+Naive UI.
## How to Contribute
We warmly welcome and appreciate all forms of contributions. If you have any ideas or suggestions, please feel free to share them by submitting [pull requests](https://github.com/soybeanjs/soybean-admin/pulls) or creating GitHub [issue](https://github.com/soybeanjs/soybean-admin/issues/new).
## Git Commit Guidelines
This project has built-in `commit` command, you can execute `pnpm commit` to generate commit information that conforms to [Conventional Commits](https://www.conventionalcommits.org/) specification. When submitting PR, please be sure to use `commit` command to create commit information to ensure the standardization of information.
## Browser Support
It is recommended to use the latest version of Chrome in development for a better experience.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) |
| --- | --- | --- | --- | --- |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## OpenSource Author
[Soybean](https://github.com/honghuangdc)
## Contributors
Thanks the following people for their contributions. If you want to contribute to this project, please refer to [How to Contribute](#how-to-contribute).
<a href="https://github.com/soybeanjs/soybean-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a>
## Communication
`SoybeanAdmin` is a completely open source and free project, helping developers to develop medium and large-scale management systems more conveniently. It also provides WeChat and QQ communication groups. If you have any questions, please feel free to ask in the group.
<div>
<p>QQ Group</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-3.jpg" style="width:200px" />
</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>
<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" />
</div>
## Star Trend
[![Star History Chart](https://api.star-history.com/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.com/#soybeanjs/soybean-admin&Date)
## License
This project is based on the [MIT © 2021 Soybean](./LICENSE) protocol, for learning purposes only, please retain the author's copyright information for commercial use, the author does not guarantee and is not responsible for the software.

190
README.md
View File

@@ -1,94 +1,65 @@
<div align="center">
<img src="./public/favicon.svg" width="160" />
<h1>SoybeanAdmin</h1>
<span>中文 | <a href="./README.en_US.md">English</a></span>
<span>English | <a href="./README.zh_CN.md">中文</a></span>
</div>
---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
[![github stars](https://img.shields.io/github/stars/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github stars](https://img.shields.io/github/stars/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> [!NOTE]
> 如果您觉得 `SoybeanAdmin`对您有所帮助,或者您喜欢我们的项目,请在 GitHub 上给我们一个 ⭐️。您的支持是我们持续改进和增加新功能的动力!感谢您的支持!
> If you think `SoybeanAdmin` is helpful to you, or you like our project, please give us a ⭐️ on GitHub. Your support is the driving force for us to continue to improve and add new features! Thank you for your support!
## 简介
## Introduction
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia UnoCSS。它内置了丰富的主题配置和组件,代码规范严谨,实现了自动化的文件路由系统。此外,它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。
## 特性
- **前沿技术应用**:采用 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。
- **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。
- **严格的代码规范**:遵循 [SoybeanJS 规范](https://docs.soybeanjs.cn/zh/standard)集成了eslint, prettier 和 simple-git-hooks保证代码的规范性。
- **TypeScript** 支持严格的类型检查,提高代码的可维护性。
- **丰富的主题配置**:内置多样的主题配置,与 UnoCSS 完美结合。
- **内置国际化方案**:轻松实现多语言支持。
- **自动化文件路由系统**:自动生成路由导入、声明和类型。更多细节请查看 [Elegant Router](https://github.com/soybeanjs/elegant-router)。
- **灵活的权限路由**:同时支持前端静态路由和后端动态路由。
- **丰富的页面组件**内置多样页面和组件包括403、404、500页面以及布局组件、标签组件、主题配置组件等。
- **命令行工具**内置高效的命令行工具git提交、删除文件、发布等。
- **移动端适配**:完美支持移动端,实现自适应布局。
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful 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.
## 版本
## Features
- **NaiveUI 版本:**
- [预览地址](https://naive.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin)
- **AntDesignVue 版本:**
- [预览地址](https://antd.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-antd)
- **ElementPlus 版本:**
- [预览地址](https://elp.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-element-plus)
- **旧版:**
- [预览地址](https://legacy.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin/tree/legacy)
- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite5, TypeScript, Pinia and UnoCSS.
- **Clear project architecture**: using pnpm monorepo architecture, clear structure, elegant and easy to understand.
- **Strict code specifications**: follow the [SoybeanJS specification](https://docs.soybeanjs.cn/standard), integrate eslint, prettier and simple-git-hooks to ensure the code is standardized.
- **TypeScript**: support strict type checking to improve code maintainability.
- **Rich theme configuration**: built-in a variety of theme configurations, perfectly integrated with UnoCSS.
- **Built-in internationalization solution**: easily realize multi-language support.
- **Automated file routing system**: automatically generate route import, declaration and type. For more details, please refer to [Elegant Router](https://github.com/soybeanjs/elegant-router).
- **Flexible permission routing**: support both front-end static routing and back-end dynamic routing.
- **Rich page components**: built-in a variety of pages and components, including 403, 404, 500 pages, as well as layout components, tag components, theme configuration components, etc.
- **Command line tool**: built-in efficient command line tool, git commit, delete file, release, etc.
- **Mobile adaptation**: perfectly support mobile terminal to realize adaptive layout.
## 文档
## Version
- [地址](https://docs.soybeanjs.cn)
- [旧版文档](https://legacy-docs.soybeanjs.cn)
- **NaiveUI Version:**
- [Preview Link](https://naive.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin)
- **AntDesignVue Version:**
- [Preview Link](https://antd.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin-antd)
- **Legacy Version:**
- [Preview Link](https://legacy.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin/tree/legacy)
## 合作事项
## Documentation
我们非常感谢大家对 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的支持!为了进一步回馈社区,并助力企业和开发者实现个性化需求,我们现提供多种合作服务,期待与您携手共赢。
- [Link](https://docs.soybeanjs.cn)
- [Legacy Docs](https://legacy-docs.soybeanjs.cn)
##### 1、定制化管理后台开发
针对企业和开发者的特定业务需求,我们提供基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的定制化管理后台开发服务。我们的团队具备丰富的行业经验,能够迅速理解并实现您的需求,打造高效、灵活且安全的定制化解决方案。
- **定制开发**我们将根据您的具体需求提供从需求分析、UI设计到功能实现的全方位服务确保项目高效交付。
- **功能扩展**:在 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 基础上,扩展您所需的特定功能模块,提升管理后台的功能和用户体验。
##### 2、企业外包服务
我们承接各类企业级外包项目,特别是在管理后台系统的开发、集成与运维方面。我们以精益求精的态度,确保项目的质量和进度,为您的业务提供强有力的技术支持。
- **项目开发**:无论是全新的项目,还是现有系统的优化与集成,我们都将为您量身打造高效可靠的解决方案。
- **系统集成与维护**:我们也提供基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的系统集成与长期维护服务,确保您的系统稳定、安全地运行。
##### 3、联系方式
如有合作意向或项目咨询,请通过以下方式与我们联系:
- **Email**: [soybeanjs@outlook.com](mailto:soybeanjs@outlook.com)
- **GitHub Issues**: 欢迎通过 [GitHub Issues](https://github.com/soybeanjs/soybean-admin/issues/new) 联系我们,进行初步的合作洽谈。
- **商务合作微信**: honghuangdc
期待与您开展深入合作,共同推动 SoybeanAdmin 项目及其在更多领域的成功应用!
## 示例图片
## Example Images
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-01.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-02.png)
@@ -103,109 +74,106 @@
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-mobile.png)
## 使用
## Usage
**环境准备**
**Environment Preparation**
确保你的环境满足以下要求:
Make sure your environment meets the following requirements:
- **git**: 你需要git来克隆和管理项目版本。
- **NodeJS**: >=18.12.0,推荐 18.19.0 或更高。
- **pnpm**: >= 8.7.0,推荐 8.14.0 或更高。
- **git**: you need git to clone and manage project versions.
- **NodeJS**: >=18.12.0, recommended 18.19.0 or higher.
- **pnpm**: >= 8.7.0, recommended 8.14.0 or higher.
**克隆项目**
**Clone Project**
```bash
git clone https://github.com/soybeanjs/soybean-admin.git
```
**安装依赖**
**Install Dependencies**
```bash
pnpm i
```
> 由于本项目采用了 pnpm monorepo 的管理方式,因此请不要使用 npm yarn 来安装依赖。
> Since this project uses the pnpm monorepo management method, please do not use npm or yarn to install dependencies.
**启动项目**
**Start Project**
```bash
pnpm dev
```
**构建项目**
**Build Project**
```bash
pnpm build
```
**代码同步**
## Ecosystem
参考 [代码同步](https://docs.soybeanjs.cn/zh/guide/sync) 文档。
## 周边生态
- [react-soybean-admin](https://github.com/mufeng889/react-soybean-admin): 基于SoybeanAdmin的React版本.
- [electron-mock-admin](https://github.com/lixin59/electron-mock-api): 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的 mock。
- [T-Shell](https://github.com/TheBlindM/T-Shell): 是一个可配置命令提示的终端模拟器和 SSH 客户端。
- [pea](https://github.com/haitang1894/pea) : 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 开发的简单权限系统。
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): 基于 Vue3/TypeScript/NaiveUI 和 NET7 & Sqlsugar 开发的后台管理框架。采用最原生最简洁的方式来实现, 前端清新优雅高颜值,后端 结构清晰,优雅易懂,功能强大。
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): 采用SpringBoot3、SaToken、MySQL等框架开发二次修改 [soybean-admin](https://github.com/soybeanjs/soybean-admin),适配动态菜单/按钮级别的鉴权,保留原汁原味、清新优雅、高颜值的后台管理系统脚手架。
- [snail-job](https://github.com/aizuda/snail-job): 一款兼具 “高性能、高颜值、高活跃” 的分布式任务重试和分布式任务调度平台。
- [SuperApi](https://github.com/TmmTop/SuperApi): 快速将你的 idea 变成线上稳定运行的产品! 无实体建库建表,对无实体库表进行增删改查,支持 15 种条件查询,以及分页,列表,无限级树形列表 等功能的 API 部署! 拥有接口文档Auth 授权,接口限流,获取客户端真实 IP先进的服务器缓存组件动态 API 等功能,期待您的体验!
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): 基于 FastAPI+Vue3+Naive UI 的现代化轻量管理平台.
- [electron-mock-admin](https://github.com/lixin59/electron-mock-api): A Mock Api management system that helps front-end developers quickly implement interface mocks.
- [T-Shell](https://github.com/TheBlindM/T-Shell): A terminal emulator and SSH client with configurable command prompts.
- [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin).
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): A backend management framework developed based on Vue3/TypeScript/NaiveUI and NET7 & Sqlsugar. It is implemented in the most original and simplest way, with a fresh and elegant front-end, a clear and elegant backend structure, and powerful functions.
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): Adopting SpringBoot 3, SaToken, MySQL and other frameworks to develop and modify [soybean-admin](https://github.com/soybeanjs/soybean-admin) for the second time, adapting dynamic menu/button-level authorization. Retaining the original flavor, fresh and elegant, high-value back-end management system scaffold.
- [snail-job](https://github.com/aizuda/snail-job): A distributed task retry and task scheduling platform with "high performance, high value and high activity".
- [SuperApi](https://github.com/TmmTop/SuperApi): Quickly turn your idea into an online stable product! Entity-less library and table building, add, delete, change and check entity-less library table, support 15 kinds of condition query, as well as paging, list, unlimited tree list and other functions of the API deployment! With interface documentation, Auth authorisation, interface flow restriction, access to the client's real IP, advanced server caching components, dynamic APIs and other features, we look forward to your experience!
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): A modern Management Platform based on FastAPI+Vue3+Naive UI.
## 如何贡献
## How to Contribute
我们热烈欢迎并感谢所有形式的贡献。如果您有任何想法或建议,欢迎通过提交 [pull requests](https://github.com/soybeanjs/soybean-admin/pulls) 或创建 GitHub [issue](https://github.com/soybeanjs/soybean-admin/issues/new) 来分享。
We warmly welcome and appreciate all forms of contributions. If you have any ideas or suggestions, please feel free to share them by submitting [pull requests](https://github.com/soybeanjs/soybean-admin/pulls) or creating GitHub [issue](https://github.com/soybeanjs/soybean-admin/issues/new).
## Git 提交规范
## Git Commit Guidelines
本项目已内置 `commit` 命令,您可以通过执行 `pnpm commit` 来生成符合 [Conventional Commits]([conventionalcommits](https://www.conventionalcommits.org/)) 规范的提交信息。在提交PR时请务必使用 `commit` 命令来创建提交信息,以确保信息的规范性。
This project has built-in `commit` command, you can execute `pnpm commit` to generate commit information that conforms to [Conventional Commits](https://www.conventionalcommits.org/) specification. When submitting PR, please be sure to use `commit` command to create commit information to ensure the standardization of information.
## Browser Support
## 浏览器支持
推荐使用最新版的 Chrome 浏览器进行开发,以获得更好的体验。
It is recommended to use the latest version of Chrome in development for a better experience.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) |
| --- | --- | --- | --- | --- |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 开源作者
## OpenSource Author
[Soybean](https://github.com/honghuangdc)
## Contributors
## 贡献者
感谢以下贡献者的贡献。如果您想为本项目做出贡献,请参考 [如何贡献](#如何贡献)。
Thanks the following people for their contributions. If you want to contribute to this project, please refer to [How to Contribute](#how-to-contribute).
<a href="https://github.com/soybeanjs/soybean-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a>
## 交流
## Communication
`SoybeanAdmin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和 QQ 交流群,使用问题欢迎在群内提问。
`SoybeanAdmin` is a completely open source and free project, helping developers to develop medium and large-scale management systems more conveniently. It also provides WeChat and QQ communication groups. If you have any questions, please feel free to ask in the group.
<div>
<p>QQ交流群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-3.jpg" style="width:200px" />
<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>
<p>微信群</p>
<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>
<p>添加下面微信邀请进微信群</p>
<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" />
</div>
<div>
<p>Add Soybean's WeChat for business consultation, cooperation, project architecture, one-on-one guidance, etc.</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybean.jpg" style="width:200px" />
</div>
## Star 趋势
## Star Trend
[![Star History Chart](https://api.star-history.com/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.com/#soybeanjs/soybean-admin&Date)
## 开源协议
## License
项目基于 [MIT © 2021 Soybean](./LICENSE) 协议,仅供学习参考,商业使用请保留作者版权信息,作者不保证也不承担任何软件的使用风险。
This project is based on the [MIT © 2021 Soybean](./LICENSE) protocol, for learning purposes only, please retain the author's copyright information for commercial use, the author does not guarantee and is not responsible for the software.

179
README.zh_CN.md Normal file
View File

@@ -0,0 +1,179 @@
<div align="center">
<img src="./public/favicon.svg" width="160" />
<h1>SoybeanAdmin</h1>
<span><a href="./README.md">English</a> | 中文</span>
</div>
---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
[![github stars](https://img.shields.io/github/stars/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> [!NOTE]
> 如果您觉得 `SoybeanAdmin`对您有所帮助,或者您喜欢我们的项目,请在 GitHub 上给我们一个 ⭐️。您的支持是我们持续改进和增加新功能的动力!感谢您的支持!
## 简介
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。
## 特性
- **前沿技术应用**:采用 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。
- **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。
- **严格的代码规范**:遵循 [SoybeanJS 规范](https://docs.soybeanjs.cn/zh/standard)集成了eslint, prettier 和 simple-git-hooks保证代码的规范性。
- **TypeScript** 支持严格的类型检查,提高代码的可维护性。
- **丰富的主题配置**:内置多样的主题配置,与 UnoCSS 完美结合。
- **内置国际化方案**:轻松实现多语言支持。
- **自动化文件路由系统**:自动生成路由导入、声明和类型。更多细节请查看 [Elegant Router](https://github.com/soybeanjs/elegant-router)。
- **灵活的权限路由**:同时支持前端静态路由和后端动态路由。
- **丰富的页面组件**内置多样页面和组件包括403、404、500页面以及布局组件、标签组件、主题配置组件等。
- **命令行工具**内置高效的命令行工具git提交、删除文件、发布等。
- **移动端适配**:完美支持移动端,实现自适应布局。
## 版本
- **NaiveUI 版本:**
- [预览地址](https://naive.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin)
- **AntDesignVue 版本:**
- [预览地址](https://antd.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-antd)
- **旧版:**
- [预览地址](https://legacy.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin/tree/legacy)
## 文档
- [地址](https://docs.soybeanjs.cn)
- [旧版文档](https://legacy-docs.soybeanjs.cn)
## 示例图片
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-01.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-02.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-03.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-04.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-05.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-06.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-07.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-08.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-09.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-10.png)
![](https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean-admin-v1-mobile.png)
## 使用
**环境准备**
确保你的环境满足以下要求:
- **git**: 你需要git来克隆和管理项目版本。
- **NodeJS**: >=18.12.0,推荐 18.19.0 或更高。
- **pnpm**: >= 8.7.0,推荐 8.14.0 或更高。
**克隆项目**
```bash
git clone https://github.com/soybeanjs/soybean-admin.git
```
**安装依赖**
```bash
pnpm i
```
> 由于本项目采用了 pnpm monorepo 的管理方式,因此请不要使用 npm 或 yarn 来安装依赖。
**启动项目**
```bash
pnpm dev
```
**构建项目**
```bash
pnpm build
```
## 周边生态
- [electron-mock-admin](https://github.com/lixin59/electron-mock-api): 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的 mock。
- [T-Shell](https://github.com/TheBlindM/T-Shell): 是一个可配置命令提示的终端模拟器和 SSH 客户端。
- [pea](https://github.com/haitang1894/pea) : 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 开发的简单权限系统。
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): 基于 Vue3/TypeScript/NaiveUI 和 NET7 & Sqlsugar 开发的后台管理框架。采用最原生最简洁的方式来实现, 前端清新优雅高颜值,后端 结构清晰,优雅易懂,功能强大。
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): 采用SpringBoot3、SaToken、MySQL等框架开发二次修改 [soybean-admin](https://github.com/soybeanjs/soybean-admin),适配动态菜单/按钮级别的鉴权,保留原汁原味、清新优雅、高颜值的后台管理系统脚手架。
- [snail-job](https://github.com/aizuda/snail-job): 一款兼具 “高性能、高颜值、高活跃” 的分布式任务重试和分布式任务调度平台。
- [SuperApi](https://github.com/TmmTop/SuperApi): 快速将你的 idea 变成线上稳定运行的产品! 无实体建库建表,对无实体库表进行增删改查,支持 15 种条件查询,以及分页,列表,无限级树形列表 等功能的 API 部署! 拥有接口文档Auth 授权,接口限流,获取客户端真实 IP先进的服务器缓存组件动态 API 等功能,期待您的体验!
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): 基于 FastAPI+Vue3+Naive UI 的现代化轻量管理平台.
## 如何贡献
我们热烈欢迎并感谢所有形式的贡献。如果您有任何想法或建议,欢迎通过提交 [pull requests](https://github.com/soybeanjs/soybean-admin/pulls) 或创建 GitHub [issue](https://github.com/soybeanjs/soybean-admin/issues/new) 来分享。
## Git 提交规范
本项目已内置 `commit` 命令,您可以通过执行 `pnpm commit` 来生成符合 [Conventional Commits]([conventionalcommits](https://www.conventionalcommits.org/)) 规范的提交信息。在提交PR时请务必使用 `commit` 命令来创建提交信息,以确保信息的规范性。
## 浏览器支持
推荐使用最新版的 Chrome 浏览器进行开发,以获得更好的体验。
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) |
| --- | --- | --- | --- | --- |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 开源作者
[Soybean](https://github.com/honghuangdc)
## 贡献者
感谢以下贡献者的贡献。如果您想为本项目做出贡献,请参考 [如何贡献](#如何贡献)。
<a href="https://github.com/soybeanjs/soybean-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a>
## 交流
`SoybeanAdmin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和 QQ 交流群,使用问题欢迎在群内提问。
<div>
<p>QQ交流群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" />
</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>
<p>添加下面微信邀请进微信群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" />
</div>
<div>
<p>添加 Soybean 的微信,业务咨询、合作、项目架构、一对一指导等</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybean.jpg" style="width:200px" />
</div>
## Star 趋势
[![Star History Chart](https://api.star-history.com/svg?repos=soybeanjs/soybean-admin&type=Date)](https://star-history.com/#soybeanjs/soybean-admin&Date)
## 开源协议
项目基于 [MIT © 2021 Soybean](./LICENSE) 协议,仅供学习参考,商业使用请保留作者版权信息,作者不保证也不承担任何软件的使用风险。

View File

@@ -1,53 +1,34 @@
import type { HttpProxy, ProxyOptions } from 'vite';
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
import { consola } from 'consola';
import type { ProxyOptions } from 'vite';
import { createServiceConfig } from '../../src/utils/service';
/**
* Set http proxy
*
* @param env - The current env
* @param enable - If enable http proxy
* @param isDev - Is development environment
*/
export function createViteProxy(env: Env.ImportMeta, enable: boolean) {
const isEnableHttpProxy = enable && env.VITE_HTTP_PROXY === 'Y';
export function createViteProxy(env: Env.ImportMeta, isDev: boolean) {
const isEnableHttpProxy = isDev && env.VITE_HTTP_PROXY === 'Y';
if (!isEnableHttpProxy) return undefined;
const isEnableProxyLog = env.VITE_PROXY_LOG === 'Y';
const { baseURL, proxyPattern, other } = createServiceConfig(env);
const proxy: Record<string, ProxyOptions> = createProxyItem({ baseURL, proxyPattern }, isEnableProxyLog);
const proxy: Record<string, ProxyOptions> = createProxyItem({ baseURL, proxyPattern });
other.forEach(item => {
Object.assign(proxy, createProxyItem(item, isEnableProxyLog));
Object.assign(proxy, createProxyItem(item));
});
return proxy;
}
function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean) {
function createProxyItem(item: App.Service.ServiceConfigItem) {
const proxy: Record<string, ProxyOptions> = {};
proxy[item.proxyPattern] = {
target: item.baseURL,
changeOrigin: true,
configure: (_proxy: HttpProxy.Server, options: ProxyOptions) => {
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
if (!enableLog) return;
const requestUrl = `${lightBlue('[proxy url]')}: ${bgYellow(` ${req.method} `)} ${green(`${item.proxyPattern}${req.url}`)}`;
const proxyUrl = `${lightBlue('[real request url]')}: ${green(`${options.target}${req.url}`)}`;
consola.log(`${requestUrl}\n${proxyUrl}`);
});
_proxy.on('error', (_err, req, _res) => {
if (!enableLog) return;
consola.log(bgRed(`Error: ${req.method} `), green(`${options.target}${req.url}`));
});
},
rewrite: path => path.replace(new RegExp(`^${item.proxyPattern}`), '')
};

View File

@@ -10,7 +10,11 @@ import { setupHtmlPlugin } from './html';
export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
const plugins: PluginOption = [
vue(),
vue({
script: {
defineModel: true
}
}),
vueJsx(),
VueDevtools(),
setupElegantRouter(),

View File

@@ -8,6 +8,20 @@ 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,12 +1,12 @@
import process from 'node:process';
import path from 'node:path';
import type { PluginOption } from 'vite';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver';
import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import { AntDesignVueResolver, NaiveUiResolver } from 'unplugin-vue-components/resolvers';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
export function setupUnplugin(viteEnv: Env.ImportMeta) {
const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
@@ -31,6 +31,9 @@ export function setupUnplugin(viteEnv: Env.ImportMeta) {
dts: 'src/typings/components.d.ts',
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
resolvers: [
AntDesignVueResolver({
importStyle: false
}),
NaiveUiResolver(),
IconsResolver({ customCollections: [collectionName], componentPrefix: VITE_ICON_PREFIX })
]

View File

@@ -1,7 +1,7 @@
{
"name": "soybean-admin",
"type": "module",
"version": "1.3.12",
"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,7 +35,7 @@
"build:test": "vite build --mode test",
"cleanup": "sa cleanup",
"commit": "sa git-commit",
"commit:zh": "sa git-commit -l=zh-cn",
"czh": "sa git-commit -l=zh-cn",
"dev": "vite --mode test",
"dev:prod": "vite --mode prod",
"gen-route": "sa gen-route",
@@ -47,61 +47,74 @@
"update-pkg": "sa update-pkg"
},
"dependencies": {
"@antv/data-set": "0.11.8",
"@antv/g2": "5.2.1",
"@better-scroll/core": "2.5.1",
"@iconify/vue": "4.3.0",
"@iconify/vue": "4.1.2",
"@sa/axios": "workspace:*",
"@sa/color": "workspace:*",
"@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*",
"@sa/utils": "workspace:*",
"@vueuse/core": "13.0.0",
"@vueuse/core": "10.11.0",
"clipboard": "2.0.11",
"dayjs": "1.11.13",
"defu": "6.1.4",
"echarts": "5.6.0",
"json5": "2.2.3",
"naive-ui": "2.41.0",
"dayjs": "1.11.12",
"dhtmlx-gantt": "8.0.9",
"dompurify": "3.1.6",
"echarts": "5.5.1",
"jsbarcode": "3.11.6",
"naive-ui": "2.39.0",
"nprogress": "0.2.0",
"pinia": "3.0.1",
"tailwind-merge": "3.0.2",
"vue": "3.5.13",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.2",
"vue-router": "4.5.0"
"pinia": "2.2.0",
"pinyin-pro": "3.23.1",
"print-js": "1.6.0",
"swiper": "11.1.5",
"tailwind-merge": "2.4.0",
"typeit": "8.8.4",
"vditor": "3.10.4",
"vue": "3.4.35",
"vue-draggable-plus": "0.5.2",
"vue-i18n": "9.13.1",
"vue-pdf-embed": "2.1.0",
"vue-router": "4.4.1",
"wangeditor": "4.7.15",
"xgplayer": "3.0.19",
"xlsx": "0.18.5"
},
"devDependencies": {
"@amap/amap-jsapi-types": "0.0.15",
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.316",
"@iconify/json": "2.2.232",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.6.0",
"@types/node": "22.13.10",
"@soybeanjs/eslint-config": "1.4.0",
"@types/bmapgl": "0.0.7",
"@types/dompurify": "3.0.5",
"@types/node": "22.0.1",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.0.0",
"@unocss/preset-icons": "66.0.0",
"@unocss/preset-uno": "66.0.0",
"@unocss/transformer-directives": "66.0.0",
"@unocss/transformer-variant-group": "66.0.0",
"@unocss/vite": "66.0.0",
"@vitejs/plugin-vue": "5.2.1",
"@vitejs/plugin-vue-jsx": "4.1.1",
"consola": "3.4.0",
"eslint": "9.22.0",
"eslint-plugin-vue": "10.0.0",
"kolorist": "1.8.0",
"lint-staged": "15.4.3",
"sass": "1.85.1",
"@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.8.0",
"eslint-plugin-vue": "9.27.0",
"lint-staged": "15.2.7",
"sass": "1.77.8",
"simple-git-hooks": "2.11.1",
"tsx": "4.19.3",
"typescript": "5.8.2",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.4.1",
"vite": "6.2.1",
"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.7.2",
"vue-eslint-parser": "10.1.1",
"vue-tsc": "2.2.8"
"vite-plugin-vue-devtools": "7.3.7",
"vue-eslint-parser": "9.4.3",
"vue-tsc": "2.0.29"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",

View File

@@ -1,20 +0,0 @@
{
"name": "@sa/alova",
"version": "1.3.12",
"exports": {
".": "./src/index.ts",
"./fetch": "./src/fetch.ts",
"./client": "./src/client.ts",
"./mock": "./src/mock.ts"
},
"typesVersions": {
"*": {
"*": ["./src/*"]
}
},
"dependencies": {
"@alova/mock": "2.0.12",
"@sa/utils": "workspace:*",
"alova": "3.2.10"
}
}

View File

@@ -1 +0,0 @@
export * from 'alova/client';

View File

@@ -1,2 +0,0 @@
/** the backend error code key */
export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';

View File

@@ -1,2 +0,0 @@
import adapterFetch from 'alova/fetch';
export default adapterFetch;

View File

@@ -1,77 +0,0 @@
import { createAlova } from 'alova';
import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova';
import VueHook from 'alova/vue';
import type { VueHookType } from 'alova/vue';
import adapterFetch from 'alova/fetch';
import { createServerTokenAuthentication } from 'alova/client';
import type { FetchRequestInit } from 'alova/fetch';
import { BACKEND_ERROR_CODE } from './constant';
import type { CustomAlovaConfig, RequestOptions } from './type';
export const createAlovaRequest = <
RequestConfig = FetchRequestInit,
ResponseType = Response,
ResponseHeader = Headers,
L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter,
L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter
>(
customConfig: CustomAlovaConfig<
AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>
>,
options: RequestOptions<AlovaGenerics<any, any, RequestConfig, ResponseType, ResponseHeader, L1Cache, L2Cache, any>>
) => {
const { tokenRefresher } = options;
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
VueHookType,
AlovaRequestAdapter<RequestConfig, ResponseType, ResponseHeader>
>({
refreshTokenOnSuccess: {
isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
handler: async (response, method) => tokenRefresher?.handler(response, method)
},
refreshTokenOnError: {
isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false,
handler: async (response, method) => tokenRefresher?.handler(response, method)
}
});
const instance = createAlova({
...customConfig,
timeout: customConfig.timeout ?? 10 * 1000,
requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(),
statesHook: VueHook,
beforeRequest: onAuthRequired(options.onRequest as any),
responded: onResponseRefreshToken({
onSuccess: async (response, method) => {
// check if http status is success
let error: any = null;
let transformedData: any = null;
try {
if (await options.isBackendSuccess(response)) {
transformedData = await options.transformBackendResponse(response);
} else {
error = new Error('the backend request error');
error.code = BACKEND_ERROR_CODE;
}
} catch (err) {
error = err;
}
if (error) {
await options.onError?.(error, response, method);
throw error;
}
return transformedData;
},
onComplete: options.onComplete,
onError: (error, method) => options.onError?.(error, null, method)
})
});
return instance;
};
export { BACKEND_ERROR_CODE };
export type * from './type';
export type * from 'alova';

View File

@@ -1 +0,0 @@
export * from '@alova/mock';

View File

@@ -1,52 +0,0 @@
import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova';
export type CustomAlovaConfig<AG extends AlovaGenerics> = Omit<
AlovaOptions<AG>,
'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter'
> & {
/** request adapter. all request of alova will be sent by it. */
requestAdapter?: AlovaRequestAdapter<AG['RequestConfig'], AG['Response'], AG['ResponseHeader']>;
};
export interface RequestOptions<AG extends AlovaGenerics> {
/**
* The hook before request
*
* For example: You can add header token in this hook
*
* @param method alova Method Instance
*/
onRequest?: AlovaOptions<AG>['beforeRequest'];
/**
* The hook to check backend response is success or not
*
* @param response alova response
*/
isBackendSuccess: (response: AG['Response']) => Promise<boolean>;
/** The config to refresh token */
tokenRefresher?: {
/** detect the token is expired */
isExpired(response: AG['Response'], Method: Method<AG>): Promise<boolean> | boolean;
/** refresh token handler */
handler(response: AG['Response'], Method: Method<AG>): Promise<void>;
};
/** The hook after backend request complete */
onComplete?: ResponseCompleteHandler<AG>;
/**
* The hook to handle error
*
* For example: You can show error message in this hook
*
* @param error
*/
onError?: (error: any, response: AG['Response'] | null, methodInstance: Method<AG>) => any | Promise<any>;
/**
* transform backend response when the responseType is json
*
* @param response alova response
*/
transformBackendResponse: (response: AG['Response']) => any;
}

View File

@@ -1,20 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"jsx": "preserve",
"lib": ["DOM", "ESNext"],
"baseUrl": ".",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"types": ["node"],
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/axios",
"version": "1.3.12",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -11,11 +11,11 @@
},
"dependencies": {
"@sa/utils": "workspace:*",
"axios": "1.8.3",
"axios-retry": "4.5.0",
"qs": "6.14.0"
"axios": "1.7.2",
"axios-retry": "4.4.2",
"qs": "6.12.3"
},
"devDependencies": {
"@types/qs": "6.9.18"
"@types/qs": "6.9.15"
}
}

View File

@@ -162,12 +162,12 @@ export function createFlatRequest<ResponseData = any, State = Record<string, unk
if (responseType === 'json') {
const data = opts.transformBackendResponse(response);
return { data, error: null, response };
return { data, error: null };
}
return { data: response.data as MappedType<R, T>, error: null };
} catch (error) {
return { data: null, error, response: (error as AxiosError<ResponseData>).response };
return { data: null, error };
}
} as FlatRequestInstance<State, ResponseData>;

View File

@@ -92,20 +92,18 @@ export interface RequestInstance<S = Record<string, unknown>> extends RequestIns
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
}
export type FlatResponseSuccessData<T = any, ResponseData = any> = {
export type FlatResponseSuccessData<T = any> = {
data: T;
error: null;
response: AxiosResponse<ResponseData>;
};
export type FlatResponseFailData<ResponseData = any> = {
data: null;
error: AxiosError<ResponseData>;
response: AxiosResponse<ResponseData>;
};
export type FlatResponseData<T = any, ResponseData = any> =
| FlatResponseSuccessData<T, ResponseData>
| FlatResponseSuccessData<T>
| FlatResponseFailData<ResponseData>;
export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> {

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { computed, reactive, ref } from 'vue';
import type { Ref, VNodeChild } from 'vue';
import type { Ref } from 'vue';
import { jsonClone } from '@sa/utils';
import useBoolean from './use-boolean';
import useLoading from './use-loading';
@@ -8,11 +8,9 @@ export type MaybePromise<T> = T | Promise<T>;
export type ApiFn = (args: any) => Promise<unknown>;
export type TableColumnCheckTitle = string | ((...args: any) => VNodeChild);
export type TableColumnCheck = {
key: string;
title: TableColumnCheckTitle;
title: string;
checked: boolean;
};

View File

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

View File

@@ -53,10 +53,23 @@ const bindProps = computed(() => {
function handleClose() {
emit('close');
}
function handleMouseup(e: MouseEvent) {
// close tab by mouse wheel button click
if (e.button === 1) {
handleClose();
}
}
</script>
<template>
<component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
<component
:is="activeTabComponent.component"
:class="activeTabComponent.class"
:style="cssVars"
v-bind="bindProps"
@mouseup="handleMouseup"
>
<template #prefix>
<slot name="prefix"></slot>
</template>

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/fetch",
"version": "1.3.12",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -10,6 +10,6 @@
}
},
"dependencies": {
"ofetch": "1.4.1"
"ofetch": "1.3.4"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/scripts",
"version": "1.3.12",
"version": "1.3.4",
"bin": {
"sa": "./bin.ts"
},
@@ -14,14 +14,14 @@
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.24",
"bumpp": "10.1.0",
"c12": "3.0.2",
"bumpp": "9.4.1",
"c12": "1.11.1",
"cac": "6.7.14",
"consola": "3.4.0",
"consola": "3.2.3",
"enquirer": "2.4.1",
"execa": "9.5.2",
"execa": "9.3.0",
"kolorist": "1.8.0",
"npm-check-updates": "17.1.15",
"npm-check-updates": "17.0.0",
"rimraf": "6.0.1"
}
}

View File

@@ -11,15 +11,13 @@ export const locales = {
},
gitCommitTypes: [
['feat', '新功能'],
['feat-wip', '开发中的功能,比如某功能的部分代码'],
['fix', '修复Bug'],
['docs', '只涉及文档更新'],
['typo', '代码或文档勘误,比如错误拼写'],
['docs', '只更新文档'],
['style', '修改代码风格,不影响代码含义的变更'],
['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'],
['perf', '可提高性能的代码更改'],
['optimize', '优化代码质量的代码更改'],
['test', '添加缺失的测试或更正现有测'],
['test', '添加缺失的测试或更正现有测'],
['build', '影响构建系统或外部依赖项的更改'],
['ci', '对 CI 配置文件和脚本的更改'],
['chore', '没有修改src或测试文件的其他变更'],
@@ -49,10 +47,8 @@ export const locales = {
},
gitCommitTypes: [
['feat', 'A new feature'],
['feat-wip', 'Features in development, such as partial code for a certain feature'],
['fix', 'A bug fix'],
['docs', 'Documentation only changes'],
['typo', 'Code or document corrections, such as spelling errors'],
['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'],

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/utils",
"version": "1.3.12",
"version": "1.3.4",
"exports": {
".": "./src/index.ts"
},
@@ -14,7 +14,7 @@
"crypto-js": "4.2.0",
"klona": "2.0.6",
"localforage": "1.10.0",
"nanoid": "5.1.3"
"nanoid": "5.0.7"
},
"devDependencies": {
"@types/crypto-js": "4.2.2"

7755
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ const naiveDateLocale = computed(() => {
const watermarkProps = computed<WatermarkProps>(() => {
return {
content: themeStore.watermark.text,
content: themeStore.watermark?.text || 'SoybeanAdmin',
cross: true,
fullscreen: true,
fontSize: 16,
@@ -50,7 +50,7 @@ const watermarkProps = computed<WatermarkProps>(() => {
>
<AppProvider>
<RouterView class="bg-layout" />
<NWatermark v-if="themeStore.watermark.visible" v-bind="watermarkProps" />
<NWatermark v-if="themeStore.watermark?.visible" v-bind="watermarkProps" />
</AppProvider>
</NConfigProvider>
</template>

View File

@@ -25,10 +25,7 @@ const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
<div v-for="item in columns" :key="item.key" class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)">
<icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
<NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
<template v-if="typeof item.title === 'function'">
<component :is="item.title" />
</template>
<template v-else>{{ item.title }}</template>
{{ item.title }}
</NCheckbox>
</div>
</VueDraggable>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales';
import { useRouterPush } from '@/hooks/common/router';
defineOptions({ name: 'ExceptionBase' });

View File

@@ -0,0 +1,78 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
defineOptions({ name: 'CustomIconSelect' });
interface Props {
/** Selected icon */
value: string;
/** List of icons */
icons: string[];
/** Icon for when nothing is selected */
emptyIcon?: string;
}
const props = withDefaults(defineProps<Props>(), {
emptyIcon: 'mdi:apps'
});
interface Emits {
(e: 'update:value', val: string): void;
}
const emit = defineEmits<Emits>();
const modelValue = computed({
get() {
return props.value;
},
set(val: string) {
emit('update:value', val);
}
});
const selectedIcon = computed(() => modelValue.value || props.emptyIcon);
const searchValue = ref('');
const iconsList = computed(() => props.icons.filter(v => v.includes(searchValue.value)));
function handleChange(iconItem: string) {
modelValue.value = iconItem;
}
</script>
<template>
<NPopover placement="bottom-end" trigger="click">
<template #trigger>
<NInput v-model:value="modelValue" readonly placeholder="点击选择图标">
<template #suffix>
<SvgIcon :icon="selectedIcon" class="p-5px text-30px" />
</template>
</NInput>
</template>
<template #header>
<NInput v-model:value="searchValue" placeholder="搜索图标"></NInput>
</template>
<div v-if="iconsList.length > 0" class="grid grid-cols-9 h-auto overflow-auto">
<span v-for="iconItem in iconsList" :key="iconItem" @click="handleChange(iconItem)">
<SvgIcon
:icon="iconItem"
class="m-2px cursor-pointer border-1px border-#d9d9d9 p-5px text-30px"
:class="{ 'border-primary': modelValue === iconItem }"
/>
</span>
</div>
<NEmpty v-else class="w-306px" description="你什么也找不到" />
</NPopover>
</template>
<style lang="scss" scoped>
:deep(.n-input-wrapper) {
padding-right: 0;
}
:deep(.n-input__suffix) {
border: 1px solid #d9d9d9;
}
</style>

View File

@@ -0,0 +1,18 @@
<script setup lang="ts">
import WebSiteLink from './web-site-link.vue';
defineOptions({ name: 'GithubLink' });
interface Props {
/** github link */
link: string;
}
defineProps<Props>();
</script>
<template>
<WebSiteLink label="github地址" :link="link" />
</template>
<style scoped></style>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
defineOptions({ name: 'WebSiteLink' });
interface Props {
/** Web site name */
label: string;
/** Web site link */
link: string;
}
defineProps<Props>();
</script>
<template>
<p>
<span>{{ label }}</span>
<a class="text-blue-500" :href="link" target="#">
{{ link }}
</a>
</p>
</template>
<style scoped></style>

View File

@@ -54,12 +54,3 @@ export const themePageAnimationModeRecord: Record<UnionKey.ThemePageAnimateMode,
};
export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord);
export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I18n.I18nKey> = {
close: 'theme.resetCacheStrategy.close',
refresh: 'theme.resetCacheStrategy.refresh'
};
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
export const DARK_CLASS = 'dark';

29
src/constants/business.ts Normal file
View File

@@ -0,0 +1,29 @@
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);

8
src/constants/map-sdk.ts Normal file
View File

@@ -0,0 +1,8 @@
/** baidu map sdk url */
export const BAIDU_MAP_SDK_URL = `https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1`;
/** Amap sdk url */
export const AMAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
/** tencent sdk url */
export const TENCENT_MAP_SDK_URL = 'https://map.qq.com/api/gljs?v=1.exp&key=A6DBZ-KXPLW-JKSRY-ONZF4-CPHY3-K6BL7';

View File

@@ -1,7 +1,7 @@
import { computed } from 'vue';
import { useCountDown, useLoading } from '@sa/hooks';
import { REG_PHONE } from '@/constants/reg';
import { $t } from '@/locales';
import { REG_PHONE } from '@/constants/reg';
export function useCaptcha() {
const { loading, startLoading, endLoading } = useLoading();

View File

@@ -1,5 +1,4 @@
import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
import { useElementSize } from '@vueuse/core';
import * as echarts from 'echarts/core';
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
import type {
@@ -30,6 +29,7 @@ import type {
} from 'echarts/components';
import { LabelLayout, UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { useElementSize } from '@vueuse/core';
import { useThemeStore } from '@/store/modules/theme';
export type ECOption = echarts.ComposeOption<

View File

@@ -18,7 +18,12 @@ export function useRouterPush(inSetup = true) {
const routerBack = router.back;
async function routerPushByKey(key: RouteKey, options?: App.Global.RouterPushOptions) {
interface RouterPushOptions {
query?: Record<string, string>;
params?: Record<string, string>;
}
async function routerPushByKey(key: RouteKey, options?: RouterPushOptions) {
const { query, params } = options || {};
const routeLocation: RouteLocationRaw = {
@@ -62,7 +67,7 @@ export function useRouterPush(inSetup = true) {
async function toLogin(loginModule?: UnionKey.LoginModule, redirectUrl?: string) {
const module = loginModule || 'pwd-login';
const options: App.Global.RouterPushOptions = {
const options: RouterPushOptions = {
params: {
module
}
@@ -88,18 +93,14 @@ export function useRouterPush(inSetup = true) {
return routerPushByKey('login', { query, params: { module } });
}
/**
* Redirect from login
*
* @param [needRedirect=true] Whether to redirect after login. Default is `true`
*/
async function redirectFromLogin(needRedirect = true) {
/** Redirect from login */
async function redirectFromLogin() {
const redirect = route.value.query?.redirect as string;
if (needRedirect && redirect) {
await routerPush(redirect);
if (redirect) {
routerPush(redirect);
} else {
await toHome();
toHome();
}
}

View File

@@ -64,7 +64,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
if (isTableColumnHasKey(column)) {
checks.push({
key: column.key as string,
title: column.title!,
title: column.title as string,
checked: true
});
} else if (column.type === 'selection') {
@@ -119,7 +119,6 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
page: 1,
pageSize: 10,
showSizePicker: true,
itemCount: 0,
pageSizes: [10, 15, 20, 25, 30],
onUpdatePage: async (page: number) => {
pagination.page = page;

View File

@@ -8,7 +8,6 @@ export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } =
function useMixMenu() {
const route = useRoute();
const routeStore = useRouteStore();
const { selectedKey } = useMenu();
const activeFirstLevelMenuKey = ref('');
@@ -17,7 +16,12 @@ function useMixMenu() {
}
function getActiveFirstLevelMenuKey() {
const [firstLevelRouteName] = selectedKey.value.split('_');
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
const [firstLevelRouteName] = routeName.split('_');
setActiveFirstLevelMenuKey(firstLevelRouteName);
}
@@ -64,20 +68,3 @@ function useMixMenu() {
getActiveFirstLevelMenuKey
};
}
export function useMenu() {
const route = useRoute();
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
return {
selectedKey
};
}

View File

@@ -42,7 +42,7 @@ function resetScroll() {
@after-leave="resetScroll"
@after-enter="appStore.setContentXScrollable(false)"
>
<KeepAlive :include="routeStore.cacheRoutes" :exclude="routeStore.excludeCacheRoutes">
<KeepAlive :include="routeStore.cacheRoutes">
<component
:is="Component"
v-if="appStore.reloadFlag"

View File

@@ -18,7 +18,7 @@ function loginOrRegister() {
toLogin();
}
type DropdownKey = 'logout';
type DropdownKey = 'user-center' | 'logout';
type DropdownOption =
| {
@@ -33,6 +33,15 @@ 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',

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { useFullscreen } from '@vueuse/core';
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
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';
@@ -40,12 +40,7 @@ const { isFullscreen, toggle } = useFullscreen();
<div class="h-full flex-y-center justify-end">
<GlobalSearch />
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
<LangSwitch
v-if="themeStore.header.multilingual.visible"
:lang="appStore.locale"
:lang-options="appStore.localeOptions"
@change-lang="appStore.changeLocale"
/>
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
<ThemeSchemaSwitch
:theme-schema="themeStore.themeScheme"
:is-dark="themeStore.darkMode"

View File

@@ -1,16 +1,26 @@
<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';
import { useMenu } from '../../../context';
defineOptions({
name: 'HorizontalMenu'
});
const route = useRoute();
const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { selectedKey } = useMenu();
const selectedKey = computed(() => {
const { hideInMenu, activeMenu } = route.meta;
const name = route.name as string;
const routeName = (hideInMenu ? activeMenu : name) || name;
return routeName;
});
</script>
<template>

View File

@@ -1,20 +1,31 @@
<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 { useMenu, useMixMenuContext } from '../../../context';
import { useMixMenuContext } from '../../../context';
defineOptions({
name: 'HorizontalMixMenu'
});
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
const { selectedKey } = useMenu();
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);

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import type { RouteKey } from '@elegant-router/types';
import { SimpleScrollbar } from '@sa/materials';
@@ -8,7 +8,7 @@ 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 { useMenu, useMixMenuContext } from '../../../context';
import { useMixMenuContext } from '../../../context';
defineOptions({
name: 'ReversedHorizontalMixMenu'
@@ -18,7 +18,6 @@ const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const {
firstLevelMenus,
childLevelMenus,
@@ -26,7 +25,16 @@ const {
setActiveFirstLevelMenuKey,
isActiveFirstLevelMenuHasChildren
} = useMixMenuContext();
const { selectedKey } = useMenu();
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);

View File

@@ -2,12 +2,11 @@
import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { SimpleScrollbar } from '@sa/materials';
import { 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 { useMenu } from '../../../context';
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
defineOptions({
name: 'VerticalMenu'
@@ -18,10 +17,18 @@ const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { selectedKey } = useMenu();
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() {

View File

@@ -3,18 +3,18 @@ import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { SimpleScrollbar } from '@sa/materials';
import { useBoolean } from '@sa/hooks';
import { 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 { $t } from '@/locales';
import { useMenu, useMixMenuContext } from '../../../context';
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: 'VerticalMixMenu'
name: 'VerticalMenuMix'
});
const route = useRoute();
@@ -31,7 +31,6 @@ const {
getActiveFirstLevelMenuKey
//
} = useMixMenuContext();
const { selectedKey } = useMenu();
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
@@ -57,6 +56,15 @@ function handleResetActiveMenu() {
}
}
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() {
@@ -114,6 +122,9 @@ watch(
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"

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { computed } from 'vue';
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import GlobalLogo from '../global-logo/index.vue';
defineOptions({

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue';
import type { VNode } from 'vue';
import { $t } from '@/locales';
import { useTabStore } from '@/store/modules/tab';
import { useSvgIcon } from '@/hooks/common/icon';
import { $t } from '@/locales';
defineOptions({
name: 'ContextMenu'

View File

@@ -3,12 +3,11 @@ import { nextTick, reactive, ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useElementBounding } from '@vueuse/core';
import { PageTab } from '@sa/materials';
import BetterScroll from '@/components/custom/better-scroll.vue';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route';
import { useTabStore } from '@/store/modules/tab';
import { isPC } from '@/utils/agent';
import BetterScroll from '@/components/custom/better-scroll.vue';
import ContextMenu from './context-menu.vue';
defineOptions({
@@ -25,7 +24,6 @@ const bsWrapper = ref<HTMLElement>();
const { width: bsWrapperWidth, left: bsWrapperLeft } = useElementBounding(bsWrapper);
const bsScroll = ref<InstanceType<typeof BetterScroll>>();
const tabRef = ref<HTMLElement>();
const isPCFlag = isPC();
const TAB_DATA_ID = 'data-tab-id';
@@ -84,10 +82,7 @@ function getContextMenuDisabledKeys(tabId: string) {
async function handleCloseTab(tab: App.Global.Tab) {
await tabStore.removeTab(tab.id);
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(tab.routeKey);
}
await routeStore.reCacheRoutesByKey(tab.routeKey);
}
async function refresh() {
@@ -114,7 +109,7 @@ function setDropdown(config: Partial<DropdownConfig>) {
let isClickContextMenu = false;
function handleDropdownVisible(visible: boolean | undefined) {
function handleDropdownVisible(visible: boolean) {
if (!isClickContextMenu) {
setDropdown({ visible });
}
@@ -171,7 +166,11 @@ init();
<template>
<DarkModeContainer class="size-full flex-y-center px-16px shadow-tab">
<div ref="bsWrapper" class="h-full flex-1-hidden">
<BetterScroll ref="bsScroll" :options="{ scrollX: true, scrollY: false, click: !isPCFlag }" @click="removeFocus">
<BetterScroll
ref="bsScroll"
:options="{ scrollX: true, scrollY: false, click: appStore.isMobile }"
@click="removeFocus"
>
<div
ref="tabRef"
class="h-full flex pr-18px"
@@ -186,7 +185,7 @@ init();
:active="tab.id === tabStore.activeTabId"
:active-color="themeStore.themeColor"
:closable="!tabStore.isTabRetain(tab.id)"
@pointerdown="tabStore.switchRouteByTab(tab)"
@click="tabStore.switchRouteByTab(tab)"
@close="handleCloseTab(tab)"
@contextmenu="handleContextMenu($event, tab.id)"
>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import Clipboard from 'clipboard';
import { useThemeStore } from '@/store/modules/theme';
import { $t } from '@/locales';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({
name: 'ConfigOperation'

View File

@@ -1,14 +1,9 @@
<script setup lang="ts">
import { computed } from 'vue';
import {
resetCacheStrategyOptions,
themePageAnimationModeOptions,
themeScrollModeOptions,
themeTabModeOptions
} from '@/constants/app';
import { useThemeStore } from '@/store/modules/theme';
import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
import { useThemeStore } from '@/store/modules/theme';
import { themePageAnimationModeOptions, themeScrollModeOptions, themeTabModeOptions } from '@/constants/app';
import { translateOptions } from '@/utils/common';
import SettingItem from '../components/setting-item.vue';
defineOptions({
@@ -27,14 +22,6 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
<template>
<NDivider>{{ $t('theme.pageFunTitle') }}</NDivider>
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
<SettingItem key="0" :label="$t('theme.resetCacheStrategy.title')">
<NSelect
v-model:value="themeStore.resetCacheStrategy"
:options="translateOptions(resetCacheStrategyOptions)"
size="small"
class="w-120px"
/>
</SettingItem>
<SettingItem key="1" :label="$t('theme.scrollMode.title')">
<NSelect
v-model:value="themeStore.layout.scrollMode"
@@ -114,10 +101,10 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
>
<NSwitch v-model:value="themeStore.footer.right" />
</SettingItem>
<SettingItem key="8" :label="$t('theme.watermark.visible')">
<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')">
<SettingItem v-if="themeStore.watermark?.visible" key="8-1" :label="$t('theme.watermark.text')">
<NInput
v-model:value="themeStore.watermark.text"
autosize
@@ -127,9 +114,6 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
placeholder="SoybeanAdmin"
/>
</SettingItem>
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
</SettingItem>
</TransitionGroup>
</template>

View File

@@ -109,14 +109,11 @@ const local: App.I18n.Schema = {
breadcrumb: {
visible: 'Breadcrumb Visible',
showIcon: 'Breadcrumb Icon Visible'
},
multilingual: {
visible: 'Display multilingual button'
}
},
tab: {
visible: 'Tab Visible',
cache: 'Tag Bar Info Cache',
cache: 'Tab Cache',
height: 'Tab Height',
mode: {
title: 'Tab Mode',
@@ -144,11 +141,6 @@ const local: App.I18n.Schema = {
},
themeDrawerTitle: 'Theme Configuration',
pageFunTitle: 'Page Function',
resetCacheStrategy: {
title: 'Reset Cache Strategy',
close: 'Close Page',
refresh: 'Refresh Page'
},
configOperation: {
copyConfig: 'Copy Config',
copySuccessMsg: 'Copy Success, Please replace the variable "themeSettings" in "src/theme/settings.ts"',
@@ -162,7 +154,60 @@ const local: App.I18n.Schema = {
404: 'Page Not Found',
500: 'Server Error',
'iframe-page': 'Iframe',
home: 'Home'
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',
plugin: 'Plugin',
plugin_copy: 'Copy',
plugin_charts: 'Charts',
plugin_charts_echarts: 'ECharts',
plugin_editor: 'Editor',
plugin_editor_quill: 'Quill',
plugin_editor_markdown: 'Markdown',
plugin_icon: 'Icon',
plugin_map: 'Map',
plugin_print: 'Print',
plugin_swiper: 'Swiper',
plugin_video: 'Video',
plugin_barcode: 'Barcode',
plugin_pinyin: 'pinyin',
plugin_excel: 'Excel',
plugin_pdf: 'PDF preview',
plugin_gantt: 'Gantt Chart',
plugin_typeit: 'Typeit'
},
page: {
login: {
@@ -211,6 +256,19 @@ 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.',
@@ -239,6 +297,163 @@ 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

@@ -109,14 +109,11 @@ const local: App.I18n.Schema = {
breadcrumb: {
visible: '显示面包屑',
showIcon: '显示面包屑图标'
},
multilingual: {
visible: '显示多语言按钮'
}
},
tab: {
visible: '显示标签栏',
cache: '标签栏信息缓存',
cache: '缓存标签页',
height: '标签栏高度',
mode: {
title: '标签栏风格',
@@ -144,11 +141,6 @@ const local: App.I18n.Schema = {
},
themeDrawerTitle: '主题配置',
pageFunTitle: '页面功能',
resetCacheStrategy: {
title: '重置缓存策略',
close: '关闭页面',
refresh: '刷新页面'
},
configOperation: {
copyConfig: '复制配置',
copySuccessMsg: '复制成功,请替换 src/theme/settings.ts 中的变量 themeSettings',
@@ -162,7 +154,60 @@ const local: App.I18n.Schema = {
404: '页面不存在',
500: '服务器错误',
'iframe-page': '外链页面',
home: '首页'
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',
plugin: '插件示例',
plugin_copy: '剪贴板',
plugin_charts: '图表',
plugin_charts_echarts: 'ECharts',
plugin_editor: '编辑器',
plugin_editor_quill: '富文本编辑器',
plugin_editor_markdown: 'MD 编辑器',
plugin_icon: '图标',
plugin_map: '地图',
plugin_print: '打印',
plugin_swiper: 'Swiper',
plugin_video: '视频',
plugin_barcode: '条形码',
plugin_pinyin: '拼音',
plugin_excel: 'Excel',
plugin_pdf: 'PDF 预览',
plugin_gantt: '甘特图',
plugin_typeit: '打字机'
},
page: {
login: {
@@ -211,6 +256,19 @@ 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分支的内容。',
@@ -239,6 +297,163 @@ 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

@@ -1,38 +1,23 @@
import { h } from 'vue';
import type { App } from 'vue';
import { NButton } from 'naive-ui';
import { $t } from '@/locales';
export function setupAppErrorHandle(app: App) {
app.config.errorHandler = (err, vm, info) => {
// eslint-disable-next-line no-console
console.error(err, vm, info);
};
}
import { $t } from '../locales';
export function setupAppVersionNotification() {
// Update check interval in milliseconds
const UPDATE_CHECK_INTERVAL = 3 * 60 * 1000;
const canAutoUpdateApp = import.meta.env.VITE_AUTOMATICALLY_DETECT_UPDATE === 'Y' && import.meta.env.PROD;
if (!canAutoUpdateApp) return;
let isShow = false;
let updateInterval: ReturnType<typeof setInterval> | undefined;
const checkForUpdates = async () => {
if (isShow) return;
document.addEventListener('visibilitychange', async () => {
const preConditions = [!isShow, document.visibilityState === 'visible', !import.meta.env.DEV];
if (!preConditions.every(Boolean)) return;
const buildTime = await getHtmlBuildTime();
// If build time hasn't changed, no update is needed
if (buildTime === BUILD_TIME) {
return;
}
isShow = true;
// Show update notification
const n = window.$notification?.create({
title: $t('system.updateTitle'),
content: $t('system.updateContent'),
@@ -43,7 +28,6 @@ export function setupAppVersionNotification() {
{
onClick() {
n?.destroy();
isShow = false;
}
},
() => $t('system.updateCancel')
@@ -64,34 +48,13 @@ export function setupAppVersionNotification() {
isShow = false;
}
});
};
const startUpdateInterval = () => {
if (updateInterval) {
clearInterval(updateInterval);
}
updateInterval = setInterval(checkForUpdates, UPDATE_CHECK_INTERVAL);
};
// If updates should be checked, set up the visibility change listener and start the update interval
if (!isShow && document.visibilityState === 'visible') {
// Check for updates when the document is visible
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
checkForUpdates();
startUpdateInterval();
}
});
// Start the update interval
startUpdateInterval();
}
});
}
async function getHtmlBuildTime() {
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
const baseURL = import.meta.env.VITE_BASE_URL;
const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
const res = await fetch(`${baseURL}index.html`);
const html = await res.text();

View File

@@ -1,3 +1,6 @@
import 'virtual:svg-icons-register';
import 'uno.css';
import '../styles/css/global.css';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';

View File

@@ -1,22 +1,16 @@
// @unocss-include
import { getRgb } from '@sa/color';
import { DARK_CLASS } from '@/constants/app';
import { localStg } from '@/utils/storage';
import { toggleHtmlClass } from '@/utils/common';
import systemLogo from '@/assets/svg-icon/logo.svg?raw';
import { $t } from '@/locales';
import { localStg } from '@/utils/storage';
import systemLogo from '@/assets/svg-icon/logo.svg?raw';
export function setupLoading() {
const themeColor = localStg.get('themeColor') || '#646cff';
const darkMode = localStg.get('darkMode') || false;
const { r, g, b } = getRgb(themeColor);
const primaryColor = `--primary-color: ${r} ${g} ${b}`;
if (darkMode) {
toggleHtmlClass(DARK_CLASS).add();
}
const loadingClasses = [
'left-0 top-0',
'left-0 bottom-0 animate-delay-500',
@@ -33,14 +27,14 @@ export function setupLoading() {
.join('\n');
const loading = `
<div class="fixed-center flex-col bg-layout" style="${primaryColor}">
<div class="fixed-center flex-col" style="${primaryColor}">
${logoWithClass}
<div class="w-56px h-56px my-36px">
<div class="relative h-full animate-spin">
${dot}
</div>
</div>
<h2 class="text-28px font-500 text-primary">${$t('system.title')}</h2>
<h2 class="text-28px font-500 text-#646464">${$t('system.title')}</h2>
</div>`;
const app = document.getElementById('app');

View File

@@ -20,5 +20,36 @@ 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"),
plugin_barcode: () => import("@/views/plugin/barcode/index.vue"),
plugin_charts_echarts: () => import("@/views/plugin/charts/echarts/index.vue"),
plugin_copy: () => import("@/views/plugin/copy/index.vue"),
plugin_editor_markdown: () => import("@/views/plugin/editor/markdown/index.vue"),
plugin_editor_quill: () => import("@/views/plugin/editor/quill/index.vue"),
plugin_excel: () => import("@/views/plugin/excel/index.vue"),
plugin_gantt: () => import("@/views/plugin/gantt/index.vue"),
plugin_icon: () => import("@/views/plugin/icon/index.vue"),
plugin_map: () => import("@/views/plugin/map/index.vue"),
plugin_pdf: () => import("@/views/plugin/pdf/index.vue"),
plugin_pinyin: () => import("@/views/plugin/pinyin/index.vue"),
plugin_print: () => import("@/views/plugin/print/index.vue"),
plugin_swiper: () => import("@/views/plugin/swiper/index.vue"),
plugin_typeit: () => import("@/views/plugin/typeit/index.vue"),
plugin_video: () => import("@/views/plugin/video/index.vue"),
"user-center": () => import("@/views/user-center/index.vue"),
};

View File

@@ -39,6 +39,135 @@ 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',
@@ -74,5 +203,327 @@ 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: 'plugin',
path: '/plugin',
component: 'layout.base',
meta: {
title: '插件示例',
i18nKey: 'route.plugin',
order: 7,
icon: 'clarity:plugin-line'
},
children: [
{
name: 'plugin_barcode',
path: '/plugin/barcode',
component: 'view.plugin_barcode',
meta: {
title: 'plugin_barcode',
i18nKey: 'route.plugin_barcode',
icon: 'ic:round-barcode'
}
},
{
name: 'plugin_charts',
path: '/plugin/charts',
meta: {
title: 'plugin_charts',
i18nKey: 'route.plugin_charts',
icon: 'mdi:chart-areaspline'
},
children: [
{
name: 'plugin_charts_echarts',
path: '/plugin/charts/echarts',
component: 'view.plugin_charts_echarts',
meta: {
title: 'plugin_charts_echarts',
i18nKey: 'route.plugin_charts_echarts',
icon: 'simple-icons:apacheecharts'
}
}
]
},
{
name: 'plugin_copy',
path: '/plugin/copy',
component: 'view.plugin_copy',
meta: {
title: 'plugin_copy',
i18nKey: 'route.plugin_copy',
icon: 'mdi:clipboard-outline'
}
},
{
name: 'plugin_editor',
path: '/plugin/editor',
meta: {
title: 'plugin_editor',
i18nKey: 'route.plugin_editor',
icon: 'icon-park-outline:editor'
},
children: [
{
name: 'plugin_editor_markdown',
path: '/plugin/editor/markdown',
component: 'view.plugin_editor_markdown',
meta: {
title: 'plugin_editor_markdown',
i18nKey: 'route.plugin_editor_markdown',
icon: 'ri:markdown-line'
}
},
{
name: 'plugin_editor_quill',
path: '/plugin/editor/quill',
component: 'view.plugin_editor_quill',
meta: {
title: 'plugin_editor_quill',
i18nKey: 'route.plugin_editor_quill',
icon: 'mdi:file-document-edit-outline'
}
}
]
},
{
name: 'plugin_excel',
path: '/plugin/excel',
component: 'view.plugin_excel',
meta: {
title: 'plugin_excel',
i18nKey: 'route.plugin_excel',
icon: 'ri:file-excel-2-line',
keepAlive: true
}
},
{
name: 'plugin_gantt',
path: '/plugin/gantt',
component: 'view.plugin_gantt',
meta: {
title: 'plugin_gantt',
i18nKey: 'route.plugin_gantt',
icon: 'ant-design:bar-chart-outlined'
}
},
{
name: 'plugin_icon',
path: '/plugin/icon',
component: 'view.plugin_icon',
meta: {
title: 'plugin_icon',
i18nKey: 'route.plugin_icon',
localIcon: 'custom-icon'
}
},
{
name: 'plugin_map',
path: '/plugin/map',
component: 'view.plugin_map',
meta: {
title: 'plugin_map',
i18nKey: 'route.plugin_map',
icon: 'mdi:map'
}
},
{
name: 'plugin_pdf',
path: '/plugin/pdf',
component: 'view.plugin_pdf',
meta: {
title: 'plugin_pdf',
i18nKey: 'route.plugin_pdf',
icon: 'uiw:file-pdf'
}
},
{
name: 'plugin_pinyin',
path: '/plugin/pinyin',
component: 'view.plugin_pinyin',
meta: {
title: 'plugin_pinyin',
i18nKey: 'route.plugin_pinyin',
icon: 'entypo-social:google-hangouts'
}
},
{
name: 'plugin_print',
path: '/plugin/print',
component: 'view.plugin_print',
meta: {
title: 'plugin_print',
i18nKey: 'route.plugin_print',
icon: 'mdi:printer'
}
},
{
name: 'plugin_swiper',
path: '/plugin/swiper',
component: 'view.plugin_swiper',
meta: {
title: 'plugin_swiper',
i18nKey: 'route.plugin_swiper',
icon: 'simple-icons:swiper'
}
},
{
name: 'plugin_typeit',
path: '/plugin/typeit',
component: 'view.plugin_typeit',
meta: {
title: 'plugin_typeit',
i18nKey: 'route.plugin_typeit',
icon: 'mdi:typewriter'
}
},
{
name: 'plugin_video',
path: '/plugin/video',
component: 'view.plugin_video',
meta: {
title: 'plugin_video',
i18nKey: 'route.plugin_video',
icon: 'mdi:video'
}
}
]
},
{
name: 'user-center',
path: '/user-center',
component: 'layout.base$view.user-center',
meta: {
title: 'user-center',
i18nKey: 'route.user-center',
hideInMenu: true
}
}
];

View File

@@ -163,12 +163,65 @@ 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)?"
"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",
"plugin": "/plugin",
"plugin_barcode": "/plugin/barcode",
"plugin_charts": "/plugin/charts",
"plugin_charts_echarts": "/plugin/charts/echarts",
"plugin_copy": "/plugin/copy",
"plugin_editor": "/plugin/editor",
"plugin_editor_markdown": "/plugin/editor/markdown",
"plugin_editor_quill": "/plugin/editor/quill",
"plugin_excel": "/plugin/excel",
"plugin_gantt": "/plugin/gantt",
"plugin_icon": "/plugin/icon",
"plugin_map": "/plugin/map",
"plugin_pdf": "/plugin/pdf",
"plugin_pinyin": "/plugin/pinyin",
"plugin_print": "/plugin/print",
"plugin_swiper": "/plugin/swiper",
"plugin_typeit": "/plugin/typeit",
"plugin_video": "/plugin/video",
"user-center": "/user-center"
};
/**

View File

@@ -6,10 +6,10 @@ 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';
import { getRouteName } from '@/router/elegant/transform';
/**
* create route guard
@@ -36,34 +36,54 @@ export function createRouteGuard(router: Router) {
const routeRoles = to.meta.roles || [];
const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;
// if it is login route when logged in, then switch to the root page
if (to.name === loginRoute && isLogin) {
next({ name: rootRoute });
return;
}
const routeSwitches: CommonType.StrategicPattern[] = [
// if it is login route when logged in, then switch to the root page
{
condition: isLogin && to.name === loginRoute,
callback: () => {
next({ name: rootRoute });
}
},
// if it is constant route, then it is allowed to access directly
{
condition: !needLogin,
callback: () => {
handleRouteSwitch(to, from, next);
}
},
// if the route need login but the user is not logged in, then switch to the login page
{
condition: !isLogin && needLogin,
callback: () => {
next({ name: loginRoute, query: { redirect: to.fullPath } });
}
},
// if the user is logged in and has authorization, then it is allowed to access
{
condition: isLogin && needLogin && hasAuth,
callback: () => {
handleRouteSwitch(to, from, next);
}
},
// if the user is logged in but does not have authorization, then switch to the 403 page
{
condition: isLogin && needLogin && !hasAuth,
callback: () => {
next({ name: noAuthorizationRoute });
}
}
];
// if the route does not need login, then it is allowed to access directly
if (!needLogin) {
handleRouteSwitch(to, from, next);
return;
}
routeSwitches.some(({ condition, callback }) => {
if (condition) {
callback();
}
// the route need login but the user is not logged in, then switch to the login page
if (!isLogin) {
next({ name: loginRoute, query: { redirect: to.fullPath } });
return;
}
// if the user is logged in but does not have authorization, then switch to the 403 page
if (!hasAuth) {
next({ name: noAuthorizationRoute });
return;
}
// switch route normally
handleRouteSwitch(to, from, next);
return condition;
});
});
}
@@ -73,6 +93,7 @@ export function createRouteGuard(router: Router) {
* @param to to route
*/
async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
const authStore = useAuthStore();
const routeStore = useRouteStore();
const notFoundRoute: RouteKey = 'not-found';
@@ -84,48 +105,8 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
// the route is captured by the "not-found" route because the constant route is not initialized
// after the constant route is initialized, redirect to the original route
const path = to.fullPath;
const location: RouteLocationRaw = {
path,
replace: true,
query: to.query,
hash: to.hash
};
return location;
}
const isLogin = Boolean(localStg.get('token'));
if (!isLogin) {
// if the user is not logged in and the route is a constant route but not the "not-found" route, then it is allowed to access.
if (to.meta.constant && !isNotFoundRoute) {
routeStore.onRouteSwitchWhenNotLoggedIn();
return null;
}
// if the user is not logged in, then switch to the login page
const loginRoute: RouteKey = 'login';
const query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
const location: RouteLocationRaw = {
name: loginRoute,
query
};
return location;
}
if (!routeStore.isInitAuthRoute) {
// initialize the auth route
await routeStore.initAuthRoute();
// the route is captured by the "not-found" route because the auth route is not initialized
// after the auth route is initialized, redirect to the original route
if (isNotFoundRoute) {
const rootRoute: RouteKey = 'root';
const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;
const path = to.fullPath;
const location: RouteLocationRaw = {
path,
@@ -138,21 +119,63 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
}
}
routeStore.onRouteSwitchWhenLoggedIn();
// the auth route is initialized
// it is not the "not-found" route, then it is allowed to access
if (!isNotFoundRoute) {
// if the route is the constant route but is not the "not-found" route, then it is allowed to access.
if (to.meta.constant && !isNotFoundRoute) {
return null;
}
// the auth route is initialized
// it is not the "not-found" route, then it is allowed to access
if (routeStore.isInitAuthRoute && !isNotFoundRoute) {
return null;
}
// it is captured by the "not-found" route, then check whether the route exists
const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
const noPermissionRoute: RouteKey = '403';
if (routeStore.isInitAuthRoute && isNotFoundRoute) {
const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
const noPermissionRoute: RouteKey = '403';
if (exist) {
const location: RouteLocationRaw = {
name: noPermissionRoute
};
return location;
}
return null;
}
// if the auth route is not initialized, then initialize the auth route
const isLogin = Boolean(localStg.get('token'));
// 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 query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
if (exist) {
const location: RouteLocationRaw = {
name: noPermissionRoute
name: loginRoute,
query
};
return location;
}
await authStore.initUserInfo();
// initialize the auth route
await routeStore.initAuthRoute();
// the route is captured by the "not-found" route because the auth route is not initialized
// after the auth route is initialized, redirect to the original route
if (isNotFoundRoute) {
const rootRoute: RouteKey = 'root';
const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;
const location: RouteLocationRaw = {
path,
replace: true,
query: to.query,
hash: to.hash
};
return location;

View File

@@ -1,5 +1,5 @@
import type { Router } from 'vue-router';
import { useTitle } from '@vueuse/core';
import type { Router } from 'vue-router';
import { $t } from '@/locales';
export function createDocumentTitleGuard(router: Router) {

View File

@@ -8,7 +8,160 @@ import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [];
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'
}
}
]
}
];
/** create routes when the auth route mode is static */
export function createStaticRoutes() {

View File

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

View File

@@ -0,0 +1,55 @@
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

@@ -1,10 +1,10 @@
import type { AxiosResponse } from 'axios';
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
import { useAuthStore } from '@/store/modules/auth';
import { $t } from '@/locales';
import { localStg } from '@/utils/storage';
import { getServiceBaseURL } from '@/utils/service';
import { $t } from '@/locales';
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
import { handleRefreshToken, showErrorMsg } from './shared';
import type { RequestInstanceState } from './type';
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
@@ -19,8 +19,12 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
},
{
async onRequest(config) {
const Authorization = getAuthorization();
Object.assign(config.headers, { Authorization });
const { headers } = config;
// set token
const token = localStg.get('token');
const Authorization = token ? `Bearer ${token}` : null;
Object.assign(headers, { Authorization });
return config;
},
@@ -79,13 +83,15 @@ 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(responseCode)) {
const success = await handleExpiredRequest(request.state);
if (success) {
const Authorization = getAuthorization();
Object.assign(response.config.headers, { Authorization });
if (expiredTokenCodes.includes(responseCode) && !request.state.isRefreshingToken) {
request.state.isRefreshingToken = true;
return instance.request(response.config) as Promise<AxiosResponse>;
const refreshConfig = await handleRefreshToken(response.config);
request.state.isRefreshingToken = false;
if (refreshConfig) {
return instance.request(refreshConfig) as Promise<AxiosResponse>;
}
}

View File

@@ -1,44 +1,34 @@
import type { AxiosRequestConfig } from 'axios';
import { useAuthStore } from '@/store/modules/auth';
import { localStg } from '@/utils/storage';
import { fetchRefreshToken } from '../api';
import type { RequestInstanceState } from './type';
export function getAuthorization() {
const token = localStg.get('token');
const Authorization = token ? `Bearer ${token}` : null;
return Authorization;
}
/** refresh token */
async function handleRefreshToken() {
/**
* refresh token
*
* @param axiosConfig - request config when the token is expired
*/
export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
const { resetStore } = useAuthStore();
const rToken = localStg.get('refreshToken') || '';
const { error, data } = await fetchRefreshToken(rToken);
const refreshToken = localStg.get('refreshToken') || '';
const { error, data } = await fetchRefreshToken(refreshToken);
if (!error) {
localStg.set('token', data.token);
localStg.set('refreshToken', data.refreshToken);
return true;
const config = { ...axiosConfig };
if (config.headers) {
config.headers.Authorization = data.token;
}
return config;
}
resetStore();
return false;
}
export async function handleExpiredRequest(state: RequestInstanceState) {
if (!state.refreshTokenFn) {
state.refreshTokenFn = handleRefreshToken();
}
const success = await state.refreshTokenFn;
setTimeout(() => {
state.refreshTokenFn = null;
}, 1000);
return success;
return null;
}
export function showErrorMsg(state: RequestInstanceState, message: string) {

View File

@@ -1,6 +1,6 @@
export interface RequestInstanceState {
/** whether the request is refreshing token */
refreshTokenFn: Promise<boolean> | null;
isRefreshingToken: boolean;
/** the request error message stack */
errMsgStack: string[];
}

View File

@@ -1,12 +1,12 @@
import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue';
import { breakpointsTailwind, useBreakpoints, useEventListener, useTitle } from '@vueuse/core';
import { defineStore } from 'pinia';
import { breakpointsTailwind, useBreakpoints, useEventListener, useTitle } from '@vueuse/core';
import { useBoolean } from '@sa/hooks';
import { router } from '@/router';
import { localStg } from '@/utils/storage';
import { SetupStoreId } from '@/enum';
import { router } from '@/router';
import { $t, setLocale } from '@/locales';
import { setDayjsLocale } from '@/locales/dayjs';
import { localStg } from '@/utils/storage';
import { useRouteStore } from '../route';
import { useTabStore } from '../tab';
import { useThemeStore } from '../theme';
@@ -46,10 +46,6 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
});
setReloadFlag(true);
if (themeStore.resetCacheStrategy === 'refresh') {
routeStore.resetRouteCache();
}
}
const locale = ref<App.I18n.LangType>(localStg.get('lang') || 'zh-CN');

View File

@@ -2,10 +2,10 @@ import { computed, reactive, ref } from 'vue';
import { useRoute } from 'vue-router';
import { defineStore } from 'pinia';
import { useLoading } from '@sa/hooks';
import { fetchGetUserInfo, fetchLogin } from '@/service/api';
import { useRouterPush } from '@/hooks/common/router';
import { localStg } from '@/utils/storage';
import { SetupStoreId } from '@/enum';
import { useRouterPush } from '@/hooks/common/router';
import { fetchGetUserInfo, fetchLogin } from '@/service/api';
import { localStg } from '@/utils/storage';
import { $t } from '@/locales';
import { useRouteStore } from '../route';
import { useTabStore } from '../tab';
@@ -69,13 +69,19 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const pass = await loginByToken(loginToken);
if (pass) {
await redirectFromLogin(redirect);
await routeStore.initAuthRoute();
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
duration: 4500
});
if (redirect) {
await redirectFromLogin();
}
if (routeStore.isInitAuthRoute) {
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
duration: 4500
});
}
}
} else {
resetStore();

View File

@@ -1,14 +1,15 @@
import { computed, nextTick, ref, shallowRef } from 'vue';
import { computed, ref, shallowRef } from 'vue';
import type { RouteRecordRaw } from 'vue-router';
import { defineStore } from 'pinia';
import { useBoolean } from '@sa/hooks';
import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
import { router } from '@/router';
import { fetchGetConstantRoutes, fetchGetUserRoutes, fetchIsRouteExist } from '@/service/api';
import { SetupStoreId } from '@/enum';
import { router } from '@/router';
import { createStaticRoutes, getAuthVueRoutes } from '@/router/routes';
import { ROOT_ROUTE } from '@/router/routes/builtin';
import { getRouteName, getRoutePath } from '@/router/elegant/transform';
import { fetchGetConstantRoutes, fetchGetUserRoutes, fetchIsRouteExist } from '@/service/api';
import { useAppStore } from '../app';
import { useAuthStore } from '../auth';
import { useTabStore } from '../tab';
import {
@@ -24,6 +25,7 @@ import {
} from './shared';
export const useRouteStore = defineStore(SetupStoreId.Route, () => {
const appStore = useAppStore();
const authStore = useAuthStore();
const tabStore = useTabStore();
const { bool: isInitConstantRoute, setBool: setIsInitConstantRoute } = useBoolean();
@@ -95,12 +97,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Cache routes */
const cacheRoutes = ref<RouteKey[]>([]);
/**
* Exclude cache routes
*
* for reset route cache
*/
const excludeCacheRoutes = ref<RouteKey[]>([]);
/** All cache routes */
const allCacheRoutes = shallowRef<RouteKey[]>([]);
/**
* Get cache routes
@@ -108,23 +106,69 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
* @param routes Vue routes
*/
function getCacheRoutes(routes: RouteRecordRaw[]) {
cacheRoutes.value = getCacheRouteNames(routes);
const alls = getCacheRouteNames(routes);
cacheRoutes.value = alls;
allCacheRoutes.value = [...alls];
}
/**
* Reset route cache
* Add cache routes
*
* @default router.currentRoute.value.name current route name
* @param routeKey
*/
async function resetRouteCache(routeKey?: RouteKey) {
const routeName = routeKey || (router.currentRoute.value.name as RouteKey);
function addCacheRoutes(routeKey: RouteKey) {
if (cacheRoutes.value.includes(routeKey)) return;
excludeCacheRoutes.value.push(routeName);
cacheRoutes.value.push(routeKey);
}
await nextTick();
/**
* Remove cache routes
*
* @param routeKey
*/
function removeCacheRoutes(routeKey: RouteKey) {
const index = cacheRoutes.value.findIndex(item => item === routeKey);
excludeCacheRoutes.value = [];
if (index === -1) return;
cacheRoutes.value.splice(index, 1);
}
/**
* Is cached route
*
* @param routeKey
*/
function isCachedRoute(routeKey: RouteKey) {
return allCacheRoutes.value.includes(routeKey);
}
/**
* Re cache routes by route key
*
* @param routeKey
*/
async function reCacheRoutesByKey(routeKey: RouteKey) {
if (!isCachedRoute(routeKey)) return;
removeCacheRoutes(routeKey);
await appStore.reloadPage();
addCacheRoutes(routeKey);
}
/**
* Re cache routes by route keys
*
* @param routeKeys
*/
async function reCacheRoutesByKeys(routeKeys: RouteKey[]) {
for await (const key of routeKeys) {
await reCacheRoutesByKey(key);
}
}
/** Global breadcrumbs */
@@ -170,17 +214,10 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
handleConstantAndAuthRoutes();
setIsInitConstantRoute(true);
tabStore.initHomeTab();
}
/** Init auth route */
async function initAuthRoute() {
// check if user info is initialized
if (!authStore.userInfo.userId) {
await authStore.initUserInfo();
}
if (authRouteMode.value === 'static') {
initStaticAuthRoute();
} else {
@@ -317,14 +354,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
}
async function onRouteSwitchWhenLoggedIn() {
await authStore.initUserInfo();
}
async function onRouteSwitchWhenNotLoggedIn() {
// some global init logic if it does not need to be logged in
}
return {
resetStore,
routeHome,
@@ -332,8 +361,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
searchMenus,
updateGlobalMenusByLocale,
cacheRoutes,
excludeCacheRoutes,
resetRouteCache,
reCacheRoutesByKey,
reCacheRoutesByKeys,
breadcrumbs,
initConstantRoute,
isInitConstantRoute,
@@ -341,8 +370,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
isInitAuthRoute,
setIsInitAuthRoute,
getIsAuthRouteExist,
getSelectedMenuKeyPath,
onRouteSwitchWhenLoggedIn,
onRouteSwitchWhenNotLoggedIn
getSelectedMenuKeyPath
};
});

View File

@@ -1,7 +1,7 @@
import type { RouteLocationNormalizedLoaded, RouteRecordRaw, _RouteRecordBase } from 'vue-router';
import type { ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
import { useSvgIcon } from '@/hooks/common/icon';
import { $t } from '@/locales';
import { useSvgIcon } from '@/hooks/common/icon';
/**
* Filter auth routes by roles
@@ -19,7 +19,7 @@ export function filterAuthRoutesByRoles(routes: ElegantConstRoute[], roles: stri
* @param route Auth route
* @param roles Roles
*/
function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]): ElegantConstRoute[] {
function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]) {
const routeRoles = (route.meta && route.meta.roles) || [];
// if the route's "roles" is empty, then it is allowed to access
@@ -34,11 +34,6 @@ function filterAuthRouteByRoles(route: ElegantConstRoute, roles: string[]): Eleg
filterRoute.children = filterRoute.children.flatMap(item => filterAuthRouteByRoles(item, roles));
}
// Exclude the route if it has no children after filtering
if (filterRoute.children?.length === 0) {
return [];
}
return hasPermission || isEmptyRoles ? [filterRoute] : [];
}
@@ -286,22 +281,13 @@ export function getBreadcrumbsByRoute(
const key = route.name as string;
const activeKey = route.meta?.activeMenu;
const menuKey = activeKey || key;
for (const menu of menus) {
if (menu.key === key) {
return [transformMenuToBreadcrumb(menu)];
}
if (menu.key === menuKey) {
const breadcrumbMenu = menuKey !== activeKey ? menu : getGlobalMenuByBaseRoute(route);
if (menu.key === activeKey) {
const ROUTE_DEGREE_SPLITTER = '_';
const parentKey = key.split(ROUTE_DEGREE_SPLITTER).slice(0, -1).join(ROUTE_DEGREE_SPLITTER);
const breadcrumbMenu = getGlobalMenuByBaseRoute(route);
if (parentKey !== activeKey) {
return [transformMenuToBreadcrumb(breadcrumbMenu)];
}
return [transformMenuToBreadcrumb(menu), transformMenuToBreadcrumb(breadcrumbMenu)];
return [transformMenuToBreadcrumb(breadcrumbMenu)];
}
if (menu.children?.length) {

View File

@@ -1,12 +1,12 @@
import { computed, ref } from 'vue';
import { useEventListener } from '@vueuse/core';
import { defineStore } from 'pinia';
import { useEventListener } from '@vueuse/core';
import type { RouteKey } from '@elegant-router/types';
import { router } from '@/router';
import { useRouteStore } from '@/store/modules/route';
import { SetupStoreId } from '@/enum';
import { useRouterPush } from '@/hooks/common/router';
import { localStg } from '@/utils/storage';
import { SetupStoreId } from '@/enum';
import { useRouteStore } from '@/store/modules/route';
import { useThemeStore } from '../theme';
import {
extractTabsByAllRoutes,
@@ -160,25 +160,6 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
update();
}
const { routerPushByKey } = useRouterPush();
/**
* Replace tab
*
* @param key Route key
* @param options Router push options
*/
async function replaceTab(key: RouteKey, options?: App.Global.RouterPushOptions) {
const oldTabId = activeTabId.value;
// push new route
await routerPushByKey(key, options);
// remove old tab (exclude fixed tab)
if (!isTabRetain(oldTabId)) {
await removeTab(oldTabId);
}
}
/**
* Switch route by tab
*
@@ -301,7 +282,6 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
removeTab,
removeActiveTab,
removeTabByRouteName,
replaceTab,
clearTabs,
clearLeftTabs,
clearRightTabs,

View File

@@ -1,10 +1,10 @@
import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
import type { Ref } from 'vue';
import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
import { defineStore } from 'pinia';
import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
import { getPaletteColorByNumber } from '@sa/color';
import { localStg } from '@/utils/storage';
import { SetupStoreId } from '@/enum';
import { localStg } from '@/utils/storage';
import {
addThemeVarsToGlobal,
createThemeToken,
@@ -174,7 +174,6 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
darkMode,
val => {
toggleCssDarkMode(val);
localStg.set('darkMode', val);
},
{ immediate: true }
);

View File

@@ -1,11 +1,11 @@
import type { GlobalThemeOverrides } from 'naive-ui';
import { defu } from 'defu';
import { addColorAlpha, getColorPalette, getPaletteColorByNumber, getRgb } from '@sa/color';
import { DARK_CLASS } from '@/constants/app';
import { toggleHtmlClass } from '@/utils/common';
import { localStg } from '@/utils/storage';
import { overrideThemeSettings, themeSettings } from '@/theme/settings';
import { themeVars } from '@/theme/vars';
import { toggleHtmlClass } from '@/utils/common';
import { localStg } from '@/utils/storage';
const DARK_CLASS = 'dark';
/** Init theme settings */
export function initThemeSettings() {
@@ -17,15 +17,12 @@ export function initThemeSettings() {
// if it is production mode, the theme settings will be cached in localStorage
// if want to update theme settings when publish new version, please update `overrideThemeSettings` in `src/theme/settings.ts`
const localSettings = localStg.get('themeSettings');
let settings = defu(localSettings, themeSettings);
const settings = localStg.get('themeSettings') || themeSettings;
const isOverride = localStg.get('overrideThemeFlag') === BUILD_TIME;
if (!isOverride) {
settings = defu(overrideThemeSettings, settings);
Object.assign(settings, overrideThemeSettings);
localStg.set('overrideThemeFlag', BUILD_TIME);
}

View File

@@ -113,8 +113,7 @@ code,
kbd,
samp,
pre {
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
font-size: 1em; /* 2 */
}

View File

@@ -1 +1 @@
@forward 'scrollbar';
@import './scrollbar.scss';

View File

@@ -12,7 +12,6 @@ export const themeSettings: App.Theme.ThemeSetting = {
error: '#f5222d'
},
isInfoFollowPrimary: true,
resetCacheStrategy: 'close',
layout: {
mode: 'vertical',
scrollMode: 'content',
@@ -27,9 +26,6 @@ export const themeSettings: App.Theme.ThemeSetting = {
breadcrumb: {
visible: true,
showIcon: true
},
multilingual: {
visible: true
}
},
tab: {
@@ -86,4 +82,9 @@ export const themeSettings: App.Theme.ThemeSetting = {
*
* If publish new version, use `overrideThemeSettings` to override certain theme settings
*/
export const overrideThemeSettings: Partial<App.Theme.ThemeSetting> = {};
export const overrideThemeSettings: Partial<App.Theme.ThemeSetting> = {
watermark: {
visible: false,
text: 'SoybeanAdmin'
}
};

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

@@ -20,9 +20,6 @@ declare namespace Api {
records: T[];
}
/** common search params of table */
type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>;
/**
* enable status
*
@@ -84,4 +81,142 @@ 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[];
};
}
}

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

@@ -20,8 +20,6 @@ declare namespace App {
otherColor: OtherColor;
/** Whether info color is followed by the primary color */
isInfoFollowPrimary: boolean;
/** Reset cache strategy */
resetCacheStrategy: UnionKey.ResetCacheStrategy;
/** Layout */
layout: {
/** Layout mode */
@@ -33,7 +31,7 @@ declare namespace App {
*
* if true, the vertical child level menus in left and horizontal first level menus in top
*/
reverseHorizontalMix: boolean;
reverseHorizontalMix?: boolean;
};
/** Page */
page: {
@@ -53,11 +51,6 @@ declare namespace App {
/** Whether to show the breadcrumb icon */
showIcon: boolean;
};
/** Multilingual */
multilingual: {
/** Whether to show the multilingual */
visible: boolean;
};
};
/** Tab */
tab: {
@@ -103,7 +96,7 @@ declare namespace App {
right: boolean;
};
/** Watermark */
watermark: {
watermark?: {
/** Whether to show the watermark */
visible: boolean;
/** Watermark text */
@@ -175,12 +168,6 @@ declare namespace App {
type RoutePath = import('@elegant-router/types').RoutePath;
type LastLevelRouteKey = import('@elegant-router/types').LastLevelRouteKey;
/** The router push options */
type RouterPushOptions = {
query?: Record<string, string>;
params?: Record<string, string>;
};
/** The global header props */
interface HeaderProps {
/** Whether to show the logo */
@@ -374,9 +361,6 @@ declare namespace App {
visible: string;
showIcon: string;
};
multilingual: {
visible: string;
};
};
tab: {
visible: string;
@@ -404,7 +388,6 @@ declare namespace App {
};
themeDrawerTitle: string;
pageFunTitle: string;
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
configOperation: {
copyConfig: string;
copySuccessMsg: string;
@@ -460,6 +443,19 @@ 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;
@@ -488,6 +484,163 @@ 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

@@ -2,7 +2,6 @@
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
@@ -12,12 +11,16 @@ declare module 'vue' {
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
CountTo: typeof import('./../components/custom/count-to.vue')['default']
CustomIconSelect: typeof import('./../components/custom/custom-icon-select.vue')['default']
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
GithubLink: typeof import('./../components/custom/github-link.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
'IconCharm:download': typeof import('~icons/charm/download')['default']
'IconFileIcons:microsoftExcel': typeof import('~icons/file-icons/microsoft-excel')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
'IconIc:roundPlus': typeof import('~icons/ic/round-plus')['default']
@@ -26,8 +29,12 @@ declare module 'vue' {
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
IconLocalActivity: typeof import('~icons/local/activity')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalCast: typeof import('~icons/local/cast')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default']
'IconMaterialSymbolsLight:rotate90DegreesCcwOutlineRounded': typeof import('~icons/material-symbols-light/rotate90-degrees-ccw-outline-rounded')['default']
'IconMdi:printer': typeof import('~icons/mdi/printer')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
@@ -38,12 +45,13 @@ 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']
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
@@ -70,16 +78,19 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPagination: typeof import('naive-ui')['NPagination']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSpace: typeof import('naive-ui')['NSpace']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
@@ -97,5 +108,6 @@ declare module 'vue' {
TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
WebSiteLink: typeof import('./../components/custom/web-site-link.vue')['default']
}
}

View File

@@ -17,12 +17,65 @@ 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";
"plugin": "/plugin";
"plugin_barcode": "/plugin/barcode";
"plugin_charts": "/plugin/charts";
"plugin_charts_echarts": "/plugin/charts/echarts";
"plugin_copy": "/plugin/copy";
"plugin_editor": "/plugin/editor";
"plugin_editor_markdown": "/plugin/editor/markdown";
"plugin_editor_quill": "/plugin/editor/quill";
"plugin_excel": "/plugin/excel";
"plugin_gantt": "/plugin/gantt";
"plugin_icon": "/plugin/icon";
"plugin_map": "/plugin/map";
"plugin_pdf": "/plugin/pdf";
"plugin_pinyin": "/plugin/pinyin";
"plugin_print": "/plugin/print";
"plugin_swiper": "/plugin/swiper";
"plugin_typeit": "/plugin/typeit";
"plugin_video": "/plugin/video";
"user-center": "/user-center";
};
/**
@@ -42,6 +95,18 @@ declare module "@elegant-router/types" {
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"
>;
/**
@@ -57,9 +122,15 @@ declare module "@elegant-router/types" {
| "403"
| "404"
| "500"
| "about"
| "function"
| "home"
| "iframe-page"
| "login"
| "manage"
| "multi-menu"
| "plugin"
| "user-center"
>;
/**
@@ -69,6 +140,8 @@ declare module "@elegant-router/types" {
CustomRouteKey,
| "root"
| "not-found"
| "exception"
| "document"
>;
/**
@@ -81,7 +154,38 @@ 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"
| "plugin_barcode"
| "plugin_charts_echarts"
| "plugin_copy"
| "plugin_editor_markdown"
| "plugin_editor_quill"
| "plugin_excel"
| "plugin_gantt"
| "plugin_icon"
| "plugin_map"
| "plugin_pdf"
| "plugin_pinyin"
| "plugin_print"
| "plugin_swiper"
| "plugin_typeit"
| "plugin_video"
| "user-center"
>;
/**
@@ -91,6 +195,16 @@ 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

@@ -8,7 +8,6 @@ declare namespace Env {
type RouterHistoryMode = 'hash' | 'history' | 'memory';
/** Interface for import.meta */
// eslint-disable-next-line @typescript-eslint/no-shadow
interface ImportMeta extends ImportMetaEnv {
/** The base url of the application */
readonly VITE_BASE_URL: string;
@@ -104,10 +103,6 @@ declare namespace Env {
readonly VITE_ICONIFY_URL?: string;
/** Used to differentiate storage across different domains */
readonly VITE_STORAGE_PREFIX?: string;
/** Whether to automatically detect updates after configuring application packaging */
readonly VITE_AUTOMATICALLY_DETECT_UPDATE?: CommonType.YesOrNo;
/** show proxy url log in terminal */
readonly VITE_PROXY_LOG?: CommonType.YesOrNo;
}
}

View File

@@ -14,6 +14,14 @@ declare global {
$notification?: import('naive-ui').NotificationProviderInst;
}
interface ViewTransition {
ready: Promise<void>;
}
export interface Document {
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition;
}
/** Build time of the project */
export const BUILD_TIME: string;
}

View File

@@ -26,7 +26,7 @@ declare namespace NaiveUI {
type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>;
type TableApiFn<T = any, R = Api.Common.CommonSearchParams> = (
type TableApiFn<T = any, R = Api.SystemManage.CommonSearchParams> = (
params: R
) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>;

20
src/typings/package.d.ts vendored Normal file
View File

@@ -0,0 +1,20 @@
/// <reference types="@amap/amap-jsapi-types" />
/// <reference types="bmapgl" />
declare namespace BMap {
class Map extends BMapGL.Map {}
class Point extends BMapGL.Point {}
}
declare const TMap: any;
interface Window {
/**
* make baidu map request under https protocol
*
* - 0: http
* - 1: https
* - 2: https
*/
HOST_TYPE: '0' | '1' | '2';
}

View File

@@ -20,8 +20,6 @@ declare namespace StorageType {
refreshToken: string;
/** The theme color */
themeColor: string;
/** The dark mode */
darkMode: boolean;
/** The theme settings */
themeSettings: App.Theme.ThemeSetting;
/**

View File

@@ -14,14 +14,6 @@ declare namespace UnionKey {
/** Theme scheme */
type ThemeScheme = 'light' | 'dark' | 'auto';
/**
* Reset cache strategy
*
* - close: re-cache when close page
* - refresh: re-cache when refresh page
*/
type ResetCacheStrategy = 'close' | 'refresh';
/**
* The layout mode
*

View File

@@ -1,7 +0,0 @@
export function isPC() {
const agents = ['Android', 'iPhone', 'webOS', 'BlackBerry', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
const isMobile = agents.some(agent => window.navigator.userAgent.includes(agent));
return !isMobile;
}

View File

@@ -1,5 +1,3 @@
import json5 from 'json5';
/**
* Create service config by current env
*
@@ -10,10 +8,10 @@ export function createServiceConfig(env: Env.ImportMeta) {
let other = {} as Record<App.Service.OtherBaseURLKey, string>;
try {
other = json5.parse(VITE_OTHER_SERVICE_BASE_URL);
} catch {
other = JSON.parse(VITE_OTHER_SERVICE_BASE_URL);
} catch (error) {
// eslint-disable-next-line no-console
console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid json5 string');
console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid JSON string');
}
const httpConfig: App.Service.SimpleServiceConfig = {

View File

@@ -2,10 +2,10 @@
import { computed } from 'vue';
import type { Component } from 'vue';
import { getPaletteColorByNumber, mixColor } from '@sa/color';
import { loginModuleRecord } from '@/constants/app';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme';
import { $t } from '@/locales';
import { loginModuleRecord } from '@/constants/app';
import PwdLogin from './modules/pwd-login.vue';
import CodeLogin from './modules/code-login.vue';
import Register from './modules/register.vue';
@@ -66,7 +66,6 @@ const bgColor = computed(() => {
@switch="themeStore.toggleThemeScheme"
/>
<LangSwitch
v-if="themeStore.header.multilingual.visible"
:lang="appStore.locale"
:lang-options="appStore.localeOptions"
:show-tooltip="false"

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