Compare commits

..

1 Commits

Author SHA1 Message Date
858d7625a4 refactor(projects): use new elegant-router plugin 2025-10-13 16:51:54 +08:00
110 changed files with 2231 additions and 4608 deletions

12
.env
View File

@ -2,9 +2,9 @@
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin" # if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
VITE_BASE_URL=/ VITE_BASE_URL=/
VITE_APP_TITLE=Dolphin VITE_APP_TITLE=SoybeanAdmin
VITE_APP_DESC='A fresh and elegant admin management system' VITE_APP_DESC=SoybeanAdmin is a fresh and elegant admin template
# the prefix of the icon name # the prefix of the icon name
VITE_ICON_PREFIX=icon VITE_ICON_PREFIX=icon
@ -29,16 +29,16 @@ VITE_HTTP_PROXY=Y
VITE_ROUTER_HISTORY_MODE=history VITE_ROUTER_HISTORY_MODE=history
# success code of backend service, when the code is received, the request is successful # success code of backend service, when the code is received, the request is successful
VITE_SERVICE_SUCCESS_CODE=00000000 VITE_SERVICE_SUCCESS_CODE=0000
# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page # logout codes of backend service, when the code is received, the user will be logged out and redirected to login page
VITE_SERVICE_LOGOUT_CODES=00010001,00010002 VITE_SERVICE_LOGOUT_CODES=8888,8889
# modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal # modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal
VITE_SERVICE_MODAL_LOGOUT_CODES=00010004 VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778
# token expired codes of backend service, when the code is received, it will refresh the token and resend the request # token expired codes of backend service, when the code is received, it will refresh the token and resend the request
VITE_SERVICE_EXPIRED_TOKEN_CODES=00010003 VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998,3333
# when the route mode is static, the defined super role # when the route mode is static, the defined super role
VITE_STATIC_SUPER_ROLE=R_SUPER VITE_STATIC_SUPER_ROLE=R_SUPER

View File

@ -2,6 +2,6 @@
VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
# other backend service base url, prod environment # other backend service base url, prod environment
VITE_OTHER_SERVICE_BASE_URL=`{ VITE_OTHER_SERVICE_BASE_URL= `{
"demo": "http://localhost:9529" "demo": "http://localhost:9529"
}` }`

View File

@ -1,7 +1,7 @@
# backend service base url, test environment # backend service base url, test environment
VITE_SERVICE_BASE_URL=http://192.168.1.7:8080 VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
# other backend service base url, test environment # other backend service base url, test environment
VITE_OTHER_SERVICE_BASE_URL=`{ VITE_OTHER_SERVICE_BASE_URL= `{
"demo": "http://localhost:9528" "demo": "http://localhost:9528"
}` }`

2
.gitignore vendored
View File

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

View File

@ -5,6 +5,7 @@
"antfu.unocss", "antfu.unocss",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"editorconfig.editorconfig", "editorconfig.editorconfig",
"esbenp.prettier-vscode",
"lokalise.i18n-ally", "lokalise.i18n-ally",
"mhutchie.git-graph", "mhutchie.git-graph",
"mikestead.dotenv", "mikestead.dotenv",

View File

@ -1,331 +1,6 @@
# Changelog # Changelog
## [v2.0.1](https://github.com/soybeanjs/soybean-admin/compare/v2.0.0...v2.0.1) (2025-12-04)
###    🚀 Features
- **docs**:
- update QQ group image in README &nbsp;-&nbsp; by @soybeanjs [<samp>(46081)</samp>](https://github.com/soybeanjs/soybean-admin/commit/46081c36)
- **projects**:
- support theme presets to only set partial content. &nbsp;-&nbsp; by **Azir-11** [<samp>(9da84)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9da847fb)
- support theme perset to override component library presets. &nbsp;-&nbsp; by **Azir-11** [<samp>(60517)</samp>](https://github.com/soybeanjs/soybean-admin/commit/605173a1)
- support pinning and unpinning of tabs &nbsp;-&nbsp; by **hooke** [<samp>(b8a76)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b8a767d7)
- hybrid layout mode auto select first deepest child menu &nbsp;-&nbsp; by @paynezhuang [<samp>(94019)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9401925f)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **docs**: update project name in ecosystem section of README &nbsp;-&nbsp; by @soybeanjs [<samp>(bb232)</samp>](https://github.com/soybeanjs/soybean-admin/commit/bb232bf8)
- **types**: add missing property in theme presets &nbsp;-&nbsp; by **刘璐** [<samp>(4a9cf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4a9cf6c3)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **projects**: simplify some theme preset configurations. &nbsp;-&nbsp; by **Azir-11** [<samp>(c6d97)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c6d97dba)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **projects**: add link to ecosystem document. &nbsp;-&nbsp; by **Azir-11** [<samp>(c472a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c472a943)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(f8dc6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f8dc639e)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(7cf40)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7cf4083b)
- **other**:
- remove Prettier's recommendation. &nbsp;-&nbsp; by **Azir-11** [<samp>(73e9a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/73e9a0fe)
- **styles**:
- format code &nbsp;-&nbsp; by @soybeanjs [<samp>(098cd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/098cd50e)
### &nbsp;&nbsp;&nbsp;🎨 Styles
- **projects**: modify homepage prompt title to tip. &nbsp;-&nbsp; by **Azir-11** [<samp>(91a26)</samp>](https://github.com/soybeanjs/soybean-admin/commit/91a261c1)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![paynezhuang](https://github.com/paynezhuang.png?size=48)](https://github.com/paynezhuang)&nbsp;&nbsp;
[hooke](mailto:hellohooke@foxmail.com),&nbsp;[Azir-11](mailto:2075125282@qq.com),&nbsp;[刘璐](mailto:hi.alue@qq.com)
## [v2.0.0](https://github.com/soybeanjs/soybean-admin/compare/v1.3.15...v2.0.0) (2025-11-02)
### &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/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) ## [v1.3.15](https://github.com/soybeanjs/soybean-admin/compare/v1.3.14...v1.3.15) (2025-06-24)
### &nbsp;&nbsp;&nbsp;🚀 Features ### &nbsp;&nbsp;&nbsp;🚀 Features

View File

@ -7,10 +7,11 @@
--- ---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE) [![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 stars](https://img.shields.io/github/stars/soybeanjs/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 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) [![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) [![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;"> <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://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>
@ -139,7 +140,7 @@ Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) docume
## Ecosystem ## 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. - [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. - [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). - [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 +152,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. - [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. - [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 ## How to Contribute
@ -182,21 +181,13 @@ Thanks the following people for their contributions. If you want to contribute t
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" /> <img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a> </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 ## 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. `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> <div>
<p>QQ Group</p> <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>
<!-- <div> <!-- <div>
<p>WeChat Group</p> <p>WeChat Group</p>

View File

@ -7,10 +7,11 @@
--- ---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE) [![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 stars](https://img.shields.io/github/stars/soybeanjs/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 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) [![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) [![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;"> <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://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>
@ -165,7 +166,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。 - [electron-mock-admin](https://github.com/lixin59/electron-mock-api): 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的 mock。
- [T-Shell](https://github.com/TheBlindM/T-Shell): 是一个可配置命令提示的终端模拟器和 SSH 客户端。 - [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) 开发的简单权限系统。 - [pea](https://github.com/haitang1894/pea) : 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 开发的简单权限系统。
@ -177,8 +178,6 @@ pnpm build
- [ba](https://github.com/xiatianYa/Ba-Server): 基于goFrame框架开发的后端服务对接soybean-admin,适配动态路由,接口鉴权限。 - [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分支,适配动态路由,接口鉴权限。 - [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):基于gin+gorm框架开发的go语言后端服务对接soybean-admin的example分支,适配动态路由,接口鉴权限。
更多周边生态请翻阅 [周边生态](https://docs.soybeanjs.cn/zh/awesome) 文档。
## 如何贡献 ## 如何贡献
@ -210,21 +209,13 @@ pnpm build
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" /> <img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
</a> </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 交流群,使用问题欢迎在群内提问。 `SoybeanAdmin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和 QQ 交流群,使用问题欢迎在群内提问。
<div> <div>
<p>QQ交流群</p> <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>
<!-- <div> <!-- <div>
<p>微信群</p> <p>微信群</p>

View File

@ -2,7 +2,7 @@ import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx'; import vueJsx from '@vitejs/plugin-vue-jsx';
import progress from 'vite-plugin-progress'; import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router'; import elegantRouter from 'elegant-router/vite';
import { setupUnocss } from './unocss'; import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin'; import { setupUnplugin } from './unplugin';
import { setupHtmlPlugin } from './html'; import { setupHtmlPlugin } from './html';
@ -13,7 +13,7 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
vue(), vue(),
vueJsx(), vueJsx(),
setupDevtoolsPlugin(viteEnv), setupDevtoolsPlugin(viteEnv),
setupElegantRouter(), elegantRouter(),
setupUnocss(viteEnv), setupUnocss(viteEnv),
...setupUnplugin(viteEnv), ...setupUnplugin(viteEnv),
progress(), 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", "name": "soybean-admin",
"type": "module", "type": "module",
"version": "2.0.1", "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的清新优雅的中后台模版。", "description": "A fresh and elegant admin template, based on Vue3、Vite7、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite7、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
"author": { "author": {
"name": "Soybean", "name": "Soybean",
@ -54,53 +54,54 @@
"@sa/hooks": "workspace:*", "@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*", "@sa/materials": "workspace:*",
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"@vueuse/core": "14.1.0", "@vueuse/core": "13.9.0",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"dayjs": "1.11.19", "dayjs": "1.11.18",
"defu": "6.1.4", "defu": "6.1.4",
"echarts": "6.0.0", "echarts": "6.0.0",
"json5": "2.2.3", "json5": "2.2.3",
"naive-ui": "2.43.2", "naive-ui": "2.43.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "3.0.4", "pinia": "3.0.3",
"tailwind-merge": "3.4.0", "tailwind-merge": "3.3.1",
"vue": "3.5.25", "vue": "3.5.22",
"vue-draggable-plus": "0.6.0", "vue-draggable-plus": "0.6.0",
"vue-i18n": "11.2.2", "vue-i18n": "11.1.12",
"vue-router": "4.6.3" "vue-router": "4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@elegant-router/vue": "0.3.8", "@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.414", "@iconify/json": "2.2.395",
"@sa/scripts": "workspace:*", "@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*", "@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.4", "@soybeanjs/eslint-config": "1.7.1",
"@types/node": "24.10.1", "@types/node": "24.7.2",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.5.10", "@unocss/eslint-config": "66.5.3",
"@unocss/preset-icons": "66.5.10", "@unocss/preset-icons": "66.5.3",
"@unocss/preset-uno": "66.5.10", "@unocss/preset-uno": "66.5.3",
"@unocss/transformer-directives": "66.5.10", "@unocss/transformer-directives": "66.5.3",
"@unocss/transformer-variant-group": "66.5.10", "@unocss/transformer-variant-group": "66.5.3",
"@unocss/vite": "66.5.10", "@unocss/vite": "66.5.3",
"@vitejs/plugin-vue": "6.0.2", "@vitejs/plugin-vue": "6.0.1",
"@vitejs/plugin-vue-jsx": "5.1.2", "@vitejs/plugin-vue-jsx": "5.1.1",
"consola": "3.4.2", "consola": "3.4.2",
"eslint": "9.39.1", "elegant-router": "1.0.4",
"eslint-plugin-vue": "10.6.2", "eslint": "9.37.0",
"eslint-plugin-vue": "10.5.0",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"sass": "1.94.2", "sass": "1.93.2",
"simple-git-hooks": "2.13.1", "simple-git-hooks": "2.13.1",
"tsx": "4.21.0", "tsx": "4.20.6",
"typescript": "5.9.3", "typescript": "5.9.3",
"unplugin-icons": "22.5.0", "unplugin-icons": "22.4.2",
"unplugin-vue-components": "30.0.0", "unplugin-vue-components": "29.1.0",
"vite": "7.2.6", "vite": "7.1.9",
"vite-plugin-progress": "0.0.7", "vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1", "vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.5", "vite-plugin-vue-devtools": "8.0.2",
"vue-eslint-parser": "10.2.0", "vue-eslint-parser": "10.2.0",
"vue-tsc": "3.1.5" "vue-tsc": "3.1.1"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify", "commit-msg": "pnpm sa git-commit-verify",

View File

@ -1,6 +1,6 @@
{ {
"name": "@sa/alova", "name": "@sa/alova",
"version": "2.0.1", "version": "1.3.15",
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
"./fetch": "./src/fetch.ts", "./fetch": "./src/fetch.ts",
@ -15,6 +15,6 @@
"dependencies": { "dependencies": {
"@alova/mock": "2.0.17", "@alova/mock": "2.0.17",
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"alova": "3.4.0" "alova": "3.3.4"
} }
} }

View File

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

View File

@ -119,11 +119,8 @@ export type FlatResponseData<ResponseData, ApiData> =
| FlatResponseSuccessData<ResponseData, ApiData> | FlatResponseSuccessData<ResponseData, ApiData>
| FlatResponseFailData<ResponseData>; | FlatResponseFailData<ResponseData>;
export interface FlatRequestInstance< export interface FlatRequestInstance<ResponseData, ApiData, State extends Record<string, unknown>>
ResponseData, extends RequestInstanceCommon<State> {
ApiData,
State extends Record<string, unknown>
> extends RequestInstanceCommon<State> {
<T extends ApiData = ApiData, R extends ResponseType = 'json'>( <T extends ApiData = ApiData, R extends ResponseType = 'json'>(
config: CustomAxiosRequestConfig<R> config: CustomAxiosRequestConfig<R>
): Promise<FlatResponseData<ResponseData, MappedType<R, T>>>; ): Promise<FlatResponseData<ResponseData, MappedType<R, T>>>;

View File

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

View File

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

View File

@ -26,11 +26,8 @@ export type HookRequestInstanceResponseData<ResponseData, ApiData> = {
loading: Ref<boolean>; loading: Ref<boolean>;
} & (HookRequestInstanceResponseSuccessData<ApiData> | HookRequestInstanceResponseFailData<ResponseData>); } & (HookRequestInstanceResponseSuccessData<ApiData> | HookRequestInstanceResponseFailData<ResponseData>);
export interface HookRequestInstance< export interface HookRequestInstance<ResponseData, ApiData, State extends Record<string, unknown>>
ResponseData, extends RequestInstanceCommon<State> {
ApiData,
State extends Record<string, unknown>
> extends RequestInstanceCommon<State> {
<T extends ApiData = ApiData, R extends ResponseType = 'json'>( <T extends ApiData = ApiData, R extends ResponseType = 'json'>(
config: CustomAxiosRequestConfig config: CustomAxiosRequestConfig
): HookRequestInstanceResponseData<ResponseData, MappedType<R, T>>; ): HookRequestInstanceResponseData<ResponseData, MappedType<R, T>>;

View File

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

View File

@ -146,8 +146,7 @@ export type LayoutScrollMode = 'wrapper' | 'content';
/** Admin layout props */ /** Admin layout props */
export interface AdminLayoutProps export interface AdminLayoutProps
extends extends AdminLayoutHeaderConfig,
AdminLayoutHeaderConfig,
AdminLayoutTabConfig, AdminLayoutTabConfig,
AdminLayoutSiderConfig, AdminLayoutSiderConfig,
AdminLayoutContentConfig, AdminLayoutContentConfig,

View File

@ -1,6 +1,6 @@
{ {
"name": "@sa/scripts", "name": "@sa/scripts",
"version": "2.0.1", "version": "1.3.15",
"bin": { "bin": {
"sa": "./bin.ts" "sa": "./bin.ts"
}, },
@ -14,15 +14,15 @@
}, },
"devDependencies": { "devDependencies": {
"@soybeanjs/changelog": "0.3.25", "@soybeanjs/changelog": "0.3.25",
"bumpp": "10.3.2", "bumpp": "10.3.1",
"c12": "3.3.2", "c12": "3.3.0",
"cac": "6.7.14", "cac": "6.7.14",
"consola": "3.4.2", "consola": "3.4.2",
"enquirer": "2.4.1", "enquirer": "2.4.1",
"execa": "9.6.1", "execa": "9.6.0",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"npm-check-updates": "19.1.2", "npm-check-updates": "19.0.0",
"picomatch": "4.0.3", "picomatch": "4.0.3",
"rimraf": "6.1.2" "rimraf": "6.0.1"
} }
} }

View File

@ -1,5 +1,5 @@
import { execCommand } from '../shared'; import { execCommand } from '../shared';
export async function updatePkg(args: string[] = ['--deep', '-u']) { 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", "name": "@sa/uno-preset",
"version": "2.0.1", "version": "1.3.15",
"exports": { "exports": {
".": "./src/index.ts" ".": "./src/index.ts"
}, },

View File

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

2853
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ defineOptions({
interface Props { interface Props {
itemAlign?: NaiveUI.Align; itemAlign?: NaiveUI.Align;
disabledDelete?: boolean; disabledDelete?: boolean;
disabledAdd?: boolean;
loading?: boolean; loading?: boolean;
} }
@ -43,7 +42,7 @@ function refresh() {
<NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px"> <NSpace :align="itemAlign" wrap justify="end" class="lt-sm:w-200px">
<slot name="prefix"></slot> <slot name="prefix"></slot>
<slot name="default"> <slot name="default">
<NButton size="small" ghost type="primary" :disabled="disabledAdd" @click="add"> <NButton size="small" ghost type="primary" @click="add">
<template #icon> <template #icon>
<icon-ic-round-plus class="text-icon" /> <icon-ic-round-plus class="text-icon" />
</template> </template>

View File

@ -1,8 +0,0 @@
import { transformRecordToOption } from '@/utils/common';
export const dictionaryTypeRecord: Record<Api.Sys.Core.DictionaryType, App.I18n.I18nKey> = {
enum: 'page.sys.core.dictionary.options.type.enum',
tree: 'page.sys.core.dictionary.options.type.tree'
};
export const dictionaryTypeOptions = transformRecordToOption(dictionaryTypeRecord);

View File

@ -1,8 +1,8 @@
import type { Ref } from 'vue';
import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue'; import { computed, effectScope, onScopeDispose, reactive, shallowRef, watch } from 'vue';
import type { Ref } from 'vue';
import type { PaginationProps } from 'naive-ui'; import type { PaginationProps } from 'naive-ui';
import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks';
import { useBoolean, useTable } from '@sa/hooks'; import { useBoolean, useTable } from '@sa/hooks';
import type { PaginationData, TableColumnCheck, UseTableOptions } from '@sa/hooks';
import type { FlatResponseData } from '@sa/axios'; import type { FlatResponseData } from '@sa/axios';
import { jsonClone } from '@sa/utils'; import { jsonClone } from '@sa/utils';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
@ -131,7 +131,6 @@ export function useNaivePaginatedTable<ResponseData, ApiData>(
getColumns, getColumns,
onFetched: data => { onFetched: data => {
pagination.itemCount = data.total; pagination.itemCount = data.total;
pagination.pageSize = data.pageSize;
} }
}); });
@ -231,17 +230,17 @@ export function useTableOperate<TableData>(
} }
export function defaultTransform<ApiData>( export function defaultTransform<ApiData>(
response: FlatResponseData<any, Api.Common.PageResponse<ApiData>> response: FlatResponseData<any, Api.Common.PaginatingQueryRecord<ApiData>>
): PaginationData<ApiData> { ): PaginationData<ApiData> {
const { data, error } = response; const { data, error } = response;
if (!error) { if (!error) {
const { records, pageIndex, pageSize, total } = data; const { records, current, size, total } = data;
return { return {
data: records, data: records,
pageNum: pageIndex, pageNum: current,
pageSize, pageSize: size,
total total
}; };
} }
@ -301,7 +300,9 @@ function getColumns<Column extends NaiveUI.TableColumn<any>>(cols: Column[], che
} }
}); });
return checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column); const filteredColumns = checks.filter(item => item.checked).map(check => columnMap.get(check.key) as Column);
return filteredColumns;
} }
export function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> { export function isTableColumnHasKey<T>(column: NaiveUI.TableColumn<T>): column is NaiveUI.TableColumnWithKey<T> {

View File

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

View File

@ -73,7 +73,7 @@ function handleDropdown(key: DropdownKey) {
<div> <div>
<ButtonIcon> <ButtonIcon>
<SvgIcon icon="ph:user-circle" class="text-icon-large" /> <SvgIcon icon="ph:user-circle" class="text-icon-large" />
<span class="text-16px font-medium">{{ authStore.userInfo.username }}</span> <span class="text-16px font-medium">{{ authStore.userInfo.userName }}</span>
</ButtonIcon> </ButtonIcon>
</div> </div>
</NDropdown> </NDropdown>

View File

@ -3,7 +3,6 @@ import { useRoute } from 'vue-router';
import { useContext } from '@sa/hooks'; import { useContext } from '@sa/hooks';
import type { RouteKey } from '@elegant-router/types'; import type { RouteKey } from '@elegant-router/types';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
export const [provideMixMenuContext, useMixMenuContext] = useContext('MixMenu', useMixMenu); export const [provideMixMenuContext, useMixMenuContext] = useContext('MixMenu', useMixMenu);
@ -11,7 +10,6 @@ export const [provideMixMenuContext, useMixMenuContext] = useContext('MixMenu',
function useMixMenu() { function useMixMenu() {
const route = useRoute(); const route = useRoute();
const routeStore = useRouteStore(); const routeStore = useRouteStore();
const themeStore = useThemeStore();
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
const { routerPushByKeyWithMetaQuery } = useRouterPush(); const { routerPushByKeyWithMetaQuery } = useRouterPush();
@ -102,46 +100,10 @@ function useMixMenu() {
() => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || [] () => secondLevelMenus.value.find(menu => menu.key === activeSecondLevelMenuKey.value)?.children || []
); );
const hasChildLevelMenus = computed(() => childLevelMenus.value.length > 0);
function getDeepestLevelMenuKey(): RouteKey | null {
if (!secondLevelMenus.value.length || !themeStore.sider.autoSelectFirstMenu) {
return null;
}
const secondLevelFirstMenu = secondLevelMenus.value[0];
if (!secondLevelFirstMenu) {
return null;
}
function findDeepest(menu: App.Global.Menu): RouteKey {
if (!menu.children?.length) {
return menu.routeKey;
}
return findDeepest(menu.children[0]);
}
return findDeepest(secondLevelFirstMenu);
}
function activeDeepestLevelMenuKey() {
const deepestLevelMenuKey = getDeepestLevelMenuKey();
if (!deepestLevelMenuKey) return;
// select the deepest second level menu
handleSelectSecondLevelMenu(deepestLevelMenuKey);
}
watch( watch(
() => route.name, () => route.name,
() => { () => {
getActiveFirstLevelMenuKey(); getActiveFirstLevelMenuKey();
// if there are child level menus, get the active second level menu key
if (hasChildLevelMenus.value) {
getActiveSecondLevelMenuKey();
}
}, },
{ immediate: true } { immediate: true }
); );
@ -159,10 +121,7 @@ function useMixMenu() {
isActiveSecondLevelMenuHasChildren, isActiveSecondLevelMenuHasChildren,
handleSelectSecondLevelMenu, handleSelectSecondLevelMenu,
getActiveSecondLevelMenuKey, getActiveSecondLevelMenuKey,
childLevelMenus, childLevelMenus
hasChildLevelMenus,
getDeepestLevelMenuKey,
activeDeepestLevelMenuKey
}; };
} }

View File

@ -2,7 +2,6 @@
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { SimpleScrollbar } from '@sa/materials'; import { SimpleScrollbar } from '@sa/materials';
import type { RouteKey } from '@elegant-router/types';
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app'; import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
@ -19,28 +18,12 @@ const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const routeStore = useRouteStore(); const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush(); const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } =
firstLevelMenus, useMixMenuContext('TopHybridHeaderFirst');
secondLevelMenus,
activeFirstLevelMenuKey,
handleSelectFirstLevelMenu,
activeDeepestLevelMenuKey
} = useMixMenuContext('TopHybridHeaderFirst');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
const expandedKeys = ref<string[]>([]); const expandedKeys = ref<string[]>([]);
/**
* Handle first level menu select
* @param key RouteKey
*/
function handleSelectMenu(key: RouteKey) {
handleSelectFirstLevelMenu(key);
// if there are second level menus, select the deepest one by default
activeDeepestLevelMenuKey();
}
function updateExpandedKeys() { function updateExpandedKeys() {
if (appStore.siderCollapse || !selectedKey.value) { if (appStore.siderCollapse || !selectedKey.value) {
expandedKeys.value = []; expandedKeys.value = [];
@ -66,7 +49,7 @@ watch(
:options="firstLevelMenus" :options="firstLevelMenus"
:indent="18" :indent="18"
responsive responsive
@update:value="handleSelectMenu" @update:value="handleSelectFirstLevelMenu"
/> />
</Teleport> </Teleport>
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`"> <Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">

View File

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteKey } from '@elegant-router/types';
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app'; import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
@ -14,25 +13,9 @@ defineOptions({
const appStore = useAppStore(); const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush(); const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } =
firstLevelMenus, useMixMenuContext('TopHybridSidebarFirst');
secondLevelMenus,
activeFirstLevelMenuKey,
handleSelectFirstLevelMenu,
activeDeepestLevelMenuKey
} = useMixMenuContext('TopHybridSidebarFirst');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
/**
* Handle first level menu select
* @param key RouteKey
*/
function handleSelectMenu(key: RouteKey) {
handleSelectFirstLevelMenu(key);
// if there are second level menus, select the deepest one by default
activeDeepestLevelMenuKey();
}
</script> </script>
<template> <template>
@ -54,7 +37,7 @@ function handleSelectMenu(key: RouteKey) {
:sider-collapse="appStore.siderCollapse" :sider-collapse="appStore.siderCollapse"
:dark-mode="themeStore.darkMode" :dark-mode="themeStore.darkMode"
:theme-color="themeStore.themeColor" :theme-color="themeStore.themeColor"
@select="handleSelectMenu" @select="handleSelectFirstLevelMenu"
@toggle-sider-collapse="appStore.toggleSiderCollapse" @toggle-sider-collapse="appStore.toggleSiderCollapse"
/> />
</div> </div>

View File

@ -33,15 +33,15 @@ const {
isActiveSecondLevelMenuHasChildren, isActiveSecondLevelMenuHasChildren,
handleSelectSecondLevelMenu, handleSelectSecondLevelMenu,
getActiveSecondLevelMenuKey, getActiveSecondLevelMenuKey,
childLevelMenus, childLevelMenus
hasChildLevelMenus,
activeDeepestLevelMenuKey
} = useMixMenuContext('VerticalHybridHeaderFirst'); } = useMixMenuContext('VerticalHybridHeaderFirst');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);
const showDrawer = computed(() => hasChildLevelMenus.value && (drawerVisible.value || appStore.mixSiderFixed)); const hasChildMenus = computed(() => childLevelMenus.value.length > 0);
const showDrawer = computed(() => hasChildMenus.value && (drawerVisible.value || appStore.mixSiderFixed));
function handleSelectMixMenu(key: RouteKey) { function handleSelectMixMenu(key: RouteKey) {
handleSelectSecondLevelMenu(key); handleSelectSecondLevelMenu(key);
@ -51,33 +51,12 @@ function handleSelectMixMenu(key: RouteKey) {
} }
} }
/**
* Handle second level menu selection based on autoSelectFirstMenu setting:
* - When disabled: Activate first second-level menu for display only, expand third-level menu if exists
* - When enabled: Navigate to the deepest menu automatically
*/
function handleSelectMenu(key: RouteKey) { function handleSelectMenu(key: RouteKey) {
handleSelectFirstLevelMenu(key); handleSelectFirstLevelMenu(key);
if (secondLevelMenus.value.length === 0) return; if (secondLevelMenus.value.length > 0) {
handleSelectMixMenu(secondLevelMenus.value[0].routeKey);
const secondFirstMenuKey = secondLevelMenus.value[0].routeKey;
// Case 1: autoSelectFirstMenu disabled - only activate menu for display
if (!themeStore.sider.autoSelectFirstMenu) {
// Check if there are third-level menus
const hasChildren = secondLevelMenus.value.find(menu => menu.key === secondFirstMenuKey)?.children?.length;
// If there are third-level menus, expand them
if (hasChildren) {
handleSelectMixMenu(secondFirstMenuKey);
}
return;
} }
// Case 2: autoSelectFirstMenu enabled - navigate to deepest menu
activeDeepestLevelMenuKey();
setDrawerVisible(false);
} }
function handleResetActiveMenu() { function handleResetActiveMenu() {
@ -135,9 +114,7 @@ watch(
</FirstLevelMenu> </FirstLevelMenu>
<div <div
class="relative h-full transition-width-300" class="relative h-full transition-width-300"
:style="{ :style="{ width: appStore.mixSiderFixed && hasChildMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
width: appStore.mixSiderFixed && hasChildLevelMenus ? themeStore.sider.mixChildMenuWidth + 'px' : '0px'
}"
> >
<DarkModeContainer <DarkModeContainer
class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300" class="absolute-lt h-full flex-col-stretch nowrap-hidden shadow-sm transition-all-300"

View File

@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
const visible = defineModel<boolean>('visible'); const visible = defineModel<boolean>('visible');
const { removeTab, clearTabs, clearLeftTabs, clearRightTabs, fixTab, unfixTab, isTabRetain, homeTab } = useTabStore(); const { removeTab, clearTabs, clearLeftTabs, clearRightTabs } = useTabStore();
const { SvgIconVNode } = useSvgIcon(); const { SvgIconVNode } = useSvgIcon();
type DropdownOption = { type DropdownOption = {
@ -64,23 +64,6 @@ const options = computed(() => {
icon: SvgIconVNode({ icon: 'ant-design:line-outlined', fontSize: 18 }) icon: SvgIconVNode({ icon: 'ant-design:line-outlined', fontSize: 18 })
} }
]; ];
if (props.tabId !== homeTab?.id) {
if (isTabRetain(props.tabId)) {
opts.push({
key: 'unpin',
label: $t('dropdown.unpin'),
icon: SvgIconVNode({ icon: 'mdi:pin-off-outline', fontSize: 18 })
});
} else {
opts.push({
key: 'pin',
label: $t('dropdown.pin'),
icon: SvgIconVNode({ icon: 'mdi:pin-outline', fontSize: 18 })
});
}
}
const { excludeKeys, disabledKeys } = props; const { excludeKeys, disabledKeys } = props;
const result = opts.filter(opt => !excludeKeys.includes(opt.key)); const result = opts.filter(opt => !excludeKeys.includes(opt.key));
@ -115,12 +98,6 @@ const dropdownAction: Record<App.Global.DropdownKey, () => void> = {
}, },
closeAll() { closeAll() {
clearTabs(); clearTabs();
},
pin() {
fixTab(props.tabId);
},
unpin() {
unfixTab(props.tabId);
} }
}; };

View File

@ -26,8 +26,6 @@ const tabRef = ref<HTMLElement>();
const isPCFlag = isPC(); const isPCFlag = isPC();
const TAB_DATA_ID = 'data-tab-id'; const TAB_DATA_ID = 'data-tab-id';
const MIDDLE_MOUSE_BUTTON = 1;
const RIGHT_MOUSE_BUTTON = 2;
type TabNamedNodeMap = NamedNodeMap & { type TabNamedNodeMap = NamedNodeMap & {
[TAB_DATA_ID]: Attr; [TAB_DATA_ID]: Attr;
@ -86,26 +84,6 @@ function handleCloseTab(tab: App.Global.Tab) {
tabStore.removeTab(tab.id); 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() { async function refresh() {
appStore.reloadPage(500); appStore.reloadPage(500);
} }
@ -204,8 +182,7 @@ init();
:active="tab.id === tabStore.activeTabId" :active="tab.id === tabStore.activeTabId"
:active-color="themeStore.themeColor" :active-color="themeStore.themeColor"
:closable="!tabStore.isTabRetain(tab.id)" :closable="!tabStore.isTabRetain(tab.id)"
@pointerdown="switchTab($event, tab)" @pointerdown="tabStore.switchRouteByTab(tab)"
@mousedown="handleMousedown($event, tab)"
@close="handleCloseTab(tab)" @close="handleCloseTab(tab)"
@contextmenu="handleContextMenu($event, tab.id)" @contextmenu="handleContextMenu($event, tab.id)"
> >

View File

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

@ -12,7 +12,6 @@ const themeStore = useThemeStore();
const layoutMode = computed(() => themeStore.layout.mode); const layoutMode = computed(() => themeStore.layout.mode);
const isMixLayoutMode = computed(() => layoutMode.value.includes('mix') || layoutMode.value.includes('hybrid')); const isMixLayoutMode = computed(() => layoutMode.value.includes('mix') || layoutMode.value.includes('hybrid'));
const isHybridLayoutMode = computed(() => layoutMode.value.includes('hybrid'));
</script> </script>
<template> <template>
@ -33,12 +32,6 @@ const isHybridLayoutMode = computed(() => layoutMode.value.includes('hybrid'));
<SettingItem v-if="layoutMode === 'vertical-mix'" key="5" :label="$t('theme.layout.sider.mixChildMenuWidth')"> <SettingItem v-if="layoutMode === 'vertical-mix'" key="5" :label="$t('theme.layout.sider.mixChildMenuWidth')">
<NInputNumber v-model:value="themeStore.sider.mixChildMenuWidth" size="small" :step="1" class="w-120px" /> <NInputNumber v-model:value="themeStore.sider.mixChildMenuWidth" size="small" :step="1" class="w-120px" />
</SettingItem> </SettingItem>
<SettingItem v-if="isHybridLayoutMode" key="6" :label="$t('theme.layout.sider.autoSelectFirstMenu')">
<template #suffix>
<IconTooltip :desc="$t('theme.layout.sider.autoSelectFirstMenuTip')" />
</template>
<NSwitch v-model:value="themeStore.sider.autoSelectFirstMenu" />
</SettingItem>
</TransitionGroup> </TransitionGroup>
</template> </template>

View File

@ -35,12 +35,6 @@ const themeStore = useThemeStore();
class="w-120px" class="w-120px"
/> />
</SettingItem> </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> </TransitionGroup>
</template> </template>

View File

@ -1,8 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { defu } from 'defu';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { themeSettings } from '@/theme/settings';
import { $t } from '@/locales'; import { $t } from '@/locales';
defineOptions({ defineOptions({
@ -16,7 +14,6 @@ type ThemePreset = Pick<
| 'colourWeakness' | 'colourWeakness'
| 'recommendColor' | 'recommendColor'
| 'themeColor' | 'themeColor'
| 'themeRadius'
| 'otherColor' | 'otherColor'
| 'isInfoFollowPrimary' | 'isInfoFollowPrimary'
| 'layout' | 'layout'
@ -33,8 +30,6 @@ type ThemePreset = Pick<
desc: string; desc: string;
i18nkey?: string; i18nkey?: string;
version: string; version: string;
/** Optional NaiveUI theme overrides */
naiveui?: App.Theme.NaiveUIThemeOverride;
}; };
const presetModules = import.meta.glob('@/theme/preset/*.json', { eager: true, import: 'default' }); const presetModules = import.meta.glob('@/theme/preset/*.json', { eager: true, import: 'default' });
@ -80,9 +75,7 @@ const getPresetDesc = (preset: ThemePreset): string => {
} }
}; };
const applyPreset = (preset: ThemePreset): void => { const applyPreset = ({ themeScheme, grayscale, colourWeakness, layout, watermark, ...rest }: ThemePreset): void => {
const mergedPreset = defu(preset, themeSettings);
const { themeScheme, grayscale, colourWeakness, layout, watermark, naiveui, ...rest } = mergedPreset;
themeStore.setThemeScheme(themeScheme); themeStore.setThemeScheme(themeScheme);
themeStore.setGrayscale(grayscale); themeStore.setGrayscale(grayscale);
themeStore.setColourWeakness(colourWeakness); themeStore.setColourWeakness(colourWeakness);
@ -102,9 +95,6 @@ const applyPreset = (preset: ThemePreset): void => {
tokens: { ...rest.tokens } tokens: { ...rest.tokens }
}); });
// Apply NaiveUI theme overrides if present
themeStore.setNaiveThemeOverrides(naiveui);
window.$message?.success($t('theme.appearance.preset.applySuccess')); window.$message?.success($t('theme.appearance.preset.applySuccess'));
}; };
</script> </script>

View File

@ -1,6 +1,6 @@
const local: App.I18n.Schema = { const local: App.I18n.Schema = {
system: { system: {
title: 'Dolphin', title: 'SoybeanAdmin',
updateTitle: 'System Version Update Notification', updateTitle: 'System Version Update Notification',
updateContent: 'A new version of the system has been detected. Do you want to refresh the page immediately?', updateContent: 'A new version of the system has been detected. Do you want to refresh the page immediately?',
updateConfirm: 'Refresh immediately', updateConfirm: 'Refresh immediately',
@ -47,8 +47,7 @@ const local: App.I18n.Schema = {
yesOrNo: { yesOrNo: {
yes: 'Yes', yes: 'Yes',
no: 'No' no: 'No'
}, }
children: 'Children'
}, },
request: { request: {
logout: 'Logout user after request failed', logout: 'Logout user after request failed',
@ -84,9 +83,6 @@ const local: App.I18n.Schema = {
error: 'Error', error: 'Error',
followPrimary: 'Follow Primary' followPrimary: 'Follow Primary'
}, },
themeRadius: {
title: 'Theme Radius'
},
recommendColor: 'Apply Recommended Color Algorithm', recommendColor: 'Apply Recommended Color Algorithm',
recommendColorDesc: 'The recommended color algorithm refers to', recommendColorDesc: 'The recommended color algorithm refers to',
preset: { preset: {
@ -142,9 +138,7 @@ const local: App.I18n.Schema = {
slider: 'Slider', slider: 'Slider',
chrome: 'Chrome', chrome: 'Chrome',
button: 'Button' button: 'Button'
}, }
closeByMiddleClick: 'Close Tab by Middle Click',
closeByMiddleClickTip: 'Enable closing tabs by clicking with the middle mouse button'
}, },
header: { header: {
title: 'Header Settings', title: 'Header Settings',
@ -161,10 +155,7 @@ const local: App.I18n.Schema = {
collapsedWidth: 'Sider Collapsed Width', collapsedWidth: 'Sider Collapsed Width',
mixWidth: 'Mix Sider Width', mixWidth: 'Mix Sider Width',
mixCollapsedWidth: 'Mix Sider Collapse Width', mixCollapsedWidth: 'Mix Sider Collapse Width',
mixChildMenuWidth: 'Mix Child Menu Width', mixChildMenuWidth: 'Mix Child Menu Width'
autoSelectFirstMenu: 'Auto Select First Submenu',
autoSelectFirstMenuTip:
'When a first-level menu is clicked, the first submenu is automatically selected and navigated to the deepest level'
}, },
footer: { footer: {
title: 'Footer Settings', title: 'Footer Settings',
@ -229,12 +220,7 @@ const local: App.I18n.Schema = {
404: 'Page Not Found', 404: 'Page Not Found',
500: 'Server Error', 500: 'Server Error',
'iframe-page': 'Iframe', 'iframe-page': 'Iframe',
home: 'Home', home: 'Home'
sys: 'System',
sys_core: 'Configuration',
sys_core_dictionary: 'Dictionary',
sys_rbac: 'Organization',
sys_rbac_region: 'Region'
}, },
page: { page: {
login: { login: {
@ -311,51 +297,6 @@ const local: App.I18n.Schema = {
desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!' desc5: 'Soybean just wrote some of the workbench pages casually, and it was enough to see!'
}, },
creativity: 'Creativity' creativity: 'Creativity'
},
sys: {
core: {
dictionary: {
title: 'Dictionary',
fields: {
id: 'ID',
name: 'Name',
code: 'Code',
type: 'string',
description: 'Description',
createTime: 'Create Time',
updateTime: 'Update Time'
},
options: {
type: {
enum: 'Enum',
tree: 'Tree'
}
},
item: {
title: 'Item',
fields: {
name: 'Name',
code: 'Code',
sort: 'Sort',
description: 'Description'
}
}
}
},
rbac: {
region: {
title: 'Region',
fields: {
name: 'Name',
code: 'Code',
extCode: 'Extend Code',
sort: 'Sort',
description: 'Description',
createTime: 'Create Time',
updateTime: 'Update Time'
}
}
}
} }
}, },
form: { form: {
@ -390,9 +331,7 @@ const local: App.I18n.Schema = {
closeOther: 'Close Other', closeOther: 'Close Other',
closeLeft: 'Close Left', closeLeft: 'Close Left',
closeRight: 'Close Right', closeRight: 'Close Right',
closeAll: 'Close All', closeAll: 'Close All'
pin: 'Pin Tab',
unpin: 'Unpin Tab'
}, },
icon: { icon: {
themeConfig: 'Theme Configuration', themeConfig: 'Theme Configuration',

View File

@ -1,6 +1,6 @@
const local: App.I18n.Schema = { const local: App.I18n.Schema = {
system: { system: {
title: 'Dolphin 管理系统', title: 'Soybean 管理系统',
updateTitle: '系统版本更新通知', updateTitle: '系统版本更新通知',
updateContent: '检测到系统有新版本发布,是否立即刷新页面?', updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
updateConfirm: '立即刷新', updateConfirm: '立即刷新',
@ -47,8 +47,7 @@ const local: App.I18n.Schema = {
yesOrNo: { yesOrNo: {
yes: '是', yes: '是',
no: '否' no: '否'
}, }
children: '子项'
}, },
request: { request: {
logout: '请求失败后登出用户', logout: '请求失败后登出用户',
@ -84,9 +83,6 @@ const local: App.I18n.Schema = {
error: '错误色', error: '错误色',
followPrimary: '跟随主色' followPrimary: '跟随主色'
}, },
themeRadius: {
title: '主题圆角'
},
recommendColor: '应用推荐算法的颜色', recommendColor: '应用推荐算法的颜色',
recommendColorDesc: '推荐颜色的算法参照', recommendColorDesc: '推荐颜色的算法参照',
preset: { preset: {
@ -139,9 +135,7 @@ const local: App.I18n.Schema = {
slider: '滑块风格', slider: '滑块风格',
chrome: '谷歌风格', chrome: '谷歌风格',
button: '按钮风格' button: '按钮风格'
}, }
closeByMiddleClick: '鼠标中键关闭标签页',
closeByMiddleClickTip: '启用后可以使用鼠标中键点击标签页进行关闭'
}, },
header: { header: {
title: '头部设置', title: '头部设置',
@ -158,9 +152,7 @@ const local: App.I18n.Schema = {
collapsedWidth: '侧边栏折叠宽度', collapsedWidth: '侧边栏折叠宽度',
mixWidth: '混合布局侧边栏宽度', mixWidth: '混合布局侧边栏宽度',
mixCollapsedWidth: '混合布局侧边栏折叠宽度', mixCollapsedWidth: '混合布局侧边栏折叠宽度',
mixChildMenuWidth: '混合布局子菜单宽度', mixChildMenuWidth: '混合布局子菜单宽度'
autoSelectFirstMenu: '自动选择第一个子菜单',
autoSelectFirstMenuTip: '点击一级菜单时,自动选择并导航到第一个子菜单的最深层级'
}, },
footer: { footer: {
title: '底部设置', title: '底部设置',
@ -225,12 +217,7 @@ const local: App.I18n.Schema = {
404: '页面不存在', 404: '页面不存在',
500: '服务器错误', 500: '服务器错误',
'iframe-page': '外链页面', 'iframe-page': '外链页面',
home: '首页', home: '首页'
sys: '系统管理',
sys_core: '系统配置',
sys_core_dictionary: '数据字典',
sys_rbac: '组织架构',
sys_rbac_region: '行政区划'
}, },
page: { page: {
login: { login: {
@ -307,51 +294,6 @@ const local: App.I18n.Schema = {
desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!' desc5: 'Soybean 刚才把工作台页面随便写了一些,凑合能看了!'
}, },
creativity: '创意' creativity: '创意'
},
sys: {
core: {
dictionary: {
title: '数据字典',
fields: {
id: 'ID',
name: '名称',
code: '代码',
type: '类型',
description: '描述',
createTime: '创建时间',
updateTime: '修改时间'
},
options: {
type: {
enum: '枚举',
tree: '树型'
}
},
item: {
title: '字典项',
fields: {
name: '名称',
code: '代码',
sort: '排序',
description: '描述'
}
}
}
},
rbac: {
region: {
title: '行政区划',
fields: {
name: '名称',
code: '代码',
extCode: '扩展代码',
sort: '排序',
description: '描述',
createTime: '创建时间',
updateTime: '修改时间'
}
}
}
} }
}, },
form: { form: {
@ -386,9 +328,7 @@ const local: App.I18n.Schema = {
closeOther: '关闭其它', closeOther: '关闭其它',
closeLeft: '关闭左侧', closeLeft: '关闭左侧',
closeRight: '关闭右侧', closeRight: '关闭右侧',
closeAll: '关闭所有', closeAll: '关闭所有'
pin: '固定标签',
unpin: '取消固定'
}, },
icon: { icon: {
themeConfig: '主题配置', themeConfig: '主题配置',

View File

@ -0,0 +1,27 @@
/* 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"),
ManageMenu: () => import("@/views/manage/menu/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,130 @@
/* 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: 'ManageMenu',
path: '/manage/menu',
layout: 'base',
component: 'ManageMenu',
meta: {
title: "ManageMenu"
},
},
{
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,28 @@
/* 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)?",
"ManageMenu": "/manage/menu",
"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,26 +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"),
sys_core_dictionary: () => import("@/views/sys/core/dictionary/index.vue"),
sys_rbac_region: () => import("@/views/sys/rbac/region/index.vue"),
};

View File

@ -1,133 +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: 10
}
},
{
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
}
},
{
name: 'sys',
path: '/sys',
component: 'layout.base',
meta: {
title: 'sys',
i18nKey: 'route.sys',
icon: 'mdi:laptop-windows',
order: 20
},
children: [
{
name: 'sys_core',
path: '/sys/core',
meta: {
title: 'sys_core',
i18nKey: 'route.sys_core',
icon: 'ic:round-construction'
},
children: [
{
name: 'sys_core_dictionary',
path: '/sys/core/dictionary',
component: 'view.sys_core_dictionary',
meta: {
title: 'sys_core_dictionary',
i18nKey: 'route.sys_core_dictionary',
icon: 'ic:round-list-alt'
}
}
]
},
{
name: 'sys_rbac',
path: '/sys/rbac',
meta: {
title: 'sys_rbac',
i18nKey: 'route.sys_rbac',
icon: 'ic:outline-account-tree'
},
children: [
{
name: 'sys_rbac_region',
path: '/sys/rbac/region',
component: 'view.sys_rbac_region',
meta: {
title: 'sys_rbac_region',
i18nKey: 'route.sys_rbac_region',
icon: 'mdi:home-city-outline'
}
}
]
}
]
}
];

View File

@ -1,197 +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)?",
"sys": "/sys",
"sys_core": "/sys/core",
"sys_core_dictionary": "/sys/core/dictionary",
"sys_rbac": "/sys/rbac",
"sys_rbac_region": "/sys/rbac/region"
};
/**
* 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

@ -35,7 +35,7 @@ export function createRouteGuard(router: Router) {
const needLogin = !to.meta.constant; const needLogin = !to.meta.constant;
const routeRoles = to.meta.roles || []; const routeRoles = to.meta.roles || [];
const hasRole = authStore.userInfo.authorities.some(role => routeRoles.includes(role)); const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole; const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;
// if it is login route when logged in, then switch to the root page // if it is login route when logged in, then switch to the root page

View File

@ -1,12 +1,6 @@
import type { App } from 'vue'; import type { App } from 'vue';
import { import { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
type RouterHistory, import type { RouterHistory } from 'vue-router';
createMemoryHistory,
createRouter,
createWebHashHistory,
createWebHistory
} from 'vue-router';
import { createBuiltinVueRoutes } from './routes/builtin';
import { createRouterGuard } from './guard'; import { createRouterGuard } from './guard';
const { VITE_ROUTER_HISTORY_MODE = 'history', VITE_BASE_URL } = import.meta.env; 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({ export const router = createRouter({
history: historyCreatorMap[VITE_ROUTER_HISTORY_MODE](VITE_BASE_URL), history: historyCreatorMap[VITE_ROUTER_HISTORY_MODE](VITE_BASE_URL),
routes: createBuiltinVueRoutes() routes: []
}); });
/** Setup Vue Router */ /** 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,40 +1,39 @@
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types'; import type { AutoRouterRedirect, AutoRouterRoute } from '@elegant-router/types';
import { generatedRoutes } from '../elegant/routes'; import { routes } from '../_generated/routes';
import { layouts, views } from '../elegant/imports'; import { layouts, views } from '../_generated/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform'; import { transformToVueRoutes } from '../_generated/transformer';
/**
* custom routes
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [];
/** create routes when the auth route mode is static */ /** create routes when the auth route mode is static */
export function createStaticRoutes() { export function createStaticRoutes() {
const constantRoutes: ElegantRoute[] = []; const constantRoutes: AutoRouterRoute[] = [];
const authRoutes: AutoRouterRoute[] = [];
const authRoutes: ElegantRoute[] = []; let rootRoute: AutoRouterRedirect | undefined;
[...customRoutes, ...generatedRoutes].forEach(item => { routes.forEach(item => {
if (item.meta?.constant) { if (item.meta?.constant) {
constantRoutes.push(item); constantRoutes.push(item);
} else { } else {
authRoutes.push(item); authRoutes.push(item);
} }
if (item.name === 'Root') {
rootRoute = item as AutoRouterRedirect;
}
}); });
return { return {
constantRoutes, constantRoutes,
authRoutes authRoutes,
rootRoute: rootRoute as AutoRouterRedirect
}; };
} }
/** /**
* Get auth vue routes * Get auth vue routes
* *
* @param routes Elegant routes * @param authRoutes Elegant routes
*/ */
export function getAuthVueRoutes(routes: ElegantConstRoute[]) { export function getAuthVueRoutes(authRoutes: AutoRouterRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views); return transformToVueRoutes(authRoutes, layouts, views);
} }

View File

@ -3,15 +3,15 @@ import { request } from '../request';
/** /**
* Login * Login
* *
* @param username User name * @param userName User name
* @param password Password * @param password Password
*/ */
export function fetchLogin(username: string, password: string) { export function fetchLogin(userName: string, password: string) {
return request<Api.Auth.LoginToken>({ return request<Api.Auth.LoginToken>({
url: '/authorize/login', url: '/auth/login',
method: 'post', method: 'post',
data: { data: {
username, userName,
password password
} }
}); });
@ -19,7 +19,7 @@ export function fetchLogin(username: string, password: string) {
/** Get user info */ /** Get user info */
export function fetchGetUserInfo() { export function fetchGetUserInfo() {
return request<Api.Auth.UserInfo>({ url: '/authorize/getUserInfo' }); return request<Api.Auth.UserInfo>({ url: '/auth/getUserInfo' });
} }
/** /**
@ -29,7 +29,7 @@ export function fetchGetUserInfo() {
*/ */
export function fetchRefreshToken(refreshToken: string) { export function fetchRefreshToken(refreshToken: string) {
return request<Api.Auth.LoginToken>({ return request<Api.Auth.LoginToken>({
url: '/authorize/refreshToken', url: '/auth/refreshToken',
method: 'post', method: 'post',
data: { data: {
refreshToken refreshToken

View File

@ -1,4 +1,2 @@
export * from './auth'; export * from './auth';
export * from './route'; export * from './route';
export * from './sys/core/dictionary';
export * from './sys/rbac/region';

View File

@ -1,81 +0,0 @@
import { request } from '../../../request';
export function fetchDictionaryPaginate(pageRequest: Api.Common.PageRequest<Api.Sys.Core.DictionaryDTO>) {
return request<Api.Common.PageResponse<Api.Sys.Core.DictionaryVO>>({
url: '/dictionary/paginate',
method: 'post',
data: pageRequest
});
}
export function fetchDictionaryInsert(dictionaryDTO: Api.Sys.Core.DictionaryDTO) {
return request({
url: '/dictionary/insert',
method: 'post',
data: dictionaryDTO
});
}
export function fetchDictionaryUpdate(dictionaryDTO: Api.Sys.Core.DictionaryDTO) {
return request({
url: '/dictionary/update',
method: 'post',
data: dictionaryDTO
});
}
export function fetchDictionaryDelete(id: string) {
return request({
url: '/dictionary/delete',
method: 'post',
data: {
id
}
});
}
export function fetchDictionaryDeleteBatch(ids: string[]) {
return request({
url: '/dictionary/deleteBatch',
method: 'post',
data: {
ids
}
});
}
export function fetchDictionaryItemTreeList(dictionaryId: string) {
return request<Api.Sys.Core.DictionaryItemVO[]>({
url: '/dictionaryItem/tree',
method: 'post',
data: {
dictionaryId
}
});
}
export function fetchDictionaryItemInsert(dictionaryItemDTO: Api.Sys.Core.DictionaryItemDTO) {
return request({
url: '/dictionaryItem/insert',
method: 'post',
data: dictionaryItemDTO
});
}
export function fetchDictionaryItemUpdate(dictionaryItemDTO: Api.Sys.Core.DictionaryItemDTO) {
return request({
url: '/dictionaryItem/update',
method: 'post',
data: dictionaryItemDTO
});
}
export function fetchDictionaryItemDelete(id: string) {
return request({
url: '/dictionaryItem/delete',
method: 'post',
data: {
id
}
});
}

View File

@ -1,45 +0,0 @@
import { request } from '../../../request';
export function fetchRegionPaginate(pageRequest: Api.Common.PageRequest<Api.Sys.Rbac.RegionDTO>) {
return request<Api.Common.PageResponse<Api.Sys.Rbac.RegionVO>>({
url: '/region/paginate',
method: 'post',
data: pageRequest
});
}
export function fetchRegionInsert(regionDTO: Api.Sys.Rbac.RegionDTO) {
return request<boolean>({
url: '/region/insert',
method: 'post',
data: regionDTO
});
}
export function fetchRegionUpdate(regionDTO: Api.Sys.Rbac.RegionDTO) {
return request<boolean>({
url: '/region/update',
method: 'post',
data: regionDTO
});
}
export function fetchRegionDelete(id: string) {
return request<boolean>({
url: '/region/delete',
method: 'post',
data: {
id
}
});
}
export function fetchRegionDeleteBatch(ids: string[]) {
return request<number>({
url: '/region/deleteBatch',
method: 'post',
data: {
ids
}
});
}

View File

@ -48,7 +48,7 @@ export const request = createFlatRequest(
handleLogout(); handleLogout();
window.removeEventListener('beforeunload', handleLogout); window.removeEventListener('beforeunload', handleLogout);
request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.message); request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg);
} }
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
@ -60,15 +60,15 @@ export const request = createFlatRequest(
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.message)) { if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.message]; request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
// prevent the user from refreshing the page // prevent the user from refreshing the page
window.addEventListener('beforeunload', handleLogout); window.addEventListener('beforeunload', handleLogout);
window.$dialog?.error({ window.$dialog?.error({
title: $t('common.error'), title: $t('common.error'),
content: response.data.message, content: response.data.msg,
positiveText: $t('common.confirm'), positiveText: $t('common.confirm'),
maskClosable: false, maskClosable: false,
closeOnEsc: false, closeOnEsc: false,
@ -106,7 +106,7 @@ export const request = createFlatRequest(
// get backend error message and code // get backend error message and code
if (error.code === BACKEND_ERROR_CODE) { if (error.code === BACKEND_ERROR_CODE) {
message = error.response?.data?.message || message; message = error.response?.data?.msg || message;
backendErrorCode = String(error.response?.data?.code || ''); backendErrorCode = String(error.response?.data?.code || '');
} }

View File

@ -17,7 +17,7 @@ async function handleRefreshToken() {
const rToken = localStg.get('refreshToken') || ''; const rToken = localStg.get('refreshToken') || '';
const { error, data } = await fetchRefreshToken(rToken); const { error, data } = await fetchRefreshToken(rToken);
if (!error) { if (!error) {
localStg.set('token', data?.accessToken); localStg.set('token', data.token);
localStg.set('refreshToken', data.refreshToken); localStg.set('refreshToken', data.refreshToken);
return true; return true;
} }

View File

@ -22,9 +22,9 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const token = ref(getToken()); const token = ref(getToken());
const userInfo: Api.Auth.UserInfo = reactive({ const userInfo: Api.Auth.UserInfo = reactive({
id: '', userId: '',
username: '', userName: '',
authorities: [], roles: [],
buttons: [] buttons: []
}); });
@ -32,7 +32,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const isStaticSuper = computed(() => { const isStaticSuper = computed(() => {
const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env; const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env;
return VITE_AUTH_ROUTE_MODE === 'static' && userInfo.authorities.includes(VITE_STATIC_SUPER_ROLE); return VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.includes(VITE_STATIC_SUPER_ROLE);
}); });
/** Is login */ /** Is login */
@ -56,12 +56,12 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
/** Record the user ID of the previous login session Used to compare with the current user ID on next login */ /** Record the user ID of the previous login session Used to compare with the current user ID on next login */
function recordUserId() { function recordUserId() {
if (!userInfo.id) { if (!userInfo.userId) {
return; return;
} }
// Store current user ID locally for next login comparison // Store current user ID locally for next login comparison
localStg.set('lastLoginUserId', userInfo.id); localStg.set('lastLoginUserId', userInfo.userId);
} }
/** /**
@ -70,14 +70,14 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
* @returns {boolean} Whether to clear all tabs * @returns {boolean} Whether to clear all tabs
*/ */
function checkTabClear(): boolean { function checkTabClear(): boolean {
if (!userInfo.id) { if (!userInfo.userId) {
return false; return false;
} }
const lastLoginUserId = localStg.get('lastLoginUserId'); const lastLoginUserId = localStg.get('lastLoginUserId');
// Clear all tabs if current user is different from previous user // Clear all tabs if current user is different from previous user
if (!lastLoginUserId || lastLoginUserId !== userInfo.id) { if (!lastLoginUserId || lastLoginUserId !== userInfo.userId) {
localStg.remove('globalTabs'); localStg.remove('globalTabs');
tabStore.clearTabs(); tabStore.clearTabs();
@ -117,7 +117,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
window.$notification?.success({ window.$notification?.success({
title: $t('page.login.common.loginSuccess'), title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { userName: userInfo.username }), content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
duration: 4500 duration: 4500
}); });
} }
@ -130,14 +130,14 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
async function loginByToken(loginToken: Api.Auth.LoginToken) { async function loginByToken(loginToken: Api.Auth.LoginToken) {
// 1. stored in the localStorage, the later requests need it in headers // 1. stored in the localStorage, the later requests need it in headers
localStg.set('token', loginToken.accessToken); localStg.set('token', loginToken.token);
localStg.set('refreshToken', loginToken.refreshToken); localStg.set('refreshToken', loginToken.refreshToken);
// 2. get user info // 2. get user info
const pass = await getUserInfo(); const pass = await getUserInfo();
if (pass) { if (pass) {
token.value = loginToken.accessToken; token.value = loginToken.token;
return true; return true;
} }

View File

@ -177,7 +177,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Init auth route */ /** Init auth route */
async function initAuthRoute() { async function initAuthRoute() {
// check if user info is initialized // check if user info is initialized
if (!authStore.userInfo.id) { if (!authStore.userInfo.userId) {
await authStore.initUserInfo(); await authStore.initUserInfo();
} }
@ -197,7 +197,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
if (authStore.isStaticSuper) { if (authStore.isStaticSuper) {
addAuthRoutes(staticAuthRoutes); addAuthRoutes(staticAuthRoutes);
} else { } else {
const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.authorities); const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles);
addAuthRoutes(filteredAuthRoutes); addAuthRoutes(filteredAuthRoutes);
} }

View File

@ -18,7 +18,6 @@ import {
getTabByRoute, getTabByRoute,
getTabIdByRoute, getTabIdByRoute,
isTabInTabs, isTabInTabs,
reorderFixedTabs,
updateTabByI18nKey, updateTabByI18nKey,
updateTabsByI18nKey updateTabsByI18nKey
} from './shared'; } from './shared';
@ -249,48 +248,6 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
await clearTabs(excludes); await clearTabs(excludes);
} }
/**
* Fix tab
*
* @param tabId
*/
function fixTab(tabId: string) {
const tabIndex = tabs.value.findIndex(t => t.id === tabId);
if (tabIndex === -1) return;
const tab = tabs.value[tabIndex];
const fixedCount = getFixedTabIds(tabs.value).length;
tab.fixedIndex = fixedCount;
if (tabIndex !== fixedCount) {
tabs.value.splice(tabIndex, 1);
tabs.value.splice(fixedCount, 0, tab);
}
reorderFixedTabs(tabs.value);
}
/**
* Unfix tab
*
* @param tabId
*/
function unfixTab(tabId: string) {
const tabIndex = tabs.value.findIndex(t => t.id === tabId);
if (tabIndex === -1) return;
const tab = tabs.value[tabIndex];
tab.fixedIndex = undefined;
const fixedCount = getFixedTabIds(tabs.value).length;
if (tabIndex !== fixedCount) {
tabs.value.splice(tabIndex, 1);
tabs.value.splice(fixedCount, 0, tab);
}
reorderFixedTabs(tabs.value);
}
/** /**
* Set new label of tab * Set new label of tab
* *
@ -361,7 +318,6 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
/** All tabs */ /** All tabs */
tabs: allTabs, tabs: allTabs,
activeTabId, activeTabId,
homeTab,
initHomeTab, initHomeTab,
initTabStore, initTabStore,
addTab, addTab,
@ -372,8 +328,6 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
clearTabs, clearTabs,
clearLeftTabs, clearLeftTabs,
clearRightTabs, clearRightTabs,
fixTab,
unfixTab,
switchRouteByTab, switchRouteByTab,
setTabLabel, setTabLabel,
resetTabLabel, resetTabLabel,

View File

@ -198,18 +198,6 @@ export function getFixedTabIds(tabs: App.Global.Tab[]) {
return fixedTabs.map(tab => tab.id); return fixedTabs.map(tab => tab.id);
} }
/**
* Reorder fixed tabs fixedIndex
*
* @param tabs
*/
export function reorderFixedTabs(tabs: App.Global.Tab[]) {
const fixedTabs = getFixedTabs(tabs);
fixedTabs.forEach((t, i) => {
t.fixedIndex = i;
});
}
/** /**
* Update tabs label * Update tabs label
* *

View File

@ -24,9 +24,6 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
/** Theme settings */ /** Theme settings */
const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings()); const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings());
/** Optional NaiveUI theme overrides from preset */
const naiveThemeOverrides: Ref<App.Theme.NaiveUIThemeOverride | undefined> = ref(undefined);
/** Watermark time instance with controls */ /** Watermark time instance with controls */
const { now: watermarkTime, pause: pauseWatermarkTime, resume: resumeWatermarkTime } = useNow({ controls: true }); const { now: watermarkTime, pause: pauseWatermarkTime, resume: resumeWatermarkTime } = useNow({ controls: true });
@ -56,7 +53,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
}); });
/** Naive theme */ /** Naive theme */
const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value, naiveThemeOverrides.value)); const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value.recommendColor));
/** /**
* Settings json * Settings json
@ -76,8 +73,8 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
const watermarkContent = computed(() => { const watermarkContent = computed(() => {
const { watermark } = settings.value; const { watermark } = settings.value;
if (watermark.enableUserName && authStore.userInfo.username) { if (watermark.enableUserName && authStore.userInfo.userName) {
return authStore.userInfo.username; return authStore.userInfo.userName;
} }
if (watermark.enableTime) { if (watermark.enableTime) {
@ -201,15 +198,6 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
} }
} }
/**
* Set NaiveUI theme overrides
*
* @param overrides NaiveUI theme overrides or undefined to clear
*/
function setNaiveThemeOverrides(overrides?: App.Theme.NaiveUIThemeOverride) {
naiveThemeOverrides.value = overrides;
}
/** Only run timer when watermark is visible and time display is enabled */ /** Only run timer when watermark is visible and time display is enabled */
function updateWatermarkTimer() { function updateWatermarkTimer() {
const { watermark } = settings.value; const { watermark } = settings.value;
@ -296,7 +284,6 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
updateThemeColors, updateThemeColors,
setThemeLayout, setThemeLayout,
setWatermarkEnableUserName, setWatermarkEnableUserName,
setWatermarkEnableTime, setWatermarkEnableTime
setNaiveThemeOverrides
}; };
}); });

View File

@ -237,30 +237,23 @@ function getNaiveThemeColors(colors: App.Theme.ThemeColor, recommended = false)
* Get naive theme * Get naive theme
* *
* @param colors Theme colors * @param colors Theme colors
* @param settings Theme settings object * @param [recommended=false] Use recommended color. Default is `false`
* @param overrides Optional manual overrides from preset
*/ */
export function getNaiveTheme( export function getNaiveTheme(colors: App.Theme.ThemeColor, recommended = false) {
colors: App.Theme.ThemeColor,
settings: App.Theme.ThemeSetting,
overrides?: GlobalThemeOverrides
) {
const { primary: colorLoading } = colors; const { primary: colorLoading } = colors;
const theme: GlobalThemeOverrides = { const theme: GlobalThemeOverrides = {
common: { common: {
...getNaiveThemeColors(colors, settings.recommendColor), ...getNaiveThemeColors(colors, recommended),
borderRadius: `${settings.themeRadius}px` borderRadius: '6px'
}, },
LoadingBar: { LoadingBar: {
colorLoading colorLoading
}, },
Tag: { Tag: {
borderRadius: `${settings.themeRadius}px` borderRadius: '6px'
} }
}; };
// If there are overrides, merge them with priority return theme;
// overrides has higher priority than auto-generated theme
return overrides ? defu(overrides, theme) : theme;
} }

View File

@ -6,7 +6,6 @@ html,
body, body,
#app { #app {
height: 100%; height: 100%;
text-autospace: normal;
} }
html { 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-width: thin;
scrollbar-color: $color transparent; 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 { &::-webkit-scrollbar-thumb {
background-color: $color; background-color: $color;
border-radius: $size; border-radius: $size;

View File

@ -2,8 +2,10 @@
"name": "Azir's Preset", "name": "Azir's Preset",
"desc": "It is a cold and elegant preset that Azir likes", "desc": "It is a cold and elegant preset that Azir likes",
"i18nkey": "theme.appearance.preset.azir", "i18nkey": "theme.appearance.preset.azir",
"version": "1.0.1", "version": "1.0.0",
"themeScheme": "light", "themeScheme": "light",
"grayscale": false,
"colourWeakness": false,
"recommendColor": true, "recommendColor": true,
"themeColor": "#78a878", "themeColor": "#78a878",
"otherColor": { "otherColor": {
@ -13,6 +15,55 @@
"error": "#c49a9a" "error": "#c49a9a"
}, },
"isInfoFollowPrimary": true, "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"
},
"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": { "tokens": {
"light": { "light": {
"colors": { "colors": {
@ -34,19 +85,5 @@
"base-text": "rgb(224, 224, 224)" "base-text": "rgb(224, 224, 224)"
} }
} }
},
"naiveui": {
"Alert": {
"borderRadiusMedium": "12px",
"fontWeightStrong": "600",
"paddingMedium": "0 20px"
},
"Card": {
"borderRadius": "16px",
"paddingMedium": "24px"
},
"Input": {
"borderRadius": "10px"
}
} }
} }

View File

@ -2,12 +2,32 @@
"name": "Compact Preset", "name": "Compact Preset",
"desc": "Compact layout preset for small screens", "desc": "Compact layout preset for small screens",
"i18nkey": "theme.appearance.preset.compact", "i18nkey": "theme.appearance.preset.compact",
"version": "1.0.1", "version": "1.0.0",
"themeRadius": 6, "themeScheme": "light",
"grayscale": false,
"colourWeakness": false,
"recommendColor": false,
"themeColor": "#646cff",
"otherColor": {
"info": "#2080f0",
"success": "#52c41a",
"warning": "#faad14",
"error": "#f5222d"
},
"isInfoFollowPrimary": true,
"layout": {
"mode": "vertical",
"scrollMode": "content"
},
"page": {
"animate": true,
"animateMode": "fade-slide"
},
"header": { "header": {
"height": 48, "height": 48,
"breadcrumb": { "breadcrumb": {
"visible": false "visible": true,
"showIcon": true
}, },
"multilingual": { "multilingual": {
"visible": false "visible": false
@ -20,9 +40,9 @@
"visible": true, "visible": true,
"cache": true, "cache": true,
"height": 36, "height": 36,
"mode": "button", "mode": "button"
"closeTabByMiddleClick": false
}, },
"fixedHeaderAndTab": true,
"sider": { "sider": {
"inverted": false, "inverted": false,
"width": 180, "width": 180,
@ -32,6 +52,38 @@
"mixChildMenuWidth": 180 "mixChildMenuWidth": 180
}, },
"footer": { "footer": {
"visible": false "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

@ -2,19 +2,18 @@
"name": "Dark Preset", "name": "Dark Preset",
"desc": "Dark theme preset for night time usage", "desc": "Dark theme preset for night time usage",
"i18nkey": "theme.appearance.preset.dark", "i18nkey": "theme.appearance.preset.dark",
"version": "1.0.1", "version": "1.0.0",
"themeScheme": "dark", "themeScheme": "dark",
"grayscale": false, "grayscale": false,
"colourWeakness": false, "colourWeakness": false,
"recommendColor": false, "recommendColor": false,
"themeColor": "#646cff", "themeColor": "#409eff",
"otherColor": { "otherColor": {
"info": "#2080f0", "info": "#2080f0",
"success": "#52c41a", "success": "#52c41a",
"warning": "#faad14", "warning": "#faad14",
"error": "#f5222d" "error": "#f5222d"
}, },
"themeRadius": 6,
"isInfoFollowPrimary": true, "isInfoFollowPrimary": true,
"layout": { "layout": {
"mode": "vertical", "mode": "vertical",
@ -41,12 +40,11 @@
"visible": true, "visible": true,
"cache": true, "cache": true,
"height": 44, "height": 44,
"mode": "chrome", "mode": "chrome"
"closeTabByMiddleClick": false
}, },
"fixedHeaderAndTab": true, "fixedHeaderAndTab": true,
"sider": { "sider": {
"inverted": false, "inverted": true,
"width": 220, "width": 220,
"collapsedWidth": 64, "collapsedWidth": 64,
"mixWidth": 90, "mixWidth": 90,

View File

@ -14,7 +14,6 @@
"warning": "#faad14", "warning": "#faad14",
"error": "#f5222d" "error": "#f5222d"
}, },
"themeRadius": 6,
"isInfoFollowPrimary": true, "isInfoFollowPrimary": true,
"layout": { "layout": {
"mode": "vertical", "mode": "vertical",
@ -41,8 +40,7 @@
"visible": true, "visible": true,
"cache": true, "cache": true,
"height": 44, "height": 44,
"mode": "chrome", "mode": "chrome"
"closeTabByMiddleClick": false
}, },
"fixedHeaderAndTab": true, "fixedHeaderAndTab": true,
"sider": { "sider": {

View File

@ -4,8 +4,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
grayscale: false, grayscale: false,
colourWeakness: false, colourWeakness: false,
recommendColor: false, recommendColor: false,
themeColor: '#ec4899', themeColor: '#646cff',
themeRadius: 6,
otherColor: { otherColor: {
info: '#2080f0', info: '#2080f0',
success: '#52c41a', success: '#52c41a',
@ -31,15 +30,14 @@ export const themeSettings: App.Theme.ThemeSetting = {
visible: true visible: true
}, },
globalSearch: { globalSearch: {
visible: false visible: true
} }
}, },
tab: { tab: {
visible: true, visible: true,
cache: true, cache: true,
height: 44, height: 44,
mode: 'chrome', mode: 'chrome'
closeTabByMiddleClick: false
}, },
fixedHeaderAndTab: true, fixedHeaderAndTab: true,
sider: { sider: {
@ -48,11 +46,10 @@ export const themeSettings: App.Theme.ThemeSetting = {
collapsedWidth: 64, collapsedWidth: 64,
mixWidth: 90, mixWidth: 90,
mixCollapsedWidth: 64, mixCollapsedWidth: 64,
mixChildMenuWidth: 200, mixChildMenuWidth: 200
autoSelectFirstMenu: false
}, },
footer: { footer: {
visible: false, visible: true,
fixed: false, fixed: false,
height: 48, height: 48,
right: true right: true

View File

@ -6,14 +6,14 @@ declare namespace Api {
*/ */
namespace Auth { namespace Auth {
interface LoginToken { interface LoginToken {
accessToken: string; token: string;
refreshToken: string; refreshToken: string;
} }
interface UserInfo { interface UserInfo {
id: string; userId: string;
username: string; userName: string;
authorities: string[]; roles: string[];
buttons: string[]; buttons: string[];
} }
} }

View File

@ -5,19 +5,38 @@
*/ */
declare namespace Api { declare namespace Api {
namespace Common { namespace Common {
/** 带查询参数的分页请求 */ interface CreatedUpdatedDTO {
interface PageRequest<T> { /** 创建人 */
pageIndex: number; createdBy: string;
pageSize: number; /** 创建时间 */
query: T; createdTime: string;
/** 更新人 */
updatedBy: string;
/** 更新时间 */
updatedTime: string;
} }
/** 分页响应 */ interface CommonRecordDTO extends CreatedUpdatedDTO {
interface PageResponse<T> { /** 主键 */
total: number; id: number;
pageIndex: number; /**
* 启用状态 Enable status
*
* - "1": 启用
* - "2": 禁用
*/
status: Union.EnableStatus | null;
}
interface PaginationQueryDTO {
page: number;
pageSize: number; pageSize: number;
records: T[]; sort: string;
}
interface PaginationDTO extends Pick<PaginationQueryDTO, 'page' | 'pageSize'> {
/** 总条数 */
total: number;
} }
} }
} }

42
src/typings/api/menu.d.ts vendored Normal file
View File

@ -0,0 +1,42 @@
declare namespace Api {
namespace Menu {
type RouteKey = import('@elegant-router/types').RouteKey;
interface Menu extends Common.CommonRecordDTO {
/** 菜单名称 */
name: string;
/** 菜单类型 */
menuType: Union.MenuType;
/** 菜单描述 */
description?: string | null;
/** 菜单图标 */
icon?: string | null;
/** 菜单图标类型 */
iconType?: string | null;
/** 父级菜单ID */
parentId?: number | null;
/** 菜单排序 */
order?: number | null;
/** 是否隐藏 */
isHidden?: boolean | null;
/** iframe 页面URL */
iframeUrl?: string | null;
/** 链接 */
href?: string | null;
/** 路由名称 */
routeName?: RouteKey | null;
/** 路由查询参数 */
routeQueries?: Record<string, string | number | boolean> | null;
/** 路由路径参数 */
routeParams?: Record<string, string | number | boolean> | null;
}
interface MenuListDTO extends Common.PaginationDTO {
list: Menu[];
}
interface MenuTreeDTO extends Menu {
children?: MenuTreeDTO[];
}
}
}

View File

@ -1,19 +1,34 @@
declare namespace Api { declare namespace Api {
/**
* namespace Route
*
* backend api module: "route"
*/
namespace Route { namespace Route {
type ElegantConstRoute = import('@elegant-router/types').ElegantConstRoute; interface RouteDTO extends Common.CommonRecordDTO {
/** 路由路径 */
interface MenuRoute extends ElegantConstRoute { path: string;
id: string; /** 路由名称 */
name: string;
/** 路由布局 */
layout: string;
/** 路由组件 */
component: string;
/** 重定向路径 */
redirect?: string | null;
/** 路由图标 */
icon?: string | null;
/** 路由图标类型 */
iconType?: string | null;
/** 是否缓存 */
isCache?: boolean | null;
/** 是否支持多开 */
isMultiple?: boolean | null;
/** 固定索引 */
fixedIndex?: number | null;
/** 是否需要权限 */
requireAuth?: boolean | null;
/** 路由属性 */
props?: Record<string, string | number | boolean> | null;
} }
interface UserRoute { interface RouteListDTO extends Common.PaginationDTO {
routes: MenuRoute[]; list: RouteDTO[];
home: import('@elegant-router/types').LastLevelRouteKey;
} }
} }
} }

View File

@ -1,43 +0,0 @@
declare namespace Api {
namespace Sys {
namespace Core {
// ******************** sys_core_dictionary ********************
type DictionaryType = 'enum' | 'tree';
interface DictionaryVO {
id: string;
name: string;
code: string;
type: DictionaryType;
description: string | null;
createTime: string;
updateTime: string;
}
interface DictionaryDTO {
id: string | null;
name: string | null;
code: string | null;
type: DictionaryType | null;
description: string | null;
}
interface DictionaryItemVO {
id: string;
dictionaryId: string | null;
name: string;
code: string;
sort: number;
description: string | null;
createTime: string;
updateTime: string;
children: DictionaryItemVO[];
}
interface DictionaryItemDTO {
id: string | null;
dictionaryId: string;
name: string;
code: string;
sort: number;
description: string | null;
}
}
}
}

View File

@ -1,34 +0,0 @@
declare namespace Api {
namespace Sys {
namespace Rbac {
// ******************** sys_rbac_region ********************
interface RegionVO {
id: string;
parentId: string | null;
parentCode: string | null;
rootId: string | null;
rootCode: string | null;
name: string;
code: string;
extCode: string | null;
sort: number;
description: string | null;
createTime: string;
updateTime: string;
children: RegionVO[];
}
interface RegionDTO {
id: string | null;
parentId: string | null;
parentCode: string | null;
rootId: string | null;
rootCode: string | null;
name: string | null;
code: string | null;
extCode: string | null;
sort: number | null;
description: string | null;
}
}
}
}

31
src/typings/api/union.d.ts vendored Normal file
View File

@ -0,0 +1,31 @@
declare namespace Api {
namespace Union {
/**
* 是否 Yes or No
*
* - "Y": yes
* - "N": no
*/
type YesOrNo = 'Y' | 'N';
/**
* 启用状态 Enable status
*
* - "1": 启用
* - "2": 禁用
*/
type EnableStatus = '1' | '2';
/**
* 菜单类型
*
* - "directory": 目录
* - "menu": 菜单
* - "iframe": iframe 页面iframe 嵌入页面)
* - "link": 链接(跳转外部链接)
* - "page": 页面(不展示在菜单中,可用于作为菜单类型的子菜单展示在面包屑中)
* - "other": 其他(作用自行定义)
*/
type MenuType = 'page' | 'directory' | 'menu' | 'iframe' | 'link' | 'other';
}
}

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

@ -4,9 +4,6 @@ declare namespace App {
namespace Theme { namespace Theme {
type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber; type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber;
/** NaiveUI theme overrides that can be specified in preset */
type NaiveUIThemeOverride = import('naive-ui').GlobalThemeOverrides;
/** Theme setting */ /** Theme setting */
interface ThemeSetting { interface ThemeSetting {
/** Theme scheme */ /** Theme scheme */
@ -19,8 +16,6 @@ declare namespace App {
recommendColor: boolean; recommendColor: boolean;
/** Theme color */ /** Theme color */
themeColor: string; themeColor: string;
/** Theme radius */
themeRadius: number;
/** Other color */ /** Other color */
otherColor: OtherColor; otherColor: OtherColor;
/** Whether info color is followed by the primary color */ /** Whether info color is followed by the primary color */
@ -74,8 +69,6 @@ declare namespace App {
height: number; height: number;
/** Tab mode */ /** Tab mode */
mode: UnionKey.ThemeTabMode; mode: UnionKey.ThemeTabMode;
/** Whether to close tab by middle click */
closeTabByMiddleClick: boolean;
}; };
/** Fixed header and tab */ /** Fixed header and tab */
fixedHeaderAndTab: boolean; fixedHeaderAndTab: boolean;
@ -96,8 +89,6 @@ declare namespace App {
mixCollapsedWidth: number; mixCollapsedWidth: number;
/** Child menu width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or 'top-hybrid-header-first' */ /** Child menu width when the layout is 'vertical-mix', 'top-hybrid-sidebar-first', or 'top-hybrid-header-first' */
mixChildMenuWidth: number; mixChildMenuWidth: number;
/** Whether to auto select the first submenu */
autoSelectFirstMenu: boolean;
}; };
/** Footer */ /** Footer */
footer: { footer: {
@ -188,9 +179,9 @@ declare namespace App {
type VNode = import('vue').VNode; type VNode = import('vue').VNode;
type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded; type RouteLocationNormalizedLoaded = import('vue-router').RouteLocationNormalizedLoaded;
type RouteKey = import('@elegant-router/types').RouteKey; type RouteKey = import('@elegant-router/types').RouteKey;
type RouteMap = import('@elegant-router/types').RouteMap; type RoutePathMap = import('@elegant-router/types').RoutePathMap;
type RoutePath = import('@elegant-router/types').RoutePath; type RoutePath = import('@elegant-router/types').RoutePath;
type LastLevelRouteKey = import('@elegant-router/types').LastLevelRouteKey; type RouteFileKey = import('@elegant-router/types').RouteFileKey;
/** The router push options */ /** The router push options */
type RouterPushOptions = { type RouterPushOptions = {
@ -208,83 +199,31 @@ declare namespace App {
showMenu?: boolean; showMenu?: boolean;
} }
type MenuType = 'page' | 'directory' | 'menu' | 'iframe' | 'link' | 'button' | 'other';
/** The global menu */ /** The global menu */
type Menu = { interface Menu {
/** id: number;
* The menu key name: string;
* menuType: MenuType;
* Equal to the route key description?: string | null;
*/ icon?: string | null;
key: string; iconType?: string | null;
/** The menu label */ parentId?: number | null;
label: string; order?: number | null;
/** The menu i18n key */ isHidden?: boolean | null;
i18nKey?: I18n.I18nKey | null; iframeUrl?: string | null;
/** The route key */ href?: string | null;
routeKey: RouteKey; routeName?: RouteKey | null;
/** The route path */ routeQueries?: Record<string, string | number | boolean> | null;
routePath: RoutePath; routeParams?: Record<string, string | number | boolean> | null;
/** The menu icon */ }
icon?: () => VNode;
/** The menu children */
children?: Menu[];
};
type Breadcrumb = Omit<Menu, 'children'> & {
options?: Breadcrumb[];
};
/** Tab route */
type TabRoute = Pick<RouteLocationNormalizedLoaded, 'name' | 'path' | 'meta'> &
Partial<Pick<RouteLocationNormalizedLoaded, 'fullPath' | 'query' | 'matched'>>;
/** The global tab */
type Tab = {
/** The tab id */
id: string;
/** The tab label */
label: string;
/**
* The new tab label
*
* If set, the tab label will be replaced by this value
*/
newLabel?: string;
/**
* The old tab label
*
* when reset the tab label, the tab label will be replaced by this value
*/
oldLabel?: string;
/** The tab route key */
routeKey: LastLevelRouteKey;
/** The tab route path */
routePath: RouteMap[LastLevelRouteKey];
/** The tab route full path */
fullPath: string;
/** The tab fixed index */
fixedIndex?: number | null;
/**
* Tab icon
*
* Iconify icon
*/
icon?: string;
/**
* Tab local icon
*
* Local icon
*/
localIcon?: string;
/** I18n key */
i18nKey?: I18n.I18nKey | null;
};
/** Form rule */ /** Form rule */
type FormRule = import('naive-ui').FormItemRule; type FormRule = import('naive-ui').FormItemRule;
/** The global dropdown key */ /** The global dropdown key */
type DropdownKey = 'closeCurrent' | 'closeOther' | 'closeLeft' | 'closeRight' | 'closeAll' | 'pin' | 'unpin'; type DropdownKey = 'closeCurrent' | 'closeOther' | 'closeLeft' | 'closeRight' | 'closeAll';
} }
/** /**
@ -359,7 +298,6 @@ declare namespace App {
yes: string; yes: string;
no: string; no: string;
}; };
children: string;
}; };
request: { request: {
logout: string; logout: string;
@ -387,9 +325,6 @@ declare namespace App {
} & Record<Theme.ThemeColorKey, string>; } & Record<Theme.ThemeColorKey, string>;
recommendColor: string; recommendColor: string;
recommendColorDesc: string; recommendColorDesc: string;
themeRadius: {
title: string;
};
preset: { preset: {
title: string; title: string;
apply: string; apply: string;
@ -413,8 +348,6 @@ declare namespace App {
cacheTip: string; cacheTip: string;
height: string; height: string;
mode: { title: string } & Record<UnionKey.ThemeTabMode, string>; mode: { title: string } & Record<UnionKey.ThemeTabMode, string>;
closeByMiddleClick: string;
closeByMiddleClickTip: string;
}; };
header: { header: {
title: string; title: string;
@ -432,8 +365,6 @@ declare namespace App {
mixWidth: string; mixWidth: string;
mixCollapsedWidth: string; mixCollapsedWidth: string;
mixChildMenuWidth: string; mixChildMenuWidth: string;
autoSelectFirstMenu: string;
autoSelectFirstMenuTip: string;
}; };
footer: { footer: {
title: string; title: string;
@ -554,51 +485,6 @@ declare namespace App {
}; };
creativity: string; creativity: string;
}; };
sys: {
core: {
dictionary: {
title: string;
fields: {
id: string;
name: string;
code: string;
type: string;
description: string;
createTime: string;
updateTime: string;
};
options: {
type: {
enum: string;
tree: string;
};
};
item: {
title: string;
fields: {
name: string;
code: string;
sort: string;
description: string;
};
};
};
};
rbac: {
region: {
title: string;
fields: {
name: string;
code: string;
extCode: string;
sort: string;
description: string;
createTime: string;
updateTime: string;
};
};
};
};
}; };
form: { form: {
required: string; required: string;
@ -681,11 +567,9 @@ declare namespace App {
/** The backend service response code */ /** The backend service response code */
code: string; code: string;
/** The backend service response message */ /** The backend service response message */
message: string; msg: string;
/** The backend service response data */ /** The backend service response data */
data: T; data: T;
/** The backend service response timestamp */
timestamp: number;
}; };
/** The demo backend service response data */ /** The demo backend service response data */

View File

@ -1,12 +1,8 @@
/* eslint-disable */ /* eslint-disable */
// @ts-nocheck // @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components // Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
import { GlobalComponents } from 'vue' // biome-ignore lint: disable
export {} export {}
/* prettier-ignore */ /* prettier-ignore */
@ -21,21 +17,14 @@ declare module 'vue' {
FullScreen: typeof import('./../components/common/full-screen.vue')['default'] FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default'] IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default'] IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default'] IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default'] IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default'] IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default'] IconLocalLogo: typeof import('~icons/local/logo')['default']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default'] IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default'] IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default'] IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default'] IconTooltip: typeof import('./../components/common/icon-tooltip.vue')['default']
IconUilSearch: typeof import('~icons/uil/search')['default'] IconUilSearch: typeof import('~icons/uil/search')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
@ -48,10 +37,7 @@ declare module 'vue' {
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox'] NCheckbox: typeof import('naive-ui')['NCheckbox']
NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
NColorPicker: typeof import('naive-ui')['NColorPicker'] NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer'] NDrawer: typeof import('naive-ui')['NDrawer']
@ -60,7 +46,6 @@ declare module 'vue' {
NEmpty: typeof import('naive-ui')['NEmpty'] NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm'] NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi'] NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
NInput: typeof import('naive-ui')['NInput'] NInput: typeof import('naive-ui')['NInput']
@ -73,10 +58,7 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal'] NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover'] NPopover: typeof import('naive-ui')['NPopover']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
@ -84,6 +66,7 @@ declare module 'vue' {
NSwitch: typeof import('naive-ui')['NSwitch'] NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab'] NTab: typeof import('naive-ui')['NTab']
NTabs: typeof import('naive-ui')['NTabs'] NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing'] NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip'] NTooltip: typeof import('naive-ui')['NTooltip']
NWatermark: typeof import('naive-ui')['NWatermark'] NWatermark: typeof import('naive-ui')['NWatermark']
@ -100,93 +83,3 @@ declare module 'vue' {
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] 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 IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
const IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
const IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
const IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
const IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
const IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
const IconIcRoundSearch: typeof import('~icons/ic/round-search')['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 IconMdiDrag: typeof import('~icons/mdi/drag')['default']
const IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
const IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
const IconMdiRefresh: typeof import('~icons/mdi/refresh')['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 NCollapse: typeof import('naive-ui')['NCollapse']
const NCollapseItem: typeof import('naive-ui')['NCollapseItem']
const NColorPicker: typeof import('naive-ui')['NColorPicker']
const NDataTable: typeof import('naive-ui')['NDataTable']
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 NFormItemGi: typeof import('naive-ui')['NFormItemGi']
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 NPopconfirm: typeof import('naive-ui')['NPopconfirm']
const NPopover: typeof import('naive-ui')['NPopover']
const NRadio: typeof import('naive-ui')['NRadio']
const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
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,248 +1,100 @@
/* eslint-disable */ /* eslint-disable */
/* prettier-ignore */ /* prettier-ignore */
/* oxlint-disable */
// biome-ignore lint: disable
// Generated by elegant-router // Generated by elegant-router
// Read more: https://github.com/soybeanjs/elegant-router // Read more: https://github.com/soybeanjs/elegant-router
declare module "@elegant-router/types" { 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 = { export type RoutePathMap = {
"root": "/"; "Root": "/";
"not-found": "/:pathMatch(.*)*"; "NotFound": "/:pathMatch(.*)*";
"403": "/403"; "403": "/403";
"404": "/404"; "404": "/404";
"500": "/500"; "500": "/500";
"home": "/home"; "Home": "/home";
"iframe-page": "/iframe-page/:url"; "IframeUrl": "/iframe/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"; "Login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"sys": "/sys"; "ManageMenu": "/manage/menu";
"sys_core": "/sys/core"; "ManageRole": "/manage/role";
"sys_core_dictionary": "/sys/core/dictionary"; "ManageRoute": "/manage/route";
"sys_rbac": "/sys/rbac"; "ManageUser": "/manage/user";
"sys_rbac_region": "/sys/rbac/region"; "Wip": "/wip";
}; };
/** /**
* route key * route key
*/ */
export type RouteKey = keyof RouteMap; export type RouteKey = keyof RoutePathMap;
/** /**
* route path * route path
*/ */
export type RoutePath = RouteMap[RouteKey]; export type RoutePath = RoutePathMap[RouteKey];
/** /**
* custom route key * root route key
*/ */
export type CustomRouteKey = Extract< export type RootRouteKey = 'Root';
RouteKey,
| "root"
| "not-found"
>;
/** /**
* 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< export type BuiltinRouteKey = RootRouteKey | NotFoundRouteKey;
RouteKey,
| "403"
| "404"
| "500"
| "home"
| "iframe-page"
| "login"
| "sys"
>;
/** /**
* the custom first level route key * reuse route key
*/ */
export type CustomFirstLevelRouteKey = Extract< export type ReuseRouteKey = never;
CustomRouteKey,
| "root"
| "not-found"
>;
/** /**
* the last level route key, which has the page file * the route file key, which has it's own file
*/ */
export type LastLevelRouteKey = Extract< export type RouteFileKey = Exclude<RouteKey, BuiltinRouteKey | ReuseRouteKey>;
RouteKey,
| "403"
| "404"
| "500"
| "iframe-page"
| "login"
| "home"
| "sys_core_dictionary"
| "sys_rbac_region"
>;
/** /**
* the custom last level route key * mapped name and path
*/ */
export type CustomLastLevelRouteKey = Extract< type MappedNamePath = {
CustomRouteKey, [K in RouteKey]: { name: K; path: RoutePathMap[K] };
| "root" }[RouteKey];
| "not-found"
>;
/** /**
* 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 * auto router route
*/
export type FirstLevelRouteNotSingleKey = Exclude<FirstLevelRouteKey, SingleLevelRouteKey>;
/**
* the custom first level route key, but not the single level
*/ */
export type CustomFirstLevelRouteNotSingleKey = Exclude<CustomFirstLevelRouteKey, CustomSingleLevelRouteKey>; export type AutoRouterRoute = AutoRouterSingleView | AutoRouterRedirect;
/**
* 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;
} }

View File

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

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

@ -0,0 +1,40 @@
/* 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> }>;
"ManageMenu": RouteRecordInfo<"ManageMenu", "/manage/menu", 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

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
// import { loginModuleRecord } from '@/constants/app'; import { loginModuleRecord } from '@/constants/app';
import { useAuthStore } from '@/store/modules/auth'; import { useAuthStore } from '@/store/modules/auth';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
@ -20,7 +20,7 @@ interface FormModel {
} }
const model: FormModel = reactive({ const model: FormModel = reactive({
userName: 'admin', userName: 'Soybean',
password: '123456' password: '123456'
}); });
@ -39,10 +39,8 @@ async function handleSubmit() {
await authStore.login(model.userName, model.password); await authStore.login(model.userName, model.password);
} }
/*
type AccountKey = 'super' | 'admin' | 'user'; type AccountKey = 'super' | 'admin' | 'user';
interface Account { interface Account {
key: AccountKey; key: AccountKey;
label: string; label: string;
@ -50,7 +48,6 @@ interface Account {
password: string; password: string;
} }
const accounts = computed<Account[]>(() => [ const accounts = computed<Account[]>(() => [
{ {
key: 'super', key: 'super',
@ -71,13 +68,10 @@ const accounts = computed<Account[]>(() => [
password: '123456' password: '123456'
} }
]); ]);
*/
/*
async function handleAccountLogin(account: Account) { async function handleAccountLogin(account: Account) {
await authStore.login(account.userName, account.password); await authStore.login(account.userName, account.password);
} }
*/
</script> </script>
<template> <template>
@ -103,7 +97,6 @@ async function handleAccountLogin(account: Account) {
<NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit"> <NButton type="primary" size="large" round block :loading="authStore.loginLoading" @click="handleSubmit">
{{ $t('common.confirm') }} {{ $t('common.confirm') }}
</NButton> </NButton>
<!--
<div class="flex-y-center justify-between gap-12px"> <div class="flex-y-center justify-between gap-12px">
<NButton class="flex-1" block @click="toggleLoginModule('code-login')"> <NButton class="flex-1" block @click="toggleLoginModule('code-login')">
{{ $t(loginModuleRecord['code-login']) }} {{ $t(loginModuleRecord['code-login']) }}
@ -118,7 +111,6 @@ async function handleAccountLogin(account: Account) {
{{ item.label }} {{ item.label }}
</NButton> </NButton>
</div> </div>
-->
</NSpace> </NSpace>
</NForm> </NForm>
</template> </template>

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> <template>
<NSpace vertical :size="16"> <NSpace vertical :size="16">
<NAlert :title="$t('common.tip')" type="warning"> <NAlert :title="$t('common.warning')" type="warning">
{{ $t('page.home.branchDesc') }} {{ $t('page.home.branchDesc') }}
</NAlert> </NAlert>
<HeaderBanner /> <HeaderBanner />

View File

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

View File

@ -48,7 +48,7 @@ const statisticData = computed<StatisticData[]>(() => [
</div> </div>
<div class="pl-12px"> <div class="pl-12px">
<h3 class="text-18px font-semibold"> <h3 class="text-18px font-semibold">
{{ $t('page.home.greeting', { userName: authStore.userInfo.username }) }} {{ $t('page.home.greeting', { userName: authStore.userInfo.userName }) }}
</h3> </h3>
<p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p> <p class="text-#999 leading-30px">{{ $t('page.home.weatherDesc') }}</p>
</div> </div>

View File

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

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