Compare commits

..

1 Commits

Author SHA1 Message Date
Soybean
a0c52201da reafctor(projects): use new elegant-router plugin 2025-08-13 16:26:25 +08:00
87 changed files with 2389 additions and 3509 deletions

2
.gitignore vendored
View File

@@ -33,3 +33,5 @@ package-lock.json
yarn.lock
.VSCodeCounter
.temp

View File

@@ -1,287 +1,6 @@
# Changelog
## [v2.0.0](https://github.com/soybeanjs/soybean-admin/compare/v1.3.15...v2.0.0) (2025-11-02)
###    🚨 Breaking Changes
- **hooks**: refactor useTable and enhance type definitions &nbsp;-&nbsp; by @soybeanjs [<samp>(8cc51)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8cc5177c)
- **projects**: optimize layout mode, split horizontal mix component into two layouts, and rename the component. &nbsp;-&nbsp; by **Azir** [<samp>(b6ac3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b6ac3106)
- **request**: remove cancelRequest method and related logic from request instances &nbsp;-&nbsp; by @soybeanjs [<samp>(b4e12)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b4e12530)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **components**:
- add the IconTooltip component. &nbsp;-&nbsp; by **Azir-11** [<samp>(a55b4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a55b4dc0)
- replace NTooltip with IconTooltip and optimize the layout of related components. &nbsp;-&nbsp; by **Azir-11** [<samp>(40057)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4005763c)
- **global-tab**:
- add support for switching tabs with right mouse button click &nbsp;-&nbsp; by @soybeanjs [<samp>(b2c91)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b2c919b6)
- **hooks**:
- add scrollX computation for total table width in useNaiveTable &nbsp;-&nbsp; by @Lruihao [<samp>(358e1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/358e1297)
- **packages**:
- materials support slider-tab. closed #823 &nbsp;-&nbsp; by @CyberShen in https://github.com/soybeanjs/soybean-admin/issues/823 [<samp>(61fa4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/61fa4b7f)
- **projects**:
- refactor theme drawer with tabbed layout for better UX. &nbsp;-&nbsp; by **Azir** [<samp>(8ba71)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8ba71a08)
- Add current time display option for watermark &nbsp;-&nbsp; by @wenyuanw in https://github.com/soybeanjs/soybean-admin/issues/772 [<samp>(f238f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f238fcbd)
- add 'vertical-hybrid-header-first' layout mode &nbsp;-&nbsp; by @wenyuanw [<samp>(b4e5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b4e5c6d9)
- add prompt information for scrolling mode and tab bar caching. &nbsp;-&nbsp; by **Azir-11** [<samp>(29a2a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/29a2a5c6)
- support theme preset function. &nbsp;-&nbsp; by **Azir-11** [<samp>(257f1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/257f1183)
- modify the default value of the reset cache policy to 'refresh'. &nbsp;-&nbsp; by **Azir-11** [<samp>(3c0a5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3c0a5282)
- optimize tabs cache cleaning strategy. close #820. &nbsp;-&nbsp; by **Azir-11** in https://github.com/soybeanjs/soybean-admin/issues/820 [<samp>(ef7ac)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ef7acc62)
- support closing tabs with middle mouse button click &nbsp;-&nbsp; by @wenyuanw [<samp>(a8d1e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a8d1e5d2)
- support set global redius &nbsp;-&nbsp; by **CyberShen123** [<samp>(24c6d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/24c6df52)
- support set global redius &nbsp;-&nbsp; by **CyberShen123** [<samp>(3549c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3549c4db)
- compatible with the new Echarts API and optimized styles. &nbsp;-&nbsp; by **Azir-11** [<samp>(9755c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9755c313)
- **styles**:
- add text-autospace property to improve text layout &nbsp;-&nbsp; by @wenyuanw [<samp>(345aa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/345aa293)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **hooks**:
- correct chart rendering logic in useEcharts &nbsp;-&nbsp; by @soybeanjs [<samp>(8a7cd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8a7cd593)
- **layout**:
- fix getSiderWidth &nbsp;-&nbsp; by @soybeanjs [<samp>(e471e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e471e914)
- **packages**:
- fix the parsing logic for stored data to ensure correct return of boolean values &nbsp;-&nbsp; by @Lruihao [<samp>(9ea56)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9ea56c9b)
- axios: fix json response. fixed #815 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [<samp>(fd087)</samp>](https://github.com/soybeanjs/soybean-admin/commit/fd087f59)
- axios: fix json response. fixed #815 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [<samp>(5be86)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5be864a8)
- **projects**:
- Fix i18n-ally not working when setting moduleResolution to bundler. fixed #780 &nbsp;-&nbsp; by @xiaobao0505 in https://github.com/soybeanjs/soybean-admin/issues/780 [<samp>(41191)</samp>](https://github.com/soybeanjs/soybean-admin/commit/41191d54)
- adjust legend position in line chart options. &nbsp;-&nbsp; by **Azir-11** [<samp>(0b998)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0b9982bd)
- **readme**:
- update GitHub stars and forks links for gitee &nbsp;-&nbsp; by @soybeanjs [<samp>(923eb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/923eb98a)
- **scripts**:
- update command to use 'npm-check-updates' instead of 'ncu' &nbsp;-&nbsp; by @soybeanjs [<samp>(8dc17)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8dc17e62)
- **styles**:
- show light color scrollbar while dark mode is on &nbsp;-&nbsp; by **whyang** [<samp>(dac50)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dac5075b)
- **table**:
- add type annotations for records in useTable hook &nbsp;-&nbsp; by @soybeanjs [<samp>(32b8f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/32b8f990)
- **types**:
- fix proxy types &nbsp;-&nbsp; by @soybeanjs [<samp>(3d72f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3d72f954)
- fix proxy types &nbsp;-&nbsp; by @soybeanjs [<samp>(12b25)</samp>](https://github.com/soybeanjs/soybean-admin/commit/12b25e0d)
- fix ts type error &nbsp;-&nbsp; by @soybeanjs [<samp>(d5a3a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d5a3a25d)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **hooks**:
- optimize useEcharts &nbsp;-&nbsp; by @soybeanjs [<samp>(936b8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/936b834e)
- **packages**:
- remove ofetch package &nbsp;-&nbsp; by @soybeanjs [<samp>(abaaa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/abaaa4a0)
- **projects**:
- improve theme drawer responsive width for mobile devices &nbsp;-&nbsp; by @wenyuanw [<samp>(8439a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8439a600)
- improve robustness of second-level menu key logic &nbsp;-&nbsp; by @wenyuanw [<samp>(8b8a2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8b8a2083)
- optimize theme drawer width &nbsp;-&nbsp; by @soybeanjs [<samp>(81468)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8146858b)
- optimize api type file &nbsp;-&nbsp; by @soybeanjs [<samp>(3a343)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3a343eea)
- optimize radius settings &nbsp;-&nbsp; by @soybeanjs [<samp>(87a66)</samp>](https://github.com/soybeanjs/soybean-admin/commit/87a66a42)
- **request**:
- enhance request options and response handling with generic types &nbsp;-&nbsp; by @soybeanjs [<samp>(50a5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/50a5cba0)
- **typings**:
- update component typings &nbsp;-&nbsp; by @soybeanjs [<samp>(1d142)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1d142695)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **hooks**:
- optimize useContext and update useMixMenuContext &nbsp;-&nbsp; by @soybeanjs [<samp>(c9651)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c965140b)
- streamline column visibility handling in useTable and table components &nbsp;-&nbsp; by @soybeanjs [<samp>(ee434)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ee434145)
- remove useSignal hook and update exports &nbsp;-&nbsp; by @soybeanjs [<samp>(87adc)</samp>](https://github.com/soybeanjs/soybean-admin/commit/87adc35f)
- **menu**:
- optimize the margin on the menu &nbsp;-&nbsp; by **NicholasLD** [<samp>(d7311)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d7311111)
- **projects**:
- remove unnecessary logic in onRouteSwitchWhenLoggedIn &nbsp;-&nbsp; by @wenyuanw [<samp>(d6c81)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d6c8142b)
- **request**:
- unify response transformation methods and deprecate transformBackendResponse &nbsp;-&nbsp; by @soybeanjs [<samp>(f83ee)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f83eefbc)
- **types**:
- move Auth and Route namespaces to separate files and clean up api.d.ts &nbsp;-&nbsp; by **Azir** [<samp>(d37ce)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d37ce046)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**:
- add github trendshift info. &nbsp;-&nbsp; by **恕瑞玛的皇帝** [<samp>(e18d3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e18d3972)
- add github trendshift info. &nbsp;-&nbsp; by **恕瑞玛的皇帝** [<samp>(2a0c9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2a0c9f1b)
- add contribution leaderboard &nbsp;-&nbsp; by @wenyuanw [<samp>(01744)</samp>](https://github.com/soybeanjs/soybean-admin/commit/017440c1)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update NodeJS and pnpm version requirements in package.json and documentation &nbsp;-&nbsp; by **Junior25306** [<samp>(a5c4b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a5c4b4e3)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(5cb1c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5cb1cebd)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(aeb63)</samp>](https://github.com/soybeanjs/soybean-admin/commit/aeb63690)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(e89b8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e89b86ce)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(c962f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c962f7b2)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(12135)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1213531b)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(e33f9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e33f944a)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(9fa95)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9fa951aa)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(b041f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b041fdd8)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(d567c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d567c057)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(6cbf5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/6cbf5705)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(6010f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/6010f518)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(232f5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/232f56fd)
- **other**:
- update the ESLint validation configuration to support more file types. &nbsp;-&nbsp; by **Azir-11** [<samp>(8d7f9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8d7f91dc)
- update the ESLint validation configuration to support more file types. &nbsp;-&nbsp; by **Azir-11** [<samp>(be8f9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/be8f915a)
- **packages**:
- update Vite version to 7 in package.json and documentation. &nbsp;-&nbsp; by **Azir** [<samp>(03dd6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/03dd64c5)
- add picomatch to fix scripts &nbsp;-&nbsp; by @soybeanjs [<samp>(805c3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/805c3381)
- **readme**:
- remove DartNode sponsorship badge from README files &nbsp;-&nbsp; by @soybeanjs [<samp>(33ade)</samp>](https://github.com/soybeanjs/soybean-admin/commit/33ade539)
- **vscode**:
- remove unused vue.server.hybridMode setting from .vscode/settings.json &nbsp;-&nbsp; by @soybeanjs [<samp>(13319)</samp>](https://github.com/soybeanjs/soybean-admin/commit/133196f3)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**: format code. &nbsp;-&nbsp; by **Azir-11** [<samp>(100e0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/100e0ea5)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)&nbsp;&nbsp;[![CyberShen](https://github.com/CyberShen.png?size=48)](https://github.com/CyberShen)&nbsp;&nbsp;[![Lruihao](https://github.com/Lruihao.png?size=48)](https://github.com/Lruihao)&nbsp;&nbsp;[![xiaobao0505](https://github.com/xiaobao0505.png?size=48)](https://github.com/xiaobao0505)&nbsp;&nbsp;
[Azir-11](mailto:2075125282@qq.com),&nbsp;[CyberShen123](mailto:s.lijun@qq.com),&nbsp;[whyang](mailto:whyang9701@gmail.com),&nbsp;[HongxuanG](mailto:1359774872@qq.com),&nbsp;[NicholasLD](mailto:878639947@qq.com),&nbsp;[Junior25306](mailto:dayu429@qq.com)
## [v2.0.0-beta.2](https://github.com/soybeanjs/soybean-admin/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2025-10-27)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **global-tab**: add support for switching tabs with right mouse button click &nbsp;-&nbsp; by @soybeanjs [<samp>(b2c91)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b2c919b6)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **layout**: fix getSiderWidth &nbsp;-&nbsp; by @soybeanjs [<samp>(e471e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e471e914)
- **packages**: axios: fix json response. fixed #815 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [<samp>(fd087)</samp>](https://github.com/soybeanjs/soybean-admin/commit/fd087f59)
- **readme**: update GitHub stars and forks links for gitee &nbsp;-&nbsp; by @soybeanjs [<samp>(923eb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/923eb98a)
- **scripts**: update command to use 'npm-check-updates' instead of 'ncu' &nbsp;-&nbsp; by @soybeanjs [<samp>(8dc17)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8dc17e62)
- **types**: fix proxy types &nbsp;-&nbsp; by @soybeanjs [<samp>(12b25)</samp>](https://github.com/soybeanjs/soybean-admin/commit/12b25e0d)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**:
- add github trendshift info. &nbsp;-&nbsp; by **恕瑞玛的皇帝** [<samp>(e18d3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e18d3972)
- add contribution leaderboard &nbsp;-&nbsp; by @wenyuanw [<samp>(01744)</samp>](https://github.com/soybeanjs/soybean-admin/commit/017440c1)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(e33f9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e33f944a)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(9fa95)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9fa951aa)
- **other**:
- update the ESLint validation configuration to support more file types. &nbsp;-&nbsp; by **Azir-11** [<samp>(8d7f9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8d7f91dc)
- **readme**:
- remove DartNode sponsorship badge from README files &nbsp;-&nbsp; by @soybeanjs [<samp>(33ade)</samp>](https://github.com/soybeanjs/soybean-admin/commit/33ade539)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)&nbsp;&nbsp;
[恕瑞玛的皇帝](mailto:2075125282@qq.com)
## [v2.0.0-beta.1](https://github.com/soybeanjs/soybean-admin/compare/v1.3.15...v2.0.0-beta.1) (2025-10-25)
### &nbsp;&nbsp;&nbsp;🚨 Breaking Changes
- **hooks**: refactor useTable and enhance type definitions &nbsp;-&nbsp; by @soybeanjs [<samp>(8cc51)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8cc5177)
- **projects**: optimize layout mode, split horizontal mix component into two layouts, and rename the component. &nbsp;-&nbsp; by **Azir** [<samp>(b6ac3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b6ac310)
- **request**: remove cancelRequest method and related logic from request instances &nbsp;-&nbsp; by @soybeanjs [<samp>(b4e12)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b4e1253)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **components**:
- add the IconTooltip component. &nbsp;-&nbsp; by **Azir-11** [<samp>(a55b4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a55b4dc)
- replace NTooltip with IconTooltip and optimize the layout of related components. &nbsp;-&nbsp; by **Azir-11** [<samp>(40057)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4005763)
- **hooks**:
- add scrollX computation for total table width in useNaiveTable &nbsp;-&nbsp; by @Lruihao [<samp>(358e1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/358e129)
- **packages**:
- materials support slider-tab. closed #823 &nbsp;-&nbsp; by @CyberShen in https://github.com/soybeanjs/soybean-admin/issues/823 [<samp>(61fa4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/61fa4b7)
- **projects**:
- refactor theme drawer with tabbed layout for better UX. &nbsp;-&nbsp; by **Azir** [<samp>(8ba71)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8ba71a0)
- Add current time display option for watermark &nbsp;-&nbsp; by @wenyuanw in https://github.com/soybeanjs/soybean-admin/issues/772 [<samp>(f238f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f238fcb)
- add 'vertical-hybrid-header-first' layout mode &nbsp;-&nbsp; by @wenyuanw [<samp>(b4e5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b4e5c6d)
- add prompt information for scrolling mode and tab bar caching. &nbsp;-&nbsp; by **Azir-11** [<samp>(29a2a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/29a2a5c)
- support theme preset function. &nbsp;-&nbsp; by **Azir-11** [<samp>(257f1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/257f118)
- modify the default value of the reset cache policy to 'refresh'. &nbsp;-&nbsp; by **Azir-11** [<samp>(3c0a5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3c0a528)
- optimize tabs cache cleaning strategy. close #820. &nbsp;-&nbsp; by **Azir-11** in https://github.com/soybeanjs/soybean-admin/issues/820 [<samp>(ef7ac)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ef7acc6)
- support closing tabs with middle mouse button click &nbsp;-&nbsp; by @wenyuanw [<samp>(a8d1e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a8d1e5d)
- support set global redius &nbsp;-&nbsp; by **CyberShen123** [<samp>(24c6d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/24c6df5)
- support set global redius &nbsp;-&nbsp; by **CyberShen123** [<samp>(3549c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3549c4d)
- **styles**:
- add text-autospace property to improve text layout &nbsp;-&nbsp; by @wenyuanw [<samp>(345aa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/345aa29)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **hooks**:
- correct chart rendering logic in useEcharts &nbsp;-&nbsp; by @soybeanjs [<samp>(8a7cd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8a7cd59)
- **packages**:
- fix the parsing logic for stored data to ensure correct return of boolean values &nbsp;-&nbsp; by @Lruihao [<samp>(9ea56)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9ea56c9)
- axios: fix json response. fixed #815 &nbsp;-&nbsp; by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/815 [<samp>(5be86)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5be864a)
- **projects**:
- Fix i18n-ally not working when setting moduleResolution to bundler. fixed #780 &nbsp;-&nbsp; by @xiaobao0505 in https://github.com/soybeanjs/soybean-admin/issues/780 [<samp>(41191)</samp>](https://github.com/soybeanjs/soybean-admin/commit/41191d5)
- **styles**:
- show light color scrollbar while dark mode is on &nbsp;-&nbsp; by **whyang** [<samp>(dac50)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dac5075)
- **table**:
- add type annotations for records in useTable hook &nbsp;-&nbsp; by @soybeanjs [<samp>(32b8f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/32b8f99)
- **types**:
- fix proxy types &nbsp;-&nbsp; by @soybeanjs [<samp>(3d72f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3d72f95)
- fix ts type error &nbsp;-&nbsp; by @soybeanjs [<samp>(d5a3a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d5a3a25)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **hooks**:
- optimize useEcharts &nbsp;-&nbsp; by @soybeanjs [<samp>(936b8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/936b834)
- **packages**:
- remove ofetch package &nbsp;-&nbsp; by @soybeanjs [<samp>(abaaa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/abaaa4a)
- **projects**:
- improve theme drawer responsive width for mobile devices &nbsp;-&nbsp; by @wenyuanw [<samp>(8439a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8439a60)
- improve robustness of second-level menu key logic &nbsp;-&nbsp; by @wenyuanw [<samp>(8b8a2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8b8a208)
- optimize theme drawer width &nbsp;-&nbsp; by @soybeanjs [<samp>(81468)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8146858)
- optimize api type file &nbsp;-&nbsp; by @soybeanjs [<samp>(3a343)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3a343ee)
- optimize radius settings &nbsp;-&nbsp; by @soybeanjs [<samp>(87a66)</samp>](https://github.com/soybeanjs/soybean-admin/commit/87a66a4)
- **request**:
- enhance request options and response handling with generic types &nbsp;-&nbsp; by @soybeanjs [<samp>(50a5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/50a5cba)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **hooks**:
- optimize useContext and update useMixMenuContext &nbsp;-&nbsp; by @soybeanjs [<samp>(c9651)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c965140)
- streamline column visibility handling in useTable and table components &nbsp;-&nbsp; by @soybeanjs [<samp>(ee434)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ee43414)
- remove useSignal hook and update exports &nbsp;-&nbsp; by @soybeanjs [<samp>(87adc)</samp>](https://github.com/soybeanjs/soybean-admin/commit/87adc35)
- **menu**:
- optimize the margin on the menu &nbsp;-&nbsp; by **NicholasLD** [<samp>(d7311)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d731111)
- **projects**:
- remove unnecessary logic in onRouteSwitchWhenLoggedIn &nbsp;-&nbsp; by @wenyuanw [<samp>(d6c81)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d6c8142)
- **request**:
- unify response transformation methods and deprecate transformBackendResponse &nbsp;-&nbsp; by @soybeanjs [<samp>(f83ee)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f83eefb)
- **types**:
- move Auth and Route namespaces to separate files and clean up api.d.ts &nbsp;-&nbsp; by **Azir** [<samp>(d37ce)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d37ce04)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: add github trendshift info. &nbsp;-&nbsp; by **恕瑞玛的皇帝** [<samp>(2a0c9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2a0c9f1)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update NodeJS and pnpm version requirements in package.json and documentation &nbsp;-&nbsp; by **Junior25306** [<samp>(a5c4b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a5c4b4e)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(5cb1c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5cb1ceb)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(aeb63)</samp>](https://github.com/soybeanjs/soybean-admin/commit/aeb6369)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(e89b8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e89b86c)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(c962f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c962f7b)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(12135)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1213531)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(b041f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b041fdd)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(d567c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d567c05)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(6cbf5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/6cbf570)
- **other**:
- update the ESLint validation configuration to support more file types. &nbsp;-&nbsp; by **Azir-11** [<samp>(be8f9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/be8f915)
- **packages**:
- update Vite version to 7 in package.json and documentation. &nbsp;-&nbsp; by **Azir** [<samp>(03dd6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/03dd64c)
- add picomatch to fix scripts &nbsp;-&nbsp; by @soybeanjs [<samp>(805c3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/805c338)
- **vscode**:
- remove unused vue.server.hybridMode setting from .vscode/settings.json &nbsp;-&nbsp; by @soybeanjs [<samp>(13319)</samp>](https://github.com/soybeanjs/soybean-admin/commit/133196f)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**: format code. &nbsp;-&nbsp; by **Azir-11** [<samp>(100e0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/100e0ea)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)&nbsp;&nbsp;[![CyberShen](https://github.com/CyberShen.png?size=48)](https://github.com/CyberShen)&nbsp;&nbsp;[![Lruihao](https://github.com/Lruihao.png?size=48)](https://github.com/Lruihao)&nbsp;&nbsp;[![xiaobao0505](https://github.com/xiaobao0505.png?size=48)](https://github.com/xiaobao0505)&nbsp;&nbsp;
[CyberShen123](mailto:s.lijun@qq.com),&nbsp;[whyang](mailto:whyang9701@gmail.com),&nbsp;[HongxuanG](mailto:1359774872@qq.com),&nbsp;[Azir-11](mailto:2075125282@qq.com),&nbsp;[NicholasLD](mailto:878639947@qq.com),&nbsp;[Junior25306](mailto:dayu429@qq.com)
## [v1.3.15](https://github.com/soybeanjs/soybean-admin/compare/v1.3.14...v1.3.15) (2025-06-24)
### &nbsp;&nbsp;&nbsp;🚀 Features

View File

@@ -7,15 +7,14 @@
---
[![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)
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<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>
<div style="display: flex; gap: 12px; align-items: center;">
<a href="https://trendshift.io/repositories/7963" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7963" alt="soybeanjs%2Fsoybean-admin | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<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>
</div>
> [!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!
@@ -139,7 +138,7 @@ Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) docume
## Ecosystem
- [skyroc-admin](https://github.com/Ohh-889/skyroc-admin): SoybeanAdmin's React version implementation.
- [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).
@@ -151,8 +150,6 @@ Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) docume
- [ba](https://github.com/xiatianYa/Ba-Server): Backend service docking with soybean admin based on goFrame framework, adapted to dynamic routing, and interface authentication permissions.
- [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):A Go backend service developed based on the Gin and GORM frameworks, integrated with the example branch of Soybean Admin. It supports dynamic routing and API permission authentication.
More ecosystem please refer to [Ecosystem](https://docs.soybeanjs.cn/awesome) document.
## How to Contribute
@@ -182,21 +179,13 @@ Thanks the following people for their contributions. If you want to contribute t
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a>
---
Here are the most active contributors from the past year. Thank you all for your support, which has enabled the project's continued development.
<a href="https://openomy.com/soybeanjs/soybean-admin" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://www.openomy.com/svg?repo=soybeanjs/soybean-admin&chart=list&latestMonth=12" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</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-5.jpg" style="width:200px" />
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-4.jpg" style="width:200px" />
</div>
<!-- <div>
<p>WeChat Group</p>

View File

@@ -7,15 +7,13 @@
---
[![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)
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<div style="display: flex; gap: 12px; align-items: center;">
<a href="https://trendshift.io/repositories/7963" target="_blank"><img src="https://trendshift.io/api/badge/repositories/7963" alt="soybeanjs%2Fsoybean-admin | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<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>
</div>
<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 上给我们一个 ⭐️。您的支持是我们持续改进和增加新功能的动力!感谢您的支持!
@@ -165,7 +163,7 @@ pnpm build
## 周边生态
- [skyroc-admin](https://github.com/Ohh-889/skyroc-admin): SoybeanAdmin的React版本实现.
- [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) 开发的简单权限系统。
@@ -177,8 +175,6 @@ pnpm build
- [ba](https://github.com/xiatianYa/Ba-Server): 基于goFrame框架开发的后端服务对接soybean-admin,适配动态路由,接口鉴权限。
- [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):基于gin+gorm框架开发的go语言后端服务对接soybean-admin的example分支,适配动态路由,接口鉴权限。
更多周边生态请翻阅 [周边生态](https://docs.soybeanjs.cn/zh/awesome) 文档。
## 如何贡献
@@ -210,21 +206,13 @@ pnpm build
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a>
---
以下是近一年中活跃度较高的贡献者,感谢各位的支持,让项目得以持续发展。
<a href="https://openomy.com/soybeanjs/soybean-admin" target="_blank" style="display: block; width: 100%;" align="center">
<img src="https://www.openomy.com/svg?repo=soybeanjs/soybean-admin&chart=list&latestMonth=12" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
</a>
## 交流
`SoybeanAdmin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和 QQ 交流群,使用问题欢迎在群内提问。
<div>
<p>QQ交流群</p>
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-5.jpg" style="width:200px" />
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-4.jpg" style="width:200px" />
</div>
<!-- <div>
<p>微信群</p>

View File

@@ -1,4 +1,4 @@
import type { ProxyOptions } from 'vite';
import type { HttpProxy, ProxyOptions } from 'vite';
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
import { consola } from 'consola';
import { createServiceConfig } from '../../src/utils/service';
@@ -33,7 +33,7 @@ function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean
proxy[item.proxyPattern] = {
target: item.baseURL,
changeOrigin: true,
configure: (_proxy, options) => {
configure: (_proxy: HttpProxy.Server, options: ProxyOptions) => {
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
if (!enableLog) return;

View File

@@ -2,7 +2,7 @@ import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router';
import elegantRouter from 'elegant-router/vite';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
import { setupHtmlPlugin } from './html';
@@ -13,7 +13,7 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
vue(),
vueJsx(),
setupDevtoolsPlugin(viteEnv),
setupElegantRouter(),
elegantRouter(),
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),
progress(),

View File

@@ -1,41 +0,0 @@
import type { RouteMeta } from 'vue-router';
import ElegantVueRouter from '@elegant-router/vue/vite';
import type { RouteKey } from '@elegant-router/types';
export function setupElegantRouter() {
return ElegantVueRouter({
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
routePathTransformer(routeName, routePath) {
const key = routeName as RouteKey;
if (key === 'login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return routePath;
},
onRouteMetaGen(routeName) {
const key = routeName as RouteKey;
const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
const meta: Partial<RouteMeta> = {
title: key,
i18nKey: `route.${key}` as App.I18n.I18nKey
};
if (constantRoutes.includes(key)) {
meta.constant = true;
}
return meta;
}
});
}

37
er.config.ts Normal file
View File

@@ -0,0 +1,37 @@
import type { RouteMeta } from 'vue-router';
import { defineConfig } from 'elegant-router';
import type { RouteKey } from '@elegant-router/types';
export default defineConfig({
pageDir: ['src/views'],
layouts: {
base: 'src/layouts/base-layout/index.vue',
blank: 'src/layouts/blank-layout/index.vue'
},
getRoutePath: node => {
if (node.name === 'Login') {
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
const moduleReg = modules.join('|');
return `/login/:module(${moduleReg})?`;
}
return node.path;
},
getRouteMeta: node => {
const constantRoutes: RouteKey[] = ['Login', '403', '404', '500'];
const name = node.name as RouteKey;
const meta: Partial<RouteMeta> = {
title: name
};
if (constantRoutes.includes(name)) {
meta.constant = true;
}
return meta;
}
});

View File

@@ -1,7 +1,7 @@
{
"name": "soybean-admin",
"type": "module",
"version": "2.0.0",
"version": "1.3.15",
"description": "A fresh and elegant admin template, based on Vue3、Vite7、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite7、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
"author": {
"name": "Soybean",
@@ -54,53 +54,53 @@
"@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*",
"@sa/utils": "workspace:*",
"@vueuse/core": "14.0.0",
"@vueuse/core": "13.5.0",
"clipboard": "2.0.11",
"dayjs": "1.11.19",
"dayjs": "1.11.13",
"defu": "6.1.4",
"echarts": "6.0.0",
"echarts": "5.6.0",
"json5": "2.2.3",
"naive-ui": "2.43.1",
"naive-ui": "2.42.0",
"nprogress": "0.2.0",
"pinia": "3.0.4",
"tailwind-merge": "3.4.0",
"vue": "3.5.24",
"pinia": "3.0.3",
"tailwind-merge": "3.3.1",
"vue": "3.5.17",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.12",
"vue-router": "4.6.3"
"vue-i18n": "11.1.10",
"vue-router": "4.5.1"
},
"devDependencies": {
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.407",
"@iconify/json": "2.2.359",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.3",
"@types/node": "24.10.1",
"@soybeanjs/eslint-config": "1.7.1",
"@types/node": "24.0.15",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.5.6",
"@unocss/preset-icons": "66.5.6",
"@unocss/preset-uno": "66.5.6",
"@unocss/transformer-directives": "66.5.6",
"@unocss/transformer-variant-group": "66.5.6",
"@unocss/vite": "66.5.6",
"@vitejs/plugin-vue": "6.0.1",
"@vitejs/plugin-vue-jsx": "5.1.1",
"@unocss/eslint-config": "66.3.3",
"@unocss/preset-icons": "66.3.3",
"@unocss/preset-uno": "66.3.3",
"@unocss/transformer-directives": "66.3.3",
"@unocss/transformer-variant-group": "66.3.3",
"@unocss/vite": "66.3.3",
"@vitejs/plugin-vue": "6.0.0",
"@vitejs/plugin-vue-jsx": "5.0.1",
"consola": "3.4.2",
"eslint": "9.39.1",
"eslint-plugin-vue": "10.5.1",
"elegant-router": "1.0.4-beta.10",
"eslint": "9.31.0",
"eslint-plugin-vue": "10.3.0",
"kolorist": "1.8.0",
"sass": "1.94.0",
"simple-git-hooks": "2.13.1",
"tsx": "4.20.6",
"typescript": "5.9.3",
"unplugin-icons": "22.5.0",
"unplugin-vue-components": "30.0.0",
"vite": "7.2.2",
"sass": "1.89.2",
"simple-git-hooks": "2.13.0",
"tsx": "4.20.3",
"typescript": "5.8.3",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.8.0",
"vite": "7.0.5",
"vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.3",
"vite-plugin-vue-devtools": "7.7.7",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.1.4"
"vue-tsc": "3.0.3"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/alova",
"version": "2.0.0",
"version": "1.3.15",
"exports": {
".": "./src/index.ts",
"./fetch": "./src/fetch.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/axios",
"version": "2.0.0",
"version": "1.3.15",
"exports": {
".": "./src/index.ts"
},
@@ -11,7 +11,7 @@
},
"dependencies": {
"@sa/utils": "workspace:*",
"axios": "1.13.2",
"axios": "1.10.0",
"axios-retry": "4.5.0",
"qs": "6.14.0"
},

View File

@@ -3,7 +3,6 @@ import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } f
import axiosRetry from 'axios-retry';
import { nanoid } from '@sa/utils';
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
import { transformResponse } from './shared';
import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant';
import type {
CustomAxiosRequestConfig,
@@ -54,8 +53,6 @@ function createCommonRequest<
async response => {
const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
await transformResponse(response);
if (responseType !== 'json' || opts.isBackendSuccess(response)) {
return Promise.resolve(response);
}

View File

@@ -1,5 +1,4 @@
import type { AxiosHeaderValue, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import type { ResponseType } from './type';
export function getContentType(config: InternalAxiosRequestConfig) {
const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json';
@@ -27,53 +26,3 @@ export function isResponseJson(response: AxiosResponse) {
return responseType === 'json' || responseType === undefined;
}
export async function transformResponse(response: AxiosResponse) {
const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
if (responseType === 'json') return;
const isJson = response.headers['content-type']?.includes('application/json');
if (!isJson) return;
if (responseType === 'blob') {
await transformBlobToJson(response);
}
if (responseType === 'arrayBuffer') {
await transformArrayBufferToJson(response);
}
}
export async function transformBlobToJson(response: AxiosResponse) {
try {
let data = response.data;
if (typeof data === 'string') {
data = JSON.parse(data);
}
if (Object.prototype.toString.call(data) === '[object Blob]') {
const json = await data.text();
data = JSON.parse(json);
}
response.data = data;
} catch {}
}
export async function transformArrayBufferToJson(response: AxiosResponse) {
try {
let data = response.data;
if (typeof data === 'string') {
data = JSON.parse(data);
}
if (Object.prototype.toString.call(data) === '[object ArrayBuffer]') {
const json = new TextDecoder().decode(data);
data = JSON.parse(json);
}
response.data = data;
} catch {}
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/materials",
"version": "2.0.0",
"version": "1.3.15",
"exports": {
".": "./src/index.ts"
},

View File

@@ -95,27 +95,3 @@
.chrome-tab_dark .chrome-tab-divider {
background-color: rgba(255, 255, 255, 0.9);
}
.slider-tab {
background-color: transparent;
height: 100%;
border-bottom: 2px solid transparent;
}
.slider-tab_dark {
background-color: transparent;
}
.slider-tab:hover {
color: var(--soy-primary-color);
}
.slider-tab_active {
color: var(--soy-primary-color);
background-color: var(--soy-primary-color-opacity1);
border-bottom-color: var(--soy-primary-color);
}
.slider-tab_active_dark {
background-color: var(--soy-primary-color-opacity2);
}

View File

@@ -10,10 +10,6 @@ declare const styles: {
readonly 'chrome-tab_dark': string;
readonly 'chrome-tab-divider': string;
readonly 'svg-close': string;
readonly 'slider-tab': string;
readonly 'slider-tab_active': string;
readonly 'slider-tab_active_dark': string;
readonly 'slider-tab_dark': string;
};
export default styles;

View File

@@ -5,7 +5,6 @@ import type { PageTabMode, PageTabProps } from '../../types';
import { ACTIVE_COLOR, createTabCssVars } from './shared';
import ChromeTab from './chrome-tab.vue';
import ButtonTab from './button-tab.vue';
import SliderTab from './slider-tab.vue';
import SvgClose from './svg-close.vue';
import style from './index.module.css';
@@ -27,7 +26,7 @@ interface Emits {
const emit = defineEmits<Emits>();
const activeTabComponent = computed(() => {
const { mode, chromeClass, buttonClass, sliderClass } = props;
const { mode, chromeClass, buttonClass } = props;
const tabComponentMap = {
chrome: {
@@ -37,10 +36,6 @@ const activeTabComponent = computed(() => {
button: {
component: ButtonTab,
class: buttonClass
},
slider: {
component: SliderTab,
class: sliderClass
}
} satisfies Record<PageTabMode, { component: Component; class?: string }>;
@@ -50,7 +45,7 @@ const activeTabComponent = computed(() => {
const cssVars = computed(() => createTabCssVars(props.activeColor));
const bindProps = computed(() => {
const { chromeClass: _chromeCls, buttonClass: _btnCls, sliderClass: _sliderCls, ...rest } = props;
const { chromeClass: _chromeCls, buttonClass: _btnCls, ...rest } = props;
return rest;
});

View File

@@ -1,53 +0,0 @@
<script setup lang="ts">
import type { PageTabProps } from '../../types';
import style from './index.module.css';
defineOptions({
name: 'SliderTab'
});
defineProps<PageTabProps>();
type SlotFn = (props?: Record<string, unknown>) => any;
type Slots = {
/**
* Slot
*
* The center content of the tab
*/
default?: SlotFn;
/**
* Slot
*
* The left content of the tab
*/
prefix?: SlotFn;
/**
* Slot
*
* The right content of the tab
*/
suffix?: SlotFn;
};
defineSlots<Slots>();
</script>
<template>
<div
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-6px whitespace-nowrap px-12px py-4px"
:class="[
style['slider-tab'],
{ [style['slider-tab_dark']]: darkMode },
{ [style['slider-tab_active']]: active },
{ [style['slider-tab_active_dark']]: active && darkMode }
]"
>
<slot name="prefix"></slot>
<slot></slot>
<slot name="suffix"></slot>
</div>
</template>
<style scoped></style>

View File

@@ -239,7 +239,7 @@ export type LayoutCssVars = {
*
* @default chrome
*/
export type PageTabMode = 'button' | 'chrome' | 'slider';
export type PageTabMode = 'button' | 'chrome';
export interface PageTabProps {
/** Whether is dark mode */
@@ -262,8 +262,6 @@ export interface PageTabProps {
buttonClass?: string;
/** The class of the chrome tab */
chromeClass?: string;
/** The class of the title tab */
sliderClass?: string;
/** Whether the tab is active */
active?: boolean;
/** The color of the active tab */

View File

@@ -1,6 +1,6 @@
{
"name": "@sa/scripts",
"version": "2.0.0",
"version": "1.3.15",
"bin": {
"sa": "./bin.ts"
},
@@ -13,16 +13,15 @@
}
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.25",
"bumpp": "10.3.1",
"c12": "3.3.2",
"@soybeanjs/changelog": "0.3.24",
"bumpp": "10.2.0",
"c12": "3.1.0",
"cac": "6.7.14",
"consola": "3.4.2",
"enquirer": "2.4.1",
"execa": "9.6.0",
"kolorist": "1.8.0",
"npm-check-updates": "19.1.2",
"picomatch": "4.0.3",
"rimraf": "6.1.0"
"npm-check-updates": "18.0.1",
"rimraf": "6.0.1"
}
}

View File

@@ -1,5 +1,5 @@
import { execCommand } from '../shared';
export async function updatePkg(args: string[] = ['--deep', '-u']) {
execCommand('npx', ['npm-check-updates', ...args], { stdio: 'inherit' });
execCommand('npx', ['ncu', ...args], { stdio: 'inherit' });
}

View File

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

View File

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

3330
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -40,8 +40,7 @@ export const themeScrollModeOptions = transformRecordToOption(themeScrollModeRec
export const themeTabModeRecord: Record<UnionKey.ThemeTabMode, App.I18n.I18nKey> = {
chrome: 'theme.layout.tab.mode.chrome',
button: 'theme.layout.tab.mode.button',
slider: 'theme.layout.tab.mode.slider'
button: 'theme.layout.tab.mode.button'
};
export const themeTabModeOptions = transformRecordToOption(themeTabModeRecord);
@@ -58,6 +57,13 @@ export const themePageAnimationModeRecord: Record<UnionKey.ThemePageAnimateMode,
export const themePageAnimationModeOptions = transformRecordToOption(themePageAnimationModeRecord);
export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I18n.I18nKey> = {
close: 'theme.layout.resetCacheStrategy.close',
refresh: 'theme.layout.resetCacheStrategy.refresh'
};
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
export const DARK_CLASS = 'dark';
export const watermarkTimeFormatOptions = [

View File

@@ -18,7 +18,7 @@ defineOptions({
const appStore = useAppStore();
const themeStore = useThemeStore();
const { secondLevelMenus, childLevelMenus, isActiveFirstLevelMenuHasChildren } = provideMixMenuContext();
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = provideMixMenuContext();
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
@@ -77,9 +77,9 @@ const isTopHybridSidebarFirst = computed(() => themeStore.layout.mode === 'top-h
const isTopHybridHeaderFirst = computed(() => themeStore.layout.mode === 'top-hybrid-header-first');
const siderWidth = computed(() => getSiderAndCollapsedWidth(false));
const siderWidth = computed(() => getSiderWidth());
const siderCollapsedWidth = computed(() => getSiderAndCollapsedWidth(true));
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
function getSiderAndCollapsedWidth(isCollapsed: boolean) {
const {
@@ -104,7 +104,7 @@ function getSiderAndCollapsedWidth(isCollapsed: boolean) {
const isMixMode = isVerticalMix.value || isTopHybridSidebarFirst.value || isVerticalHybridHeaderFirst.value;
let finalWidth = isMixMode ? mixWidth : width;
if (isVerticalMix.value && appStore.mixSiderFixed && secondLevelMenus.value.length) {
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
finalWidth += mixChildMenuWidth;
}
@@ -114,6 +114,14 @@ function getSiderAndCollapsedWidth(isCollapsed: boolean) {
return finalWidth;
}
function getSiderWidth() {
return getSiderAndCollapsedWidth(false);
}
function getSiderCollapsedWidth() {
return getSiderAndCollapsedWidth(true);
}
</script>
<template>

View File

@@ -30,17 +30,15 @@ const { selectedKey } = useMenu();
/>
</Teleport>
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
<div class="h-full pt-2">
<FirstLevelMenu
:menus="firstLevelMenus"
:active-menu-key="activeFirstLevelMenuKey"
:sider-collapse="appStore.siderCollapse"
:dark-mode="themeStore.darkMode"
:theme-color="themeStore.themeColor"
@select="handleSelectFirstLevelMenu"
@toggle-sider-collapse="appStore.toggleSiderCollapse"
/>
</div>
<FirstLevelMenu
:menus="firstLevelMenus"
:active-menu-key="activeFirstLevelMenuKey"
:sider-collapse="appStore.siderCollapse"
:dark-mode="themeStore.darkMode"
:theme-color="themeStore.themeColor"
@select="handleSelectFirstLevelMenu"
@toggle-sider-collapse="appStore.toggleSiderCollapse"
/>
</Teleport>
</template>

View File

@@ -26,8 +26,6 @@ const tabRef = ref<HTMLElement>();
const isPCFlag = isPC();
const TAB_DATA_ID = 'data-tab-id';
const MIDDLE_MOUSE_BUTTON = 1;
const RIGHT_MOUSE_BUTTON = 2;
type TabNamedNodeMap = NamedNodeMap & {
[TAB_DATA_ID]: Attr;
@@ -86,26 +84,6 @@ function handleCloseTab(tab: App.Global.Tab) {
tabStore.removeTab(tab.id);
}
function handleMousedown(e: MouseEvent, tab: App.Global.Tab) {
const isMiddleClick = e.button === MIDDLE_MOUSE_BUTTON;
if (!isMiddleClick || !themeStore.tab.closeTabByMiddleClick) {
return;
}
if (tabStore.isTabRetain(tab.id)) {
return;
}
e.preventDefault();
handleCloseTab(tab);
}
function switchTab(e: MouseEvent, tab: App.Global.Tab) {
if ([MIDDLE_MOUSE_BUTTON, RIGHT_MOUSE_BUTTON].includes(e.button)) return;
tabStore.switchRouteByTab(tab);
}
async function refresh() {
appStore.reloadPage(500);
}
@@ -191,9 +169,7 @@ init();
<div
ref="tabRef"
class="h-full flex pr-18px"
:class="[
themeStore.tab.mode === 'chrome' || themeStore.tab.mode === 'slider' ? 'items-end' : 'items-center gap-12px'
]"
:class="[themeStore.tab.mode === 'chrome' ? 'items-end' : 'items-center gap-12px']"
>
<PageTab
v-for="tab in tabStore.tabs"
@@ -204,8 +180,7 @@ init();
:active="tab.id === tabStore.activeTabId"
:active-color="themeStore.themeColor"
:closable="!tabStore.isTabRetain(tab.id)"
@pointerdown="switchTab($event, tab)"
@mousedown="handleMousedown($event, tab)"
@pointerdown="tabStore.switchRouteByTab(tab)"
@close="handleCloseTab(tab)"
@contextmenu="handleContextMenu($event, tab.id)"
>

View File

@@ -6,7 +6,6 @@ import AppearanceSettings from './modules/appearance/index.vue';
import LayoutSettings from './modules/layout/index.vue';
import GeneralSettings from './modules/general/index.vue';
import ConfigOperation from './modules/config-operation.vue';
import PresetSettings from './modules/preset/index.vue';
defineOptions({
name: 'ThemeDrawer'
@@ -34,7 +33,6 @@ const drawerWidth = computed(() => {
<NTab name="appearance" :tab="$t('theme.tabs.appearance')"></NTab>
<NTab name="layout" :tab="$t('theme.tabs.layout')"></NTab>
<NTab name="general" :tab="$t('theme.tabs.general')"></NTab>
<NTab name="preset" :tab="$t('theme.tabs.preset')"></NTab>
</NTabs>
<div class="min-h-400px">
@@ -42,7 +40,6 @@ const drawerWidth = computed(() => {
<AppearanceSettings v-if="activeTab === 'appearance'" />
<LayoutSettings v-else-if="activeTab === 'layout'" />
<GeneralSettings v-else-if="activeTab === 'general'" />
<PresetSettings v-else-if="activeTab === 'preset'" />
</KeepAlive>
</div>

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import ThemeSchema from './modules/theme-schema.vue';
import ThemeColor from './modules/theme-color.vue';
import ThemeRadius from './modules/theme-radius.vue';
defineOptions({
name: 'AppearanceSettings'
@@ -12,7 +11,6 @@ defineOptions({
<div class="flex-col-stretch gap-16px">
<ThemeSchema />
<ThemeColor />
<ThemeRadius />
</div>
</template>

View File

@@ -1,22 +0,0 @@
<script setup lang="ts">
import { useThemeStore } from '@/store/modules/theme';
import { $t } from '@/locales';
import SettingItem from '../../../components/setting-item.vue';
defineOptions({
name: 'ThemeRadius'
});
const themeStore = useThemeStore();
</script>
<template>
<NDivider>{{ $t('theme.appearance.themeRadius.title') }}</NDivider>
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
<SettingItem key="1" :label="$t('theme.appearance.themeRadius.title')">
<NInputNumber v-model:value="themeStore.themeRadius" size="small" :step="1" :min="0" :max="16" class="w-120px" />
</SettingItem>
</TransitionGroup>
</template>
<style scoped></style>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { themeTabModeOptions } from '@/constants/app';
import { resetCacheStrategyOptions, themeTabModeOptions } from '@/constants/app';
import { useThemeStore } from '@/store/modules/theme';
import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
@@ -15,6 +15,14 @@ const themeStore = useThemeStore();
<template>
<NDivider>{{ $t('theme.layout.tab.title') }}</NDivider>
<TransitionGroup tag="div" name="setting-list" class="flex-col-stretch gap-12px">
<SettingItem key="0" :label="$t('theme.layout.resetCacheStrategy.title')">
<NSelect
v-model:value="themeStore.resetCacheStrategy"
:options="translateOptions(resetCacheStrategyOptions)"
size="small"
class="w-120px"
/>
</SettingItem>
<SettingItem key="1" :label="$t('theme.layout.tab.visible')">
<NSwitch v-model:value="themeStore.tab.visible" />
</SettingItem>
@@ -35,12 +43,6 @@ const themeStore = useThemeStore();
class="w-120px"
/>
</SettingItem>
<SettingItem v-if="themeStore.tab.visible" key="5" :label="$t('theme.layout.tab.closeByMiddleClick')">
<template #suffix>
<IconTooltip :desc="$t('theme.layout.tab.closeByMiddleClickTip')" />
</template>
<NSwitch v-model:value="themeStore.tab.closeTabByMiddleClick" />
</SettingItem>
</TransitionGroup>
</template>

View File

@@ -1,15 +0,0 @@
<script setup lang="ts">
import ThemePreset from './modules/theme-preset.vue';
defineOptions({
name: 'PresetSettings'
});
</script>
<template>
<div class="flex-col-stretch gap-16px">
<ThemePreset />
</div>
</template>
<style scoped></style>

View File

@@ -1,148 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useThemeStore } from '@/store/modules/theme';
import { $t } from '@/locales';
defineOptions({
name: 'ThemePreset'
});
type ThemePreset = Pick<
App.Theme.ThemeSetting,
| 'themeScheme'
| 'grayscale'
| 'colourWeakness'
| 'recommendColor'
| 'themeColor'
| 'themeRadius'
| 'otherColor'
| 'isInfoFollowPrimary'
| 'layout'
| 'page'
| 'header'
| 'tab'
| 'fixedHeaderAndTab'
| 'sider'
| 'footer'
| 'watermark'
| 'tokens'
> & {
name: string;
desc: string;
i18nkey?: string;
version: string;
};
const presetModules = import.meta.glob('@/theme/preset/*.json', { eager: true, import: 'default' });
const themeStore = useThemeStore();
// Extract preset data
const presets = computed(() =>
Object.entries(presetModules)
.map(([path, presetData]) => {
const fileName = path.split('/').pop()?.replace('.json', '') || '';
return {
id: fileName,
...(presetData as ThemePreset)
};
})
.sort((a, b) => {
if (a.name === 'default') return -1;
if (b.name === 'default') return 1;
return a.name.localeCompare(b.name);
})
);
const getPresetName = (preset: ThemePreset): string => {
if (!preset.i18nkey) return preset.name;
try {
const key = `${preset.i18nkey}.name` as App.I18n.I18nKey;
const translated = $t(key);
return translated !== key ? translated : preset.name;
} catch {
return preset.name;
}
};
const getPresetDesc = (preset: ThemePreset): string => {
if (!preset.i18nkey) return preset.desc;
try {
const key = `${preset.i18nkey}.desc` as App.I18n.I18nKey;
const translated = $t(key);
return translated !== key ? translated : preset.desc;
} catch {
return preset.desc;
}
};
const applyPreset = ({ themeScheme, grayscale, colourWeakness, layout, watermark, ...rest }: ThemePreset): void => {
themeStore.setThemeScheme(themeScheme);
themeStore.setGrayscale(grayscale);
themeStore.setColourWeakness(colourWeakness);
themeStore.setThemeLayout(layout.mode);
themeStore.setWatermarkEnableUserName(watermark.enableUserName);
themeStore.setWatermarkEnableTime(watermark.enableTime);
Object.assign(themeStore, {
...rest,
layout: { ...themeStore.layout, scrollMode: layout.scrollMode },
page: { ...rest.page },
header: { ...rest.header },
tab: { ...rest.tab },
sider: { ...rest.sider },
footer: { ...rest.footer },
watermark: { ...watermark },
tokens: { ...rest.tokens }
});
window.$message?.success($t('theme.appearance.preset.applySuccess'));
};
</script>
<template>
<NDivider>{{ $t('theme.appearance.preset.title') }}</NDivider>
<div class="flex flex-col gap-3">
<div
v-for="preset in presets"
:key="preset.id"
class="border border-primary/10 rounded-lg border-solid bg-white/5 p-3 backdrop-blur-10 transition-all duration-300 hover:(shadow-md -translate-y-0.5)"
>
<div class="mb-2 flex items-center justify-between">
<div class="min-w-0 w-full flex flex-1 items-center justify-between gap-2">
<h5 class="m-0 truncate text-sm text-primary font-600">
{{ getPresetName(preset) }}
</h5>
<NBadge :value="`v${preset.version}`" type="info" size="small" class="flex-shrink-0 opacity-80" />
</div>
<NButton type="primary" size="tiny" ghost round class="ml-2 flex-shrink-0" @click="applyPreset(preset)">
{{ $t('theme.appearance.preset.apply') }}
</NButton>
</div>
<p class="line-clamp-2 mb-3 text-xs text-gray-500 leading-4">{{ getPresetDesc(preset) }}</p>
<div class="flex items-center justify-between">
<div class="flex gap-1">
<div
v-for="(color, key) in { primary: preset.themeColor, ...preset.otherColor }"
:key="key"
class="h-3 w-3 cursor-pointer border border-white/30 rounded-full transition-transform hover:scale-110"
:style="{ backgroundColor: color }"
:class="{ 'ring-1 ring-primary/50': key === 'primary' }"
:title="key"
/>
</div>
<div class="flex items-center gap-1">
<div class="text-lg">
{{ preset.themeScheme === 'dark' ? '🌙' : '☀️' }}
</div>
<div class="text-lg">
{{ preset.grayscale ? '🎨' : '' }}
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -62,8 +62,7 @@ const local: App.I18n.Schema = {
tabs: {
appearance: 'Appearance',
layout: 'Layout',
general: 'General',
preset: 'Preset'
general: 'General'
},
appearance: {
themeSchema: {
@@ -83,32 +82,8 @@ const local: App.I18n.Schema = {
error: 'Error',
followPrimary: 'Follow Primary'
},
themeRadius: {
title: 'Theme Radius'
},
recommendColor: 'Apply Recommended Color Algorithm',
recommendColorDesc: 'The recommended color algorithm refers to',
preset: {
title: 'Theme Presets',
apply: 'Apply',
applySuccess: 'Preset applied successfully',
default: {
name: 'Default Preset',
desc: 'Default theme preset with balanced settings'
},
dark: {
name: 'Dark Preset',
desc: 'Dark theme preset for night time usage'
},
compact: {
name: 'Compact Preset',
desc: 'Compact layout preset for small screens'
},
azir: {
name: "Azir's Preset",
desc: 'It is a cold and elegant preset that Azir likes'
}
}
recommendColorDesc: 'The recommended color algorithm refers to'
},
layout: {
layoutMode: {
@@ -138,12 +113,9 @@ const local: App.I18n.Schema = {
height: 'Tab Height',
mode: {
title: 'Tab Mode',
slider: 'Slider',
chrome: 'Chrome',
button: 'Button'
},
closeByMiddleClick: 'Close Tab by Middle Click',
closeByMiddleClickTip: 'Enable closing tabs by clicking with the middle mouse button'
}
},
header: {
title: 'Header Settings',
@@ -191,6 +163,11 @@ const local: App.I18n.Schema = {
}
},
fixedHeaderAndTab: 'Fixed Header And Tab'
},
resetCacheStrategy: {
title: 'Reset Cache Strategy',
close: 'Close Page',
refresh: 'Refresh Page'
}
},
general: {

View File

@@ -62,8 +62,7 @@ const local: App.I18n.Schema = {
tabs: {
appearance: '外观',
layout: '布局',
general: '通用',
preset: '预设'
general: '通用'
},
appearance: {
themeSchema: {
@@ -83,32 +82,8 @@ const local: App.I18n.Schema = {
error: '错误色',
followPrimary: '跟随主色'
},
themeRadius: {
title: '主题圆角'
},
recommendColor: '应用推荐算法的颜色',
recommendColorDesc: '推荐颜色的算法参照',
preset: {
title: '主题预设',
apply: '应用',
applySuccess: '预设应用成功',
default: {
name: '默认预设',
desc: 'Soybean 默认主题预设'
},
dark: {
name: '暗色预设',
desc: '适用于夜间使用的暗色主题预设'
},
compact: {
name: '紧凑型',
desc: '适用于小屏幕的紧凑布局预设'
},
azir: {
name: 'Azir的预设',
desc: '是 Azir 比较喜欢的莫兰迪色系冷淡风'
}
}
recommendColorDesc: '推荐颜色的算法参照'
},
layout: {
layoutMode: {
@@ -135,12 +110,9 @@ const local: App.I18n.Schema = {
height: '标签栏高度',
mode: {
title: '标签栏风格',
slider: '滑块风格',
chrome: '谷歌风格',
button: '按钮风格'
},
closeByMiddleClick: '鼠标中键关闭标签页',
closeByMiddleClickTip: '启用后可以使用鼠标中键点击标签页进行关闭'
}
},
header: {
title: '头部设置',
@@ -164,7 +136,7 @@ const local: App.I18n.Schema = {
visible: '显示底部',
fixed: '固定底部',
height: '底部高度',
right: '底部右'
right: '底部右'
},
content: {
title: '内容区域设置',
@@ -188,6 +160,11 @@ const local: App.I18n.Schema = {
}
},
fixedHeaderAndTab: '固定头部和标签栏'
},
resetCacheStrategy: {
title: '重置缓存策略',
close: '关闭页面',
refresh: '刷新页面'
}
},
general: {

View File

@@ -0,0 +1,31 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteFileKey, RouteLayoutKey, RawRouteComponent } from "@elegant-router/types";
export const layouts: Record<RouteLayoutKey, RawRouteComponent> = {
base: () => import("@/layouts/base-layout/index.vue"),
blank: () => import("@/layouts/blank-layout/index.vue"),
};
export const views: Record<RouteFileKey, RawRouteComponent> = {
403: () => import("@/views/(builtin)/403/index.vue"),
404: () => import("@/views/(builtin)/404/index.vue"),
500: () => import("@/views/(builtin)/500/index.vue"),
Home: () => import("@/views/home/index.vue"),
IframeUrl: () => import("@/views/(builtin)/iframe/[url].vue"),
Login: () => import("@/views/(builtin)/login/index.vue"),
ManageApi: () => import("@/views/manage/api/index.vue"),
ManageDictionary: () => import("@/views/manage/dictionary/index.vue"),
ManageMenu: () => import("@/views/manage/menu/index.vue"),
ManageOrganization: () => import("@/views/manage/organization/index.vue"),
ManagePermission: () => import("@/views/manage/permission/index.vue"),
ManageRole: () => import("@/views/manage/role/index.vue"),
ManageRoute: () => import("@/views/manage/route/index.vue"),
ManageUser: () => import("@/views/manage/user/index.vue"),
Wip: () => import("@/views/(builtin)/wip/index.vue"),
};

View File

@@ -0,0 +1,166 @@
/* eslint-disable */
/* prettier-ignore */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { AutoRouterRoute } from '@elegant-router/types';
export const routes: AutoRouterRoute[] = [
{
name: 'Root',
path: '/',
redirect: '/home',
meta: {
title: "Root"
},
},
{
name: 'NotFound',
path: '/:pathMatch(.*)*',
layout: 'base',
component: '404',
meta: {
title: "NotFound"
},
},
{
name: '403',
path: '/403',
layout: 'base',
component: '403',
meta: {
title: "403",
constant: true
},
},
{
name: '404',
path: '/404',
layout: 'base',
component: '404',
meta: {
title: "404",
constant: true
},
},
{
name: '500',
path: '/500',
layout: 'base',
component: '500',
meta: {
title: "500",
constant: true
},
},
{
name: 'Home',
path: '/home',
layout: 'base',
component: 'Home',
meta: {
title: "Home"
},
},
{
name: 'IframeUrl',
path: '/iframe/:url',
layout: 'base',
component: 'IframeUrl',
meta: {
title: "IframeUrl"
}
},
{
name: 'Login',
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
layout: 'base',
component: 'Login',
meta: {
title: "Login",
constant: true
},
},
{
name: 'ManageApi',
path: '/manage/api',
layout: 'base',
component: 'ManageApi',
meta: {
title: "ManageApi"
}
},
{
name: 'ManageDictionary',
path: '/manage/dictionary',
layout: 'base',
component: 'ManageDictionary',
meta: {
title: "ManageDictionary"
}
},
{
name: 'ManageMenu',
path: '/manage/menu',
layout: 'base',
component: 'ManageMenu',
meta: {
title: "ManageMenu"
},
},
{
name: 'ManageOrganization',
path: '/manage/organization',
layout: 'base',
component: 'ManageOrganization',
meta: {
title: "ManageOrganization"
}
},
{
name: 'ManagePermission',
path: '/manage/permission',
layout: 'base',
component: 'ManagePermission',
meta: {
title: "ManagePermission"
}
},
{
name: 'ManageRole',
path: '/manage/role',
layout: 'base',
component: 'ManageRole',
meta: {
title: "ManageRole"
},
},
{
name: 'ManageRoute',
path: '/manage/route',
layout: 'base',
component: 'ManageRoute',
meta: {
title: "ManageRoute"
}
},
{
name: 'ManageUser',
path: '/manage/user',
layout: 'base',
component: 'ManageUser',
meta: {
title: "ManageUser"
},
},
{
name: 'Wip',
path: '/wip',
layout: 'base',
component: 'Wip',
meta: {
title: "Wip"
}
}
];

View File

@@ -0,0 +1,32 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteKey, RoutePathMap } from '@elegant-router/types';
const routePathMap: RoutePathMap = {
"Root": "/",
"NotFound": "/:pathMatch(.*)*",
"403": "/403",
"404": "/404",
"500": "/500",
"Home": "/home",
"IframeUrl": "/iframe/:url",
"Login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"ManageApi": "/manage/api",
"ManageDictionary": "/manage/dictionary",
"ManageMenu": "/manage/menu",
"ManageOrganization": "/manage/organization",
"ManagePermission": "/manage/permission",
"ManageRole": "/manage/role",
"ManageRoute": "/manage/route",
"ManageUser": "/manage/user",
"Wip": "/wip",
};
export function getRoutePath(key: RouteKey) {
return routePathMap[key];
}

View File

@@ -0,0 +1,70 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteRecordRaw } from 'vue-router';
import type {
AutoRouterRedirect,
AutoRouterRoute,
AutoRouterSingleView,
RawRouteComponent,
RouteFileKey,
RouteLayoutKey
} from '@elegant-router/types';
export function transformToVueRoutes(
routes: AutoRouterRoute[],
layouts: Record<RouteLayoutKey, RawRouteComponent>,
views: Record<RouteFileKey, RawRouteComponent>
) {
const { redirects, groupedRoutes } = getFormattedRoutes(routes);
const vueRoutes: RouteRecordRaw[] = [...redirects];
groupedRoutes.forEach((items, layout) => {
const layoutRoute: RouteRecordRaw = {
path: `/${layout}-layout`,
component: layouts[layout],
children: items.map(item => {
const { layout: _, component, ...rest } = item;
return {
component: views[component],
...rest
};
})
};
vueRoutes.push(layoutRoute);
});
return vueRoutes;
}
function getFormattedRoutes(routes: AutoRouterRoute[]) {
const groupedRoutes = new Map<RouteLayoutKey, AutoRouterSingleView[]>();
const redirects: AutoRouterRedirect[] = [];
routes.forEach(route => {
if (isAutoRouterRedirect(route)) {
redirects.push(route);
return;
}
const items = groupedRoutes.get(route.layout) || [];
items.push(route);
groupedRoutes.set(route.layout, items);
});
return {
redirects,
groupedRoutes
};
}
function isAutoRouterRedirect(route: AutoRouterRoute): route is AutoRouterRedirect {
return 'redirect' in route;
}

View File

@@ -1,24 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteComponent } from "vue-router";
import type { LastLevelRouteKey, RouteLayout } from "@elegant-router/types";
import BaseLayout from "@/layouts/base-layout/index.vue";
import BlankLayout from "@/layouts/blank-layout/index.vue";
export const layouts: Record<RouteLayout, RouteComponent | (() => Promise<RouteComponent>)> = {
base: BaseLayout,
blank: BlankLayout,
};
export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<RouteComponent>)> = {
403: () => import("@/views/_builtin/403/index.vue"),
404: () => import("@/views/_builtin/404/index.vue"),
500: () => import("@/views/_builtin/500/index.vue"),
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
login: () => import("@/views/_builtin/login/index.vue"),
home: () => import("@/views/home/index.vue"),
};

View File

@@ -1,78 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { GeneratedRoute } from '@elegant-router/types';
export const generatedRoutes: GeneratedRoute[] = [
{
name: '403',
path: '/403',
component: 'layout.blank$view.403',
meta: {
title: '403',
i18nKey: 'route.403',
constant: true,
hideInMenu: true
}
},
{
name: '404',
path: '/404',
component: 'layout.blank$view.404',
meta: {
title: '404',
i18nKey: 'route.404',
constant: true,
hideInMenu: true
}
},
{
name: '500',
path: '/500',
component: 'layout.blank$view.500',
meta: {
title: '500',
i18nKey: 'route.500',
constant: true,
hideInMenu: true
}
},
{
name: 'home',
path: '/home',
component: 'layout.base$view.home',
meta: {
title: 'home',
i18nKey: 'route.home',
icon: 'mdi:monitor-dashboard',
order: 1
}
},
{
name: 'iframe-page',
path: '/iframe-page/:url',
component: 'layout.base$view.iframe-page',
props: true,
meta: {
title: 'iframe-page',
i18nKey: 'route.iframe-page',
constant: true,
hideInMenu: true,
keepAlive: true
}
},
{
name: 'login',
path: '/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?',
component: 'layout.blank$view.login',
props: true,
meta: {
title: 'login',
i18nKey: 'route.login',
constant: true,
hideInMenu: true
}
}
];

View File

@@ -1,192 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
import type { RouteRecordRaw, RouteComponent } from 'vue-router';
import type { ElegantConstRoute } from '@elegant-router/vue';
import type { RouteMap, RouteKey, RoutePath } from '@elegant-router/types';
/**
* transform elegant const routes to vue routes
* @param routes elegant const routes
* @param layouts layout components
* @param views view components
*/
export function transformElegantRoutesToVueRoutes(
routes: ElegantConstRoute[],
layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
) {
return routes.flatMap(route => transformElegantRouteToVueRoute(route, layouts, views));
}
/**
* transform elegant route to vue route
* @param route elegant const route
* @param layouts layout components
* @param views view components
*/
function transformElegantRouteToVueRoute(
route: ElegantConstRoute,
layouts: Record<string, RouteComponent | (() => Promise<RouteComponent>)>,
views: Record<string, RouteComponent | (() => Promise<RouteComponent>)>
) {
const LAYOUT_PREFIX = 'layout.';
const VIEW_PREFIX = 'view.';
const ROUTE_DEGREE_SPLITTER = '_';
const FIRST_LEVEL_ROUTE_COMPONENT_SPLIT = '$';
function isLayout(component: string) {
return component.startsWith(LAYOUT_PREFIX);
}
function getLayoutName(component: string) {
const layout = component.replace(LAYOUT_PREFIX, '');
if(!layouts[layout]) {
throw new Error(`Layout component "${layout}" not found`);
}
return layout;
}
function isView(component: string) {
return component.startsWith(VIEW_PREFIX);
}
function getViewName(component: string) {
const view = component.replace(VIEW_PREFIX, '');
if(!views[view]) {
throw new Error(`View component "${view}" not found`);
}
return view;
}
function isFirstLevelRoute(item: ElegantConstRoute) {
return !item.name.includes(ROUTE_DEGREE_SPLITTER);
}
function isSingleLevelRoute(item: ElegantConstRoute) {
return isFirstLevelRoute(item) && !item.children?.length;
}
function getSingleLevelRouteComponent(component: string) {
const [layout, view] = component.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT);
return {
layout: getLayoutName(layout),
view: getViewName(view)
};
}
const vueRoutes: RouteRecordRaw[] = [];
// add props: true to route
if (route.path.includes(':') && !route.props) {
route.props = true;
}
const { name, path, component, children, ...rest } = route;
const vueRoute = { name, path, ...rest } as RouteRecordRaw;
try {
if (component) {
if (isSingleLevelRoute(route)) {
const { layout, view } = getSingleLevelRouteComponent(component);
const singleLevelRoute: RouteRecordRaw = {
path,
component: layouts[layout],
meta: {
title: route.meta?.title || ''
},
children: [
{
name,
path: '',
component: views[view],
...rest
} as RouteRecordRaw
]
};
return [singleLevelRoute];
}
if (isLayout(component)) {
const layoutName = getLayoutName(component);
vueRoute.component = layouts[layoutName];
}
if (isView(component)) {
const viewName = getViewName(component);
vueRoute.component = views[viewName];
}
}
} catch (error: any) {
console.error(`Error transforming route "${route.name}": ${error.toString()}`);
return [];
}
// add redirect to child
if (children?.length && !vueRoute.redirect) {
vueRoute.redirect = {
name: children[0].name
};
}
if (children?.length) {
const childRoutes = children.flatMap(child => transformElegantRouteToVueRoute(child, layouts, views));
if(isFirstLevelRoute(route)) {
vueRoute.children = childRoutes;
} else {
vueRoutes.push(...childRoutes);
}
}
vueRoutes.unshift(vueRoute);
return vueRoutes;
}
/**
* map of route name and route path
*/
const routeMap: RouteMap = {
"root": "/",
"not-found": "/:pathMatch(.*)*",
"403": "/403",
"404": "/404",
"500": "/500",
"home": "/home",
"iframe-page": "/iframe-page/:url",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"
};
/**
* get route path by route name
* @param name route name
*/
export function getRoutePath<T extends RouteKey>(name: T) {
return routeMap[name];
}
/**
* get route name by route path
* @param path route path
*/
export function getRouteName(path: RoutePath) {
const routeEntries = Object.entries(routeMap) as [RouteKey, RoutePath][];
const routeName: RouteKey | null = routeEntries.find(([, routePath]) => routePath === path)?.[0] || null;
return routeName;
}

View File

@@ -1,12 +1,6 @@
import type { App } from 'vue';
import {
type RouterHistory,
createMemoryHistory,
createRouter,
createWebHashHistory,
createWebHistory
} from 'vue-router';
import { createBuiltinVueRoutes } from './routes/builtin';
import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
import type { RouterHistory } from 'vue-router';
import { createRouterGuard } from './guard';
const { VITE_ROUTER_HISTORY_MODE = 'history', VITE_BASE_URL } = import.meta.env;
@@ -19,7 +13,7 @@ const historyCreatorMap: Record<Env.RouterHistoryMode, (base?: string) => Router
export const router = createRouter({
history: historyCreatorMap[VITE_ROUTER_HISTORY_MODE](VITE_BASE_URL),
routes: createBuiltinVueRoutes()
routes: []
});
/** Setup Vue Router */

View File

@@ -1,31 +0,0 @@
import type { CustomRoute } from '@elegant-router/types';
import { layouts, views } from '../elegant/imports';
import { getRoutePath, transformElegantRoutesToVueRoutes } from '../elegant/transform';
export const ROOT_ROUTE: CustomRoute = {
name: 'root',
path: '/',
redirect: getRoutePath(import.meta.env.VITE_ROUTE_HOME) || '/home',
meta: {
title: 'root',
constant: true
}
};
const NOT_FOUND_ROUTE: CustomRoute = {
name: 'not-found',
path: '/:pathMatch(.*)*',
component: 'layout.blank$view.404',
meta: {
title: 'not-found',
constant: true
}
};
/** builtin routes, it must be constant and setup in vue-router */
const builtinRoutes: CustomRoute[] = [ROOT_ROUTE, NOT_FOUND_ROUTE];
/** create builtin vue routes */
export function createBuiltinVueRoutes() {
return transformElegantRoutesToVueRoutes(builtinRoutes, layouts, views);
}

View File

@@ -1,22 +1,14 @@
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
import { generatedRoutes } from '../elegant/routes';
import { layouts, views } from '../elegant/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
/**
* custom routes
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [];
import type { AutoRouterRoute } from '@elegant-router/types';
import { routes } from '../_generated/routes';
import { layouts, views } from '../_generated/imports';
import { transformToVueRoutes } from '../_generated/transformer';
/** create routes when the auth route mode is static */
export function createStaticRoutes() {
const constantRoutes: ElegantRoute[] = [];
const constantRoutes: AutoRouterRoute[] = [];
const authRoutes: AutoRouterRoute[] = [];
const authRoutes: ElegantRoute[] = [];
[...customRoutes, ...generatedRoutes].forEach(item => {
routes.forEach(item => {
if (item.meta?.constant) {
constantRoutes.push(item);
} else {
@@ -33,8 +25,8 @@ export function createStaticRoutes() {
/**
* Get auth vue routes
*
* @param routes Elegant routes
* @param authRoutes Elegant routes
*/
export function getAuthVueRoutes(routes: ElegantConstRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views);
export function getAuthVueRoutes(authRoutes: AutoRouterRoute[]) {
return transformToVueRoutes(authRoutes, layouts, views);
}

View File

@@ -46,7 +46,10 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
});
setReloadFlag(true);
routeStore.resetRouteCache();
if (themeStore.resetCacheStrategy === 'refresh') {
routeStore.resetRouteCache();
}
}
const locale = ref<App.I18n.LangType>(localStg.get('lang') || 'zh-CN');

View File

@@ -112,8 +112,10 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
await switchRouteByTab(nextTab);
}
// reset route cache
routeStore.resetRouteCache(removedTabRouteKey);
// reset route cache if cache strategy is close
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(removedTabRouteKey);
}
}
/** remove active tab */
@@ -145,8 +147,10 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
const routeKeysToReset: RouteKey[] = [];
for (const tab of tabsToRemove) {
routeKeysToReset.push(tab.routeKey);
if (themeStore.resetCacheStrategy === 'close') {
for (const tab of tabsToRemove) {
routeKeysToReset.push(tab.routeKey);
}
}
const removedTabsIds = tabsToRemove.map(tab => tab.id);

View File

@@ -53,7 +53,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
});
/** Naive theme */
const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value));
const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value.recommendColor));
/**
* Settings json

View File

@@ -236,23 +236,22 @@ function getNaiveThemeColors(colors: App.Theme.ThemeColor, recommended = false)
/**
* Get naive theme
*
* @param settings Theme settings object.
* @param settings.recommendColor Whether to use recommended color palette.
* @param settings.themeRadius Border radius to use in the theme (in px).
* @param colors Theme colors
* @param [recommended=false] Use recommended color. Default is `false`
*/
export function getNaiveTheme(colors: App.Theme.ThemeColor, settings: App.Theme.ThemeSetting) {
export function getNaiveTheme(colors: App.Theme.ThemeColor, recommended = false) {
const { primary: colorLoading } = colors;
const theme: GlobalThemeOverrides = {
common: {
...getNaiveThemeColors(colors, settings.recommendColor),
borderRadius: `${settings.themeRadius}px`
...getNaiveThemeColors(colors, recommended),
borderRadius: '6px'
},
LoadingBar: {
colorLoading
},
Tag: {
borderRadius: `${settings.themeRadius}px`
borderRadius: '6px'
}
};

View File

@@ -6,7 +6,6 @@ html,
body,
#app {
height: 100%;
text-autospace: normal;
}
html {

View File

@@ -1,14 +1,7 @@
@mixin scrollbar($size: 7px, $color: rgba(0, 0, 0, 0.5), $dark-color: rgba(255, 255, 255, 0.5)) {
@mixin scrollbar($size: 7px, $color: rgba(0, 0, 0, 0.5)) {
scrollbar-width: thin;
scrollbar-color: $color transparent;
// Dark theme override
.dark & {
// guide safari use light color scrollbar
color-scheme: dark;
scrollbar-color: $dark-color transparent;
}
&::-webkit-scrollbar-thumb {
background-color: $color;
border-radius: $size;

View File

@@ -1,91 +0,0 @@
{
"name": "Azir's Preset",
"desc": "It is a cold and elegant preset that Azir likes",
"i18nkey": "theme.appearance.preset.azir",
"version": "1.0.0",
"themeScheme": "light",
"grayscale": false,
"colourWeakness": false,
"recommendColor": true,
"themeColor": "#78a878",
"otherColor": {
"info": "#89b989",
"success": "#99c299",
"warning": "#d4bb9d",
"error": "#c49a9a"
},
"themeRadius": 6,
"isInfoFollowPrimary": true,
"layout": {
"mode": "vertical-mix",
"scrollMode": "wrapper"
},
"page": {
"animate": true,
"animateMode": "zoom-fade"
},
"header": {
"height": 64,
"breadcrumb": {
"visible": true,
"showIcon": true
},
"multilingual": {
"visible": true
},
"globalSearch": {
"visible": true
}
},
"tab": {
"visible": true,
"cache": true,
"height": 48,
"mode": "chrome",
"closeTabByMiddleClick": false
},
"fixedHeaderAndTab": true,
"sider": {
"inverted": false,
"width": 220,
"collapsedWidth": 64,
"mixWidth": 90,
"mixCollapsedWidth": 64,
"mixChildMenuWidth": 200
},
"footer": {
"visible": true,
"fixed": true,
"height": 56,
"right": true
},
"watermark": {
"visible": false,
"text": "SoybeanAdmin",
"enableUserName": false,
"enableTime": true,
"timeFormat": "YYYY-MM-DD HH:mm:ss"
},
"tokens": {
"light": {
"colors": {
"container": "rgb(255, 255, 255)",
"layout": "rgb(247, 250, 252)",
"inverted": "rgb(0, 20, 40)",
"base-text": "rgb(31, 31, 31)"
},
"boxShadow": {
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
}
},
"dark": {
"colors": {
"container": "rgb(28, 28, 28)",
"layout": "rgb(18, 18, 18)",
"base-text": "rgb(224, 224, 224)"
}
}
}
}

View File

@@ -1,91 +0,0 @@
{
"name": "Compact Preset",
"desc": "Compact layout preset for small screens",
"i18nkey": "theme.appearance.preset.compact",
"version": "1.0.0",
"themeScheme": "light",
"grayscale": false,
"colourWeakness": false,
"recommendColor": false,
"themeColor": "#646cff",
"otherColor": {
"info": "#2080f0",
"success": "#52c41a",
"warning": "#faad14",
"error": "#f5222d"
},
"themeRadius": 6,
"isInfoFollowPrimary": true,
"layout": {
"mode": "vertical",
"scrollMode": "content"
},
"page": {
"animate": true,
"animateMode": "fade-slide"
},
"header": {
"height": 48,
"breadcrumb": {
"visible": true,
"showIcon": true
},
"multilingual": {
"visible": false
},
"globalSearch": {
"visible": false
}
},
"tab": {
"visible": true,
"cache": true,
"height": 36,
"mode": "button",
"closeTabByMiddleClick": false
},
"fixedHeaderAndTab": true,
"sider": {
"inverted": false,
"width": 180,
"collapsedWidth": 48,
"mixWidth": 80,
"mixCollapsedWidth": 48,
"mixChildMenuWidth": 180
},
"footer": {
"visible": false,
"fixed": false,
"height": 40,
"right": true
},
"watermark": {
"visible": false,
"text": "SoybeanAdmin",
"enableUserName": false,
"enableTime": false,
"timeFormat": "YYYY-MM-DD HH:mm"
},
"tokens": {
"light": {
"colors": {
"container": "rgb(255, 255, 255)",
"layout": "rgb(247, 250, 252)",
"inverted": "rgb(0, 20, 40)",
"base-text": "rgb(31, 31, 31)"
},
"boxShadow": {
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
}
},
"dark": {
"colors": {
"container": "rgb(28, 28, 28)",
"layout": "rgb(18, 18, 18)",
"base-text": "rgb(224, 224, 224)"
}
}
}
}

View File

@@ -1,91 +0,0 @@
{
"name": "Dark Preset",
"desc": "Dark theme preset for night time usage",
"i18nkey": "theme.appearance.preset.dark",
"version": "1.0.0",
"themeScheme": "dark",
"grayscale": false,
"colourWeakness": false,
"recommendColor": false,
"themeColor": "#409eff",
"otherColor": {
"info": "#2080f0",
"success": "#52c41a",
"warning": "#faad14",
"error": "#f5222d"
},
"themeRadius": 6,
"isInfoFollowPrimary": true,
"layout": {
"mode": "vertical",
"scrollMode": "content"
},
"page": {
"animate": true,
"animateMode": "fade-slide"
},
"header": {
"height": 56,
"breadcrumb": {
"visible": true,
"showIcon": true
},
"multilingual": {
"visible": true
},
"globalSearch": {
"visible": true
}
},
"tab": {
"visible": true,
"cache": true,
"height": 44,
"mode": "chrome",
"closeTabByMiddleClick": false
},
"fixedHeaderAndTab": true,
"sider": {
"inverted": true,
"width": 220,
"collapsedWidth": 64,
"mixWidth": 90,
"mixCollapsedWidth": 64,
"mixChildMenuWidth": 200
},
"footer": {
"visible": true,
"fixed": false,
"height": 48,
"right": true
},
"watermark": {
"visible": false,
"text": "SoybeanAdmin",
"enableUserName": false,
"enableTime": false,
"timeFormat": "YYYY-MM-DD HH:mm"
},
"tokens": {
"light": {
"colors": {
"container": "rgb(255, 255, 255)",
"layout": "rgb(247, 250, 252)",
"inverted": "rgb(0, 20, 40)",
"base-text": "rgb(31, 31, 31)"
},
"boxShadow": {
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
}
},
"dark": {
"colors": {
"container": "rgb(28, 28, 28)",
"layout": "rgb(18, 18, 18)",
"base-text": "rgb(224, 224, 224)"
}
}
}
}

View File

@@ -1,91 +0,0 @@
{
"name": "default",
"desc": "Default theme preset with balanced settings",
"i18nkey": "theme.appearance.preset.default",
"version": "1.0.0",
"themeScheme": "light",
"grayscale": false,
"colourWeakness": false,
"recommendColor": false,
"themeColor": "#646cff",
"otherColor": {
"info": "#2080f0",
"success": "#52c41a",
"warning": "#faad14",
"error": "#f5222d"
},
"themeRadius": 6,
"isInfoFollowPrimary": true,
"layout": {
"mode": "vertical",
"scrollMode": "content"
},
"page": {
"animate": true,
"animateMode": "fade-slide"
},
"header": {
"height": 56,
"breadcrumb": {
"visible": true,
"showIcon": true
},
"multilingual": {
"visible": true
},
"globalSearch": {
"visible": true
}
},
"tab": {
"visible": true,
"cache": true,
"height": 44,
"mode": "chrome",
"closeTabByMiddleClick": false
},
"fixedHeaderAndTab": true,
"sider": {
"inverted": false,
"width": 220,
"collapsedWidth": 64,
"mixWidth": 90,
"mixCollapsedWidth": 64,
"mixChildMenuWidth": 200
},
"footer": {
"visible": true,
"fixed": false,
"height": 48,
"right": true
},
"watermark": {
"visible": false,
"text": "SoybeanAdmin",
"enableUserName": false,
"enableTime": false,
"timeFormat": "YYYY-MM-DD HH:mm"
},
"tokens": {
"light": {
"colors": {
"container": "rgb(255, 255, 255)",
"layout": "rgb(247, 250, 252)",
"inverted": "rgb(0, 20, 40)",
"base-text": "rgb(31, 31, 31)"
},
"boxShadow": {
"header": "0 1px 2px rgb(0, 21, 41, 0.08)",
"sider": "2px 0 8px 0 rgb(29, 35, 41, 0.05)",
"tab": "0 1px 2px rgb(0, 21, 41, 0.08)"
}
},
"dark": {
"colors": {
"container": "rgb(28, 28, 28)",
"layout": "rgb(18, 18, 18)",
"base-text": "rgb(224, 224, 224)"
}
}
}
}

View File

@@ -5,7 +5,6 @@ export const themeSettings: App.Theme.ThemeSetting = {
colourWeakness: false,
recommendColor: false,
themeColor: '#646cff',
themeRadius: 6,
otherColor: {
info: '#2080f0',
success: '#52c41a',
@@ -13,6 +12,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
error: '#f5222d'
},
isInfoFollowPrimary: true,
resetCacheStrategy: 'close',
layout: {
mode: 'vertical',
scrollMode: 'content'
@@ -38,8 +38,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
visible: true,
cache: true,
height: 44,
mode: 'chrome',
closeTabByMiddleClick: false
mode: 'chrome'
},
fixedHeaderAndTab: true,
sider: {

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

@@ -16,12 +16,12 @@ declare namespace App {
recommendColor: boolean;
/** Theme color */
themeColor: string;
/** Theme radius */
themeRadius: number;
/** Other color */
otherColor: OtherColor;
/** Whether info color is followed by the primary color */
isInfoFollowPrimary: boolean;
/** Reset cache strategy */
resetCacheStrategy: UnionKey.ResetCacheStrategy;
/** Layout */
layout: {
/** Layout mode */
@@ -71,8 +71,6 @@ declare namespace App {
height: number;
/** Tab mode */
mode: UnionKey.ThemeTabMode;
/** Whether to close tab by middle click */
closeTabByMiddleClick: boolean;
};
/** Fixed header and tab */
fixedHeaderAndTab: boolean;
@@ -369,7 +367,6 @@ declare namespace App {
appearance: string;
layout: string;
general: string;
preset: string;
};
appearance: {
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
@@ -378,23 +375,9 @@ declare namespace App {
themeColor: {
title: string;
followPrimary: string;
} & Record<Theme.ThemeColorKey, string>;
} & Theme.ThemeColor;
recommendColor: string;
recommendColorDesc: string;
themeRadius: {
title: string;
};
preset: {
title: string;
apply: string;
applySuccess: string;
[key: string]:
| {
name: string;
desc: string;
}
| string;
};
};
layout: {
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string> & {
@@ -407,8 +390,6 @@ declare namespace App {
cacheTip: string;
height: string;
mode: { title: string } & Record<UnionKey.ThemeTabMode, string>;
closeByMiddleClick: string;
closeByMiddleClickTip: string;
};
header: {
title: string;
@@ -443,6 +424,7 @@ declare namespace App {
};
fixedHeaderAndTab: string;
};
resetCacheStrategy: { title: string } & Record<UnionKey.ResetCacheStrategy, string>;
};
general: {
title: string;

View File

@@ -1,12 +1,8 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import { GlobalComponents } from 'vue'
// biome-ignore lint: disable
export {}
/* prettier-ignore */
@@ -35,7 +31,6 @@ declare module 'vue' {
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NBadge: typeof import('naive-ui')['NBadge']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
@@ -86,79 +81,3 @@ declare module 'vue' {
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
}
}
// For TSX support
declare global {
const AppProvider: typeof import('./../components/common/app-provider.vue')['default']
const BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
const ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
const CountTo: typeof import('./../components/custom/count-to.vue')['default']
const DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
const ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
const FullScreen: typeof import('./../components/common/full-screen.vue')['default']
const IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
const IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
const IconLocalBanner: typeof import('~icons/local/banner')['default']
const IconLocalLogo: typeof import('~icons/local/logo')['default']
const IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
const IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
const IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
const IconUilSearch: typeof import('~icons/uil/search')['default']
const LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
const LookForward: typeof import('./../components/custom/look-forward.vue')['default']
const MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
const NAlert: typeof import('naive-ui')['NAlert']
const NBadge: typeof import('naive-ui')['NBadge']
const NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
const NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
const NButton: typeof import('naive-ui')['NButton']
const NCard: typeof import('naive-ui')['NCard']
const NCheckbox: typeof import('naive-ui')['NCheckbox']
const NColorPicker: typeof import('naive-ui')['NColorPicker']
const NDialogProvider: typeof import('naive-ui')['NDialogProvider']
const NDivider: typeof import('naive-ui')['NDivider']
const NDrawer: typeof import('naive-ui')['NDrawer']
const NDrawerContent: typeof import('naive-ui')['NDrawerContent']
const NDropdown: typeof import('naive-ui')['NDropdown']
const NEmpty: typeof import('naive-ui')['NEmpty']
const NForm: typeof import('naive-ui')['NForm']
const NFormItem: typeof import('naive-ui')['NFormItem']
const NGi: typeof import('naive-ui')['NGi']
const NGrid: typeof import('naive-ui')['NGrid']
const NInput: typeof import('naive-ui')['NInput']
const NInputGroup: typeof import('naive-ui')['NInputGroup']
const NInputNumber: typeof import('naive-ui')['NInputNumber']
const NList: typeof import('naive-ui')['NList']
const NListItem: typeof import('naive-ui')['NListItem']
const NLoadingBarProvider: typeof import('naive-ui')['NLoadingBarProvider']
const NMenu: typeof import('naive-ui')['NMenu']
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
const NModal: typeof import('naive-ui')['NModal']
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
const NPopover: typeof import('naive-ui')['NPopover']
const NScrollbar: typeof import('naive-ui')['NScrollbar']
const NSelect: typeof import('naive-ui')['NSelect']
const NSpace: typeof import('naive-ui')['NSpace']
const NStatistic: typeof import('naive-ui')['NStatistic']
const NSwitch: typeof import('naive-ui')['NSwitch']
const NTab: typeof import('naive-ui')['NTab']
const NTabs: typeof import('naive-ui')['NTabs']
const NThing: typeof import('naive-ui')['NThing']
const NTooltip: typeof import('naive-ui')['NTooltip']
const NWatermark: typeof import('naive-ui')['NWatermark']
const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
const ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
const RouterLink: typeof import('vue-router')['RouterLink']
const RouterView: typeof import('vue-router')['RouterView']
const SoybeanAvatar: typeof import('./../components/custom/soybean-avatar.vue')['default']
const SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default']
const SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
const TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
const TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
const ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
const WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
}

View File

@@ -1,240 +1,104 @@
/* eslint-disable */
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
declare module "@elegant-router/types" {
type ElegantConstRoute = import('@elegant-router/vue').ElegantConstRoute;
type RouteRecordSingleView = import("vue-router").RouteRecordSingleView;
type RouteRecordRedirect = import("vue-router").RouteRecordRedirect;
type RouteComponent = import("vue-router").RouteComponent;
type Lazy<T> = () => Promise<T>;
export type RawRouteComponent = RouteComponent | Lazy<RouteComponent>;
/**
* route layout
* route layout key
*/
export type RouteLayout = "base" | "blank";
export type RouteLayoutKey = "base" | "blank";
/**
* route map
* route path map
*/
export type RouteMap = {
"root": "/";
"not-found": "/:pathMatch(.*)*";
export type RoutePathMap = {
"Root": "/";
"NotFound": "/:pathMatch(.*)*";
"403": "/403";
"404": "/404";
"500": "/500";
"home": "/home";
"iframe-page": "/iframe-page/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"Home": "/home";
"IframeUrl": "/iframe/:url";
"Login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"ManageApi": "/manage/api";
"ManageDictionary": "/manage/dictionary";
"ManageMenu": "/manage/menu";
"ManageOrganization": "/manage/organization";
"ManagePermission": "/manage/permission";
"ManageRole": "/manage/role";
"ManageRoute": "/manage/route";
"ManageUser": "/manage/user";
"Wip": "/wip";
};
/**
* route key
*/
export type RouteKey = keyof RouteMap;
export type RouteKey = keyof RoutePathMap;
/**
* route path
*/
export type RoutePath = RouteMap[RouteKey];
export type RoutePath = RoutePathMap[RouteKey];
/**
* custom route key
* root route key
*/
export type CustomRouteKey = Extract<
RouteKey,
| "root"
| "not-found"
>;
export type RootRouteKey = 'Root';
/**
* the generated route key
* not found route key
*/
export type GeneratedRouteKey = Exclude<RouteKey, CustomRouteKey>;
export type NotFoundRouteKey = 'NotFound';
/**
* the first level route key, which contain the layout of the route
* builtin route key
*/
export type FirstLevelRouteKey = Extract<
RouteKey,
| "403"
| "404"
| "500"
| "home"
| "iframe-page"
| "login"
>;
export type BuiltinRouteKey = RootRouteKey | NotFoundRouteKey;
/**
* the custom first level route key
* reuse route key
*/
export type CustomFirstLevelRouteKey = Extract<
CustomRouteKey,
| "root"
| "not-found"
>;
export type ReuseRouteKey = never;
/**
* the last level route key, which has the page file
* the route file key, which has it's own file
*/
export type LastLevelRouteKey = Extract<
RouteKey,
| "403"
| "404"
| "500"
| "iframe-page"
| "login"
| "home"
>;
export type RouteFileKey = Exclude<RouteKey, BuiltinRouteKey | ReuseRouteKey>;
/**
* the custom last level route key
* mapped name and path
*/
export type CustomLastLevelRouteKey = Extract<
CustomRouteKey,
| "root"
| "not-found"
>;
type MappedNamePath = {
[K in RouteKey]: { name: K; path: RoutePathMap[K] };
}[RouteKey];
/**
* the single level route key
* auto router single view
*/
export type SingleLevelRouteKey = FirstLevelRouteKey & LastLevelRouteKey;
export type AutoRouterSingleView = Omit<RouteRecordSingleView, 'component' | 'name' | 'path'> & {
component: RouteFileKey;
layout: RouteLayoutKey;
} & MappedNamePath;
/**
* the custom single level route key
* auto router redirect
*/
export type CustomSingleLevelRouteKey = CustomFirstLevelRouteKey & CustomLastLevelRouteKey;
export type AutoRouterRedirect = Omit<RouteRecordRedirect, 'children' | 'name' | 'path'> & MappedNamePath;
/**
* the first level route key, but not the single level
*/
export type FirstLevelRouteNotSingleKey = Exclude<FirstLevelRouteKey, SingleLevelRouteKey>;
/**
* the custom first level route key, but not the single level
* auto router route
*/
export type CustomFirstLevelRouteNotSingleKey = Exclude<CustomFirstLevelRouteKey, CustomSingleLevelRouteKey>;
/**
* the center level route key
*/
export type CenterLevelRouteKey = Exclude<GeneratedRouteKey, FirstLevelRouteKey | LastLevelRouteKey>;
/**
* the custom center level route key
*/
export type CustomCenterLevelRouteKey = Exclude<CustomRouteKey, CustomFirstLevelRouteKey | CustomLastLevelRouteKey>;
/**
* the center level route key
*/
type GetChildRouteKey<K extends RouteKey, T extends RouteKey = RouteKey> = T extends `${K}_${infer R}`
? R extends `${string}_${string}`
? never
: T
: never;
/**
* the single level route
*/
type SingleLevelRoute<K extends SingleLevelRouteKey = SingleLevelRouteKey> = K extends string
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}$view.${K}`;
}
: never;
/**
* the last level route
*/
type LastLevelRoute<K extends GeneratedRouteKey> = K extends LastLevelRouteKey
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component: `view.${K}`;
}
: never;
/**
* the center level route
*/
type CenterLevelRoute<K extends GeneratedRouteKey> = K extends CenterLevelRouteKey
? Omit<ElegantConstRoute, 'component'> & {
name: K;
path: RouteMap[K];
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the multi level route
*/
type MultiLevelRoute<K extends FirstLevelRouteNotSingleKey = FirstLevelRouteNotSingleKey> = K extends string
? ElegantConstRoute & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}`;
children: (CenterLevelRoute<GetChildRouteKey<K>> | LastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom first level route
*/
type CustomSingleLevelRoute<K extends CustomFirstLevelRouteKey = CustomFirstLevelRouteKey> = K extends string
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component?: `layout.${RouteLayout}$view.${LastLevelRouteKey}`;
}
: never;
/**
* the custom last level route
*/
type CustomLastLevelRoute<K extends CustomRouteKey> = K extends CustomLastLevelRouteKey
? Omit<ElegantConstRoute, 'children'> & {
name: K;
path: RouteMap[K];
component?: `view.${LastLevelRouteKey}`;
}
: never;
/**
* the custom center level route
*/
type CustomCenterLevelRoute<K extends CustomRouteKey> = K extends CustomCenterLevelRouteKey
? Omit<ElegantConstRoute, 'component'> & {
name: K;
path: RouteMap[K];
children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom multi level route
*/
type CustomMultiLevelRoute<K extends CustomFirstLevelRouteNotSingleKey = CustomFirstLevelRouteNotSingleKey> =
K extends string
? ElegantConstRoute & {
name: K;
path: RouteMap[K];
component: `layout.${RouteLayout}`;
children: (CustomCenterLevelRoute<GetChildRouteKey<K>> | CustomLastLevelRoute<GetChildRouteKey<K>>)[];
}
: never;
/**
* the custom route
*/
type CustomRoute = CustomSingleLevelRoute | CustomMultiLevelRoute;
/**
* the generated route
*/
type GeneratedRoute = SingleLevelRoute | MultiLevelRoute;
/**
* the elegant route
*/
type ElegantRoute = GeneratedRoute | CustomRoute;
export type AutoRouterRoute = AutoRouterSingleView | AutoRouterRedirect;
}

View File

@@ -1,7 +1,7 @@
import 'vue-router';
export {};
declare module 'vue-router' {
interface RouteMeta {
export interface RouteMeta {
/**
* Title of the route
*

44
src/typings/typed-router.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
/* eslint-disable */
// @ts-nocheck
/* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router
export {}
declare module "vue-router" {
type RouteNamedMap = import("vue-router/auto-routes").RouteNamedMap;
export interface TypesConfig {
RouteNamedMap: RouteNamedMap;
}
}
declare module "vue-router/auto-routes" {
import type { RouteParamsRawGeneric, RouteParamsGeneric, RouteMeta, RouteRecordInfo, ParamValue, ParamValueZeroOrOne } from "vue-router";
/**
* route named map
*/
export interface RouteNamedMap {
"Root": RouteRecordInfo<"Root", "/", Record<never, never>, Record<never, never>>;
"NotFound": RouteRecordInfo<"NotFound", "/:pathMatch(.*)*", Record<never, never>, Record<never, never>>;
"403": RouteRecordInfo<"403", "/403", Record<never, never>, Record<never, never>>;
"404": RouteRecordInfo<"404", "/404", Record<never, never>, Record<never, never>>;
"500": RouteRecordInfo<"500", "/500", Record<never, never>, Record<never, never>>;
"Home": RouteRecordInfo<"Home", "/home", Record<never, never>, Record<never, never>>;
"IframeUrl": RouteRecordInfo<"IframeUrl", "/iframe/:url", { url: ParamValue<true> }, { url: ParamValue<false> }>;
"Login": RouteRecordInfo<"Login", "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?", { module?: ParamValueZeroOrOne<true> }, { module?: ParamValueZeroOrOne<false> }>;
"ManageApi": RouteRecordInfo<"ManageApi", "/manage/api", Record<never, never>, Record<never, never>>;
"ManageDictionary": RouteRecordInfo<"ManageDictionary", "/manage/dictionary", Record<never, never>, Record<never, never>>;
"ManageMenu": RouteRecordInfo<"ManageMenu", "/manage/menu", Record<never, never>, Record<never, never>>;
"ManageOrganization": RouteRecordInfo<"ManageOrganization", "/manage/organization", Record<never, never>, Record<never, never>>;
"ManagePermission": RouteRecordInfo<"ManagePermission", "/manage/permission", Record<never, never>, Record<never, never>>;
"ManageRole": RouteRecordInfo<"ManageRole", "/manage/role", Record<never, never>, Record<never, never>>;
"ManageRoute": RouteRecordInfo<"ManageRoute", "/manage/route", Record<never, never>, Record<never, never>>;
"ManageUser": RouteRecordInfo<"ManageUser", "/manage/user", Record<never, never>, Record<never, never>>;
"Wip": RouteRecordInfo<"Wip", "/wip", Record<never, never>, Record<never, never>>
}
}

View File

@@ -14,6 +14,14 @@ 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

@@ -23,7 +23,7 @@ const appStore = useAppStore();
const themeStore = useThemeStore();
interface LoginModule {
label: App.I18n.I18nKey;
label: string;
component: Component;
}

View File

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

View File

@@ -15,7 +15,7 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16));
<template>
<NSpace vertical :size="16">
<NAlert :title="$t('common.tip')" type="warning">
<NAlert :title="$t('common.warning')" type="warning">
{{ $t('page.home.branchDesc') }}
</NAlert>
<HeaderBanner />

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue';
import { createReusableTemplate } from '@vueuse/core';
import { useThemeStore } from '@/store/modules/theme';
import { $t } from '@/locales';
defineOptions({
@@ -73,8 +72,6 @@ interface GradientBgProps {
const [DefineGradientBg, GradientBg] = createReusableTemplate<GradientBgProps>();
const themeStore = useThemeStore();
function getGradientColor(color: CardData['color']) {
return `linear-gradient(to bottom right, ${color.start}, ${color.end})`;
}
@@ -84,10 +81,7 @@ function getGradientColor(color: CardData['color']) {
<NCard :bordered="false" size="small" class="card-wrapper">
<!-- define component start: GradientBg -->
<DefineGradientBg v-slot="{ $slots, gradientColor }">
<div
class="px-16px pb-4px pt-8px text-white"
:style="{ backgroundImage: gradientColor, borderRadius: themeStore.themeRadius + 'px' }"
>
<div class="rd-8px px-16px pb-4px pt-8px text-white" :style="{ backgroundImage: gradientColor }">
<component :is="$slots.default" />
</div>
</DefineGradientBg>

View File

@@ -21,14 +21,13 @@ const { domRef, updateOptions } = useEcharts(() => ({
}
},
legend: {
data: [$t('page.home.downloadCount'), $t('page.home.registerCount')],
top: '0'
data: [$t('page.home.downloadCount'), $t('page.home.registerCount')]
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%'
containLabel: true
},
xAxis: {
type: 'category',

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageApi</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageDictionary</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageMenu</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageOrganization</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManagePermission</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageRole</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageRoute</div>
</template>

View File

@@ -0,0 +1,5 @@
<script setup lang="ts"></script>
<template>
<div>ManageUser</div>
</template>