mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-10-11 20:33:41 +08:00
Compare commits
105 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
af735e87b8 | ||
|
b336841567 | ||
|
b094d6882b | ||
|
b94baa18c1 | ||
|
3eeace94dd | ||
|
ad2f24707a | ||
|
cd9d58d4de | ||
|
2198b982f0 | ||
|
376e2f7d84 | ||
|
069fa8a817 | ||
|
b5a723cb5f | ||
|
79e85bc6b5 | ||
|
bf7183747b | ||
|
aabb2a49c4 | ||
|
7026126a57 | ||
|
c212565248 | ||
|
813d8ce4c7 | ||
|
20a8127434 | ||
|
ec8cadd183 | ||
|
4470cb4e8c | ||
|
8f9a70505c | ||
|
9094b21cbb | ||
|
6259287240 | ||
|
22004ff4dc | ||
|
08827a42a3 | ||
|
6a6eb9afd6 | ||
|
ff51b72dac | ||
|
fe06b8c499 | ||
|
4b63bbcf67 | ||
|
5531a68641 | ||
|
584cd54d6d | ||
|
2bec899031 | ||
|
83862ba76e | ||
|
060c0a91c7 | ||
|
76649e2a08 | ||
|
ddf3823a55 | ||
|
e75fd73f34 | ||
|
1f4647b995 | ||
|
910dfca8bb | ||
|
ffb48b15bc | ||
|
e6086f0f35 | ||
|
9b05d73e6d | ||
|
bd69c00e74 | ||
|
d08a3817d1 | ||
|
d0380ce5cb | ||
|
756f84ab1d | ||
|
5a4f844d24 | ||
|
59faf15229 | ||
|
5bd96b8dd3 | ||
|
8efdb10d86 | ||
|
2aa85c6f93 | ||
|
3ad438984e | ||
|
f1850416c8 | ||
|
086bad474e | ||
|
42b121a117 | ||
|
db64b0e2e9 | ||
|
40d0f8ac1c | ||
|
87b18386f5 | ||
|
4c1c7e6599 | ||
|
ac78b9a394 | ||
|
e379d6ce67 | ||
|
64fc0996e3 | ||
|
bc8dc47d7f | ||
|
6a83f869f9 | ||
|
419ea42343 | ||
|
24ff852180 | ||
|
57b4a9d532 | ||
|
0774a51525 | ||
|
68ea974617 | ||
|
4ea9c85757 | ||
|
fa56e9c9e7 | ||
|
071241f8cb | ||
|
1978397c90 | ||
|
e4d53aa7b5 | ||
|
e7f0a30bd8 | ||
|
4d17cfdcd3 | ||
|
46e90a7208 | ||
|
3b47b5a56f | ||
|
073fbcfc36 | ||
|
0a90dd3764 | ||
|
60f3b14085 | ||
|
18b3f05382 | ||
|
72ccb6bf6d | ||
|
21df57b71c | ||
|
7bd43df5f7 | ||
|
599b4e1947 | ||
|
003e145e77 | ||
|
c4b5c65625 | ||
|
ebe55af7d5 | ||
|
1ed2eb5fdd | ||
|
d3849baaff | ||
|
e3bd397248 | ||
|
42f950f819 | ||
|
3d10ef1a90 | ||
|
6b465c0ebd | ||
|
dbe31eb1dc | ||
|
a1920fcad9 | ||
|
34999971fd | ||
|
1cb3816e48 | ||
|
de9567420a | ||
|
3e61eab498 | ||
|
ec5b1c3f84 | ||
|
1fc34cc5f8 | ||
|
c347528bbb | ||
|
d335df6f46 |
3
.env
3
.env
@@ -43,3 +43,6 @@ VITE_STATIC_SUPER_ROLE=R_SUPER
|
||||
|
||||
# sourcemap
|
||||
VITE_SOURCE_MAP=N
|
||||
|
||||
# Used to differentiate storage across different domains
|
||||
VITE_STORAGE_PREFIX=SOY_
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# backend service base url, prod environment
|
||||
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default
|
||||
VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
|
||||
|
||||
# other backend service base url, prod environment
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# backend service base url, test environment
|
||||
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default
|
||||
VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
|
||||
|
||||
# other backend service base url, test environment
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
|
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -12,7 +12,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "TS Debugger",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/tsx",
|
||||
"runtimeExecutable": "tsx",
|
||||
"skipFiles": ["<node_internals>/**", "${workspaceFolder}/node_modules/**"],
|
||||
"program": "${file}"
|
||||
}
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -14,5 +14,6 @@
|
||||
"i18n-ally.localesPaths": ["src/locales/langs"],
|
||||
"prettier.enable": false,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"unocss.root": ["./"]
|
||||
"unocss.root": ["./"],
|
||||
"vue.server.hybridMode": true
|
||||
}
|
||||
|
367
CHANGELOG.md
367
CHANGELOG.md
@@ -1,6 +1,371 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [v1.2.5](https://github.com/soybeanjs/soybean-admin/compare/v1.2.4...v1.2.5) (2024-06-15)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**: Fix the issue of abnormal tab caching after logout. fixed #495 - by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/495 [<samp>(3eeac)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3eeace9)
|
||||
|
||||
### 🔥 Performance
|
||||
|
||||
- **project**: Initializing the static routing function does not require asynchronization - by **CHENZL** in https://github.com/soybeanjs/soybean-admin/issues/493 [<samp>(2198b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2198b98)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **projects**: optimize code - by @soybeanjs [<samp>(b94ba)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b94baa1)
|
||||
- **types**: Enhance compatibility of global types - by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/494 [<samp>(cd9d5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/cd9d58d)
|
||||
- **utils**: Reduce code indentation and improve readability - by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/496 [<samp>(ad2f2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ad2f247)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**: update CHANGELOG - by @soybeanjs [<samp>(b3368)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b336841)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**: update deps - by @soybeanjs [<samp>(b094d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b094d68)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/Azir-11)
|
||||
[CHENZL](mailto:zlong5568863@qq.com)
|
||||
|
||||
## [v1.2.4](https://github.com/soybeanjs/soybean-admin/compare/v1.2.3...v1.2.4) (2024-06-14)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **projects**:
|
||||
- optimize `setupAppVersionNotification` - by @soybeanjs [<samp>(b5a72)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b5a723c)
|
||||
- get buildTime with timezone 'Asia/Shanghai' - by @soybeanjs [<samp>(069fa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/069fa8a)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs)
|
||||
|
||||
## [v1.2.3](https://github.com/soybeanjs/soybean-admin/compare/v1.2.2...v1.2.3) (2024-06-13)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- fix mobile browser theme issue by adding color-scheme meta tag to index.html - by @KickCashew in https://github.com/soybeanjs/soybean-admin/issues/488 [<samp>(c2125)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c212565)
|
||||
- Fix secondary directory components is empty - by @paynezhuang in https://github.com/soybeanjs/soybean-admin/issues/491 [<samp>(aabb2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/aabb2a4)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**:
|
||||
- Fixed the hyperlink pointing error - by **Azir** [<samp>(20a81)</samp>](https://github.com/soybeanjs/soybean-admin/commit/20a8127)
|
||||
- update README - by @soybeanjs [<samp>(70261)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7026126)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**:
|
||||
- update deps - by @soybeanjs [<samp>(813d8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/813d8ce)
|
||||
- update deps - by @soybeanjs [<samp>(bf718)</samp>](https://github.com/soybeanjs/soybean-admin/commit/bf71837)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/paynezhuang) [](https://github.com/KickCashew)
|
||||
[Azir](mailto:2075125282@qq.com)
|
||||
|
||||
## [v1.2.2](https://github.com/honghuangdc/soybean-admin/compare/v1.2.1...v1.2.2) (2024-06-12)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **projects**: reset scroll position when tab change - by @soybeanjs [<samp>(9094b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/9094b21)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- hide AppVersionNotification in DEV mode - by @sigma-plus in https://github.com/honghuangdc/soybean-admin/issues/482 [<samp>(62592)</samp>](https://github.com/honghuangdc/soybean-admin/commit/6259287)
|
||||
- fix menu-toggler hidden in mobile layout. fixed #483 - by @soybeanjs in https://github.com/honghuangdc/soybean-admin/issues/483 [<samp>(4470c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4470cb4)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**: update README - by @soybeanjs [<samp>(8f9a7)</samp>](https://github.com/honghuangdc/soybean-admin/commit/8f9a705)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/sigma-plus)
|
||||
|
||||
## [v1.2.1](https://github.com/honghuangdc/soybean-admin/compare/v1.2.0...v1.2.1) (2024-06-07)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- fix get user info when page reload - by @soybeanjs [<samp>(ff51b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ff51b72)
|
||||
- fix setupAppVersionNotification render - by @soybeanjs [<samp>(6a6eb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/6a6eb9a)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**: update CHANGELOG - by @soybeanjs [<samp>(fe06b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/fe06b8c)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**: update deps - by @soybeanjs [<samp>(08827)</samp>](https://github.com/honghuangdc/soybean-admin/commit/08827a4)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs)
|
||||
|
||||
## [v1.2.0](https://github.com/soybeanjs/soybean-admin/compare/v1.1.5...v1.2.0) (2024-06-06)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **projects**:
|
||||
- support system new version update notification. close #420 - by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/420 [<samp>(584cd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/584cd54)
|
||||
- get user info in router guard and remove in localStorage. close #459 - by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/459 [<samp>(5531a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5531a68)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**: update CHANGELOG - by @soybeanjs [<samp>(2bec8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2bec899)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs)
|
||||
|
||||
## [v1.1.5](https://github.com/soybeanjs/soybean-admin/compare/v1.1.4...v1.1.5) (2024-06-06)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**: fix register name, CodeLogin => Register - by @m-xlsea in https://github.com/soybeanjs/soybean-admin/issues/478 [<samp>(ddf38)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ddf3823)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**: update deps - by @soybeanjs [<samp>(060c0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/060c0a9)
|
||||
- **projects**: update vscode settings: vue official - by @soybeanjs [<samp>(76649)</samp>](https://github.com/soybeanjs/soybean-admin/commit/76649e2)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/m-xlsea)
|
||||
|
||||
## [v1.1.4](https://github.com/honghuangdc/soybean-admin/compare/v1.1.3...v1.1.4) (2024-06-06)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **utils**: modalLogout bug when esc is pressed - by @sigma-plus in https://github.com/honghuangdc/soybean-admin/issues/470 [<samp>(bd69c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/bd69c00)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **projects**: optimize RouteMeta remarks - by @soybeanjs [<samp>(ffb48)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ffb48b1)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**:
|
||||
- update CHANGELOG - by @soybeanjs [<samp>(756f8)</samp>](https://github.com/honghuangdc/soybean-admin/commit/756f84a)
|
||||
- update Node&pnpm version - by @Azir-11 in https://github.com/honghuangdc/soybean-admin/issues/472 [<samp>(9b05d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/9b05d73)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**:
|
||||
- update deps - by @soybeanjs [<samp>(d0380)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d0380ce)
|
||||
- update deps - by @soybeanjs [<samp>(1f464)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1f4647b)
|
||||
- **projects**:
|
||||
- close http proxy - by @soybeanjs [<samp>(d08a3)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d08a381)
|
||||
- update mock url - by @soybeanjs [<samp>(e6086)</samp>](https://github.com/honghuangdc/soybean-admin/commit/e6086f0)
|
||||
- update vscode settings - by @soybeanjs [<samp>(910df)</samp>](https://github.com/honghuangdc/soybean-admin/commit/910dfca)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/Azir-11) [](https://github.com/sigma-plus)
|
||||
|
||||
## [v1.1.3](https://github.com/soybeanjs/soybean-admin/compare/v1.1.2...v1.1.3) (2024-06-02)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **components**:
|
||||
- Fix the issue of search box popping up repeatedly due to carriage return - by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/468 [<samp>(5bd96)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5bd96b8)
|
||||
- **projects**:
|
||||
- fix click menu search. fixed #466, close #467 - by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/466 and https://github.com/soybeanjs/soybean-admin/issues/467 [<samp>(8efdb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8efdb10)
|
||||
- fix reCacheRoute. fixed #464 - by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/464 [<samp>(59faf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/59faf15)
|
||||
- **styles**:
|
||||
- fix FirstLevelMenu style. fixed #450 - by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/450 [<samp>(db64b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/db64b0e)
|
||||
- fix PinToggler style. fixed #451 - by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/451 [<samp>(42b12)</samp>](https://github.com/soybeanjs/soybean-admin/commit/42b121a)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **components**: accuracy draggable area for TableColumnSetting with animation - by @orangelckc in https://github.com/soybeanjs/soybean-admin/issues/465 [<samp>(2aa85)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2aa85c6)
|
||||
- **projects**: unocss border shortcut - by @soybeanjs [<samp>(40d0f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/40d0f8a)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**: update CHANGELOG - by @soybeanjs [<samp>(87b18)</samp>](https://github.com/soybeanjs/soybean-admin/commit/87b1838)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **other**:
|
||||
- correct spell mistake - by @orangelckc in https://github.com/soybeanjs/soybean-admin/issues/460 [<samp>(086ba)</samp>](https://github.com/soybeanjs/soybean-admin/commit/086bad4)
|
||||
- correct spell mistake - by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/462 [<samp>(f1850)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f185041)
|
||||
- **projects**:
|
||||
- update vscode launch.json - by @soybeanjs [<samp>(4c1c7)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4c1c7e6)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/Azir-11) [](https://github.com/orangelckc)
|
||||
|
||||
## [v1.1.2](https://github.com/soybeanjs/soybean-admin/compare/v1.1.1...v1.1.2) (2024-05-24)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- fix header style & fix button highlight when click global-tab. fixed #446 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/446 [<samp>(64fc0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/64fc099)
|
||||
- fix multi tab page only render once. fixed #441 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/441 [<samp>(e379d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e379d6c)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **projects**: optimize code - by @honghuangdc [<samp>(bc8dc)</samp>](https://github.com/soybeanjs/soybean-admin/commit/bc8dc47)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
|
||||
## [v1.1.1](https://github.com/soybeanjs/soybean-admin/compare/v1.1.0...v1.1.1) (2024-05-20)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **hooks**: add setOptions for useEcharts - by @honghuangdc [<samp>(e4d53)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e4d53aa)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- fix useRouter. fixed #436 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/436 [<samp>(0774a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0774a51)
|
||||
- add error handle when get routes in dynamic route mode. fixed 440 - by @honghuangdc [<samp>(57b4a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/57b4a9d)
|
||||
- **styles**:
|
||||
- fix useTable type - by @honghuangdc [<samp>(07124)</samp>](https://github.com/soybeanjs/soybean-admin/commit/071241f)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**:
|
||||
- update CHANGELOG - by @honghuangdc [<samp>(19783)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1978397)
|
||||
- update README.md - by @honghuangdc [<samp>(fa56e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/fa56e9c)
|
||||
- update README.md - by @honghuangdc [<samp>(419ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/419ea42)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **projects**:
|
||||
- update deps & fix TS error - by @honghuangdc [<samp>(4ea9c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4ea9c85)
|
||||
- update eslint-config & fix code - by @honghuangdc [<samp>(68ea9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/68ea974)
|
||||
- update @elegant-router/vue & add error handle for resolve route. fixed #442 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/442 [<samp>(24ff8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/24ff852)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
|
||||
## [v1.1.0](https://github.com/honghuangdc/soybean-admin/compare/v1.0.9...v1.1.0) (2024-05-07)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **projects**:
|
||||
- support grayscale. fixed #385 - by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/385 [<samp>(d335d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d335df6)
|
||||
- Add prefix to local storage - by **Azir** [<samp>(1fc34)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1fc34cc)
|
||||
- add table showTotal options - by **paynezhuang** [<samp>(3e61e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3e61eab)
|
||||
- add recommend color switch. closed #388 - by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/388 [<samp>(a1920)</samp>](https://github.com/honghuangdc/soybean-admin/commit/a1920fc)
|
||||
- add menu route field - by **paynezhuang** [<samp>(dbe31)</samp>](https://github.com/honghuangdc/soybean-admin/commit/dbe31eb)
|
||||
- support repeated request errors occur once in a short time. close #368, close #369 - by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/368 and https://github.com/honghuangdc/soybean-admin/issues/369 [<samp>(e3bd3)</samp>](https://github.com/honghuangdc/soybean-admin/commit/e3bd397)
|
||||
- close tab by mouse wheel button click - by **JianJroh** [<samp>(d3849)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d3849ba)
|
||||
- page: support manage_menu more options. close #366 - by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/366 [<samp>(c4b5c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c4b5c65)
|
||||
- useTable adds expand to display - by **paynezhuang** [<samp>(0a90d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0a90dd3)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- menu fixedIndexInTab default null - by **paynezhuang** [<samp>(3d10e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3d10ef1)
|
||||
- fix menu-toggler zIndex - by @honghuangdc [<samp>(7bd43)</samp>](https://github.com/honghuangdc/soybean-admin/commit/7bd43df)
|
||||
- fix manage_menu modal style - by @honghuangdc [<samp>(60f3b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/60f3b14)
|
||||
- fix menu data when role is changed. fixed #391 - by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/391 [<samp>(3b47b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3b47b5a)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **projects**: remove deprecated code - by @honghuangdc [<samp>(72ccb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/72ccb6b)
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **projects**:
|
||||
- refactor @sa/color-palette => @sa/color & perf @sa/utils - by @honghuangdc [<samp>(34999)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3499997)
|
||||
- menu-operate-drawer => menu-operate-modal - by @honghuangdc [<samp>(003e1)</samp>](https://github.com/honghuangdc/soybean-admin/commit/003e145)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**:
|
||||
- add CHANGELOG.zh_CN.md - by @honghuangdc [<samp>(18b3f)</samp>](https://github.com/honghuangdc/soybean-admin/commit/18b3f05)
|
||||
- update CHANGELOG - by @honghuangdc [<samp>(4d17c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4d17cfd)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**:
|
||||
- update deps - by @honghuangdc [<samp>(1cb38)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1cb3816)
|
||||
- update deps - by @honghuangdc [<samp>(599b4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/599b4e1)
|
||||
- **projects**:
|
||||
- merge main to v1.1.0 - by @honghuangdc [<samp>(ebe55)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ebe55af)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
[paynezhuang](mailto:paynezhuang@gmail.com), [JianJroh](mailto:rhjian@foxmail.com), [Azir](mailto:2075125282@qq.com)
|
||||
|
||||
## [v1.1.0-beta.2](https://github.com/honghuangdc/soybean-admin/compare/v1.1.0-beta.1...v1.1.0-beta.2) (2024-05-07)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **projects**: useTable adds expand to display - by **paynezhuang** [<samp>(0a90d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0a90dd3)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- fix manage_menu modal style - by @honghuangdc [<samp>(60f3b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/60f3b14)
|
||||
- fix menu data when role is changed. fixed #391 - by @honghuangdc in https://github.com/honghuangdc/soybean-admin/issues/391 [<samp>(3b47b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3b47b5a)
|
||||
|
||||
### 🛠 Optimizations
|
||||
|
||||
- **projects**: remove deprecated code - by @honghuangdc [<samp>(72ccb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/72ccb6b)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
- **projects**: add CHANGELOG.zh_CN.md - by @honghuangdc [<samp>(18b3f)</samp>](https://github.com/honghuangdc/soybean-admin/commit/18b3f05)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
[paynezhuang](mailto:paynezhuang@gmail.com)
|
||||
|
||||
## [v1.1.0-beta.1](https://github.com/soybeanjs/soybean-admin/compare/v1.0.9...v1.1.0-beta.1) (2024-05-07)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- **projects**:
|
||||
- support grayscale. fixed #385 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/385 [<samp>(d335d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d335df6)
|
||||
- Add prefix to local storage - by **Azir** [<samp>(1fc34)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1fc34cc)
|
||||
- add table showTotal options - by **paynezhuang** [<samp>(3e61e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e61eab)
|
||||
- add recommend color switch. closed #388 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/388 [<samp>(a1920)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a1920fc)
|
||||
- add menu route field - by **paynezhuang** [<samp>(dbe31)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dbe31eb)
|
||||
- support repeated request errors occur once in a short time. close #368, close #369 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/368 and https://github.com/soybeanjs/soybean-admin/issues/369 [<samp>(e3bd3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e3bd397)
|
||||
- close tab by mouse wheel button click - by **JianJroh** [<samp>(d3849)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d3849ba)
|
||||
- page: support manage_menu more options. close #366 - by @honghuangdc in https://github.com/soybeanjs/soybean-admin/issues/366 [<samp>(c4b5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c4b5c65)
|
||||
|
||||
### 🐞 Bug Fixes
|
||||
|
||||
- **projects**:
|
||||
- menu fixedIndexInTab default null - by **paynezhuang** [<samp>(3d10e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3d10ef1)
|
||||
- fix menu-toggler zIndex - by @honghuangdc [<samp>(7bd43)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7bd43df)
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **projects**:
|
||||
- refactor @sa/color-palette => @sa/color & perf @sa/utils - by @honghuangdc [<samp>(34999)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3499997)
|
||||
- menu-operate-drawer => menu-operate-modal - by @honghuangdc [<samp>(003e1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/003e145)
|
||||
|
||||
### 🏡 Chore
|
||||
|
||||
- **deps**:
|
||||
- update deps - by @honghuangdc [<samp>(1cb38)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1cb3816)
|
||||
- update deps - by @honghuangdc [<samp>(599b4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/599b4e1)
|
||||
- **projects**:
|
||||
- merge main to v1.1.0 - by @honghuangdc [<samp>(ebe55)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ebe55af)
|
||||
|
||||
### ❤️ Contributors
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
[JianJroh](mailto:rhjian@foxmail.com), [paynezhuang](mailto:paynezhuang@gmail.com), [Azir](mailto:2075125282@qq.com)
|
||||
|
||||
## [v1.0.9](https://github.com/soybeanjs/soybean-admin/compare/v1.0.8...v1.0.9) (2024-05-05)
|
||||
|
||||
### 🚀 Features
|
||||
@@ -137,7 +502,7 @@
|
||||
|
||||
### 💅 Refactors
|
||||
|
||||
- **hooks**: refactor @sa/color-palette - by @honghuangdc [<samp>(93191)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9319173)
|
||||
- **hooks**: refactor @sa/color - by @honghuangdc [<samp>(93191)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9319173)
|
||||
|
||||
### 📖 Documentation
|
||||
|
||||
|
340
CHANGELOG.zh_CN.md
Normal file
340
CHANGELOG.zh_CN.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# 更新日志
|
||||
|
||||
|
||||
## [v1.2.4](https://github.com/soybeanjs/soybean-admin/compare/v1.2.3...v1.2.4) (2024-06-14)
|
||||
|
||||
### 🛠 优化
|
||||
|
||||
- **项目**:
|
||||
- 优化 `setupAppVersionNotification` - 由 @soybeanjs 提交 [<samp>(b5a72)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b5a723c)
|
||||
- 获取 'Asia/Shanghai' 时区的构建时间 - 由 @soybeanjs 提交 [<samp>(069fa)</samp>](https://github.com/soybeanjs/soybean-admin/commit/069fa8a)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs)
|
||||
|
||||
## [v1.2.3](https://github.com/soybeanjs/soybean-admin/compare/v1.2.2...v1.2.3) (2024-06-13)
|
||||
|
||||
### 🐞 修复错误
|
||||
|
||||
- **项目**:
|
||||
- 通过在index.html中添加color-scheme元标签修复移动浏览器主题问题 - 由 @KickCashew 在 https://github.com/soybeanjs/soybean-admin/issues/488 中提交 [<samp>(c2125)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c212565)
|
||||
- 修复二级目录组件为空 - 由 @paynezhuang 在 https://github.com/soybeanjs/soybean-admin/issues/491 中提交 [<samp>(aabb2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/aabb2a4)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**:
|
||||
- 修复超链接指向错误 - 由 **Azir** 提交 [<samp>(20a81)</samp>](https://github.com/soybeanjs/soybean-admin/commit/20a8127)
|
||||
- 更新 README - 由 @soybeanjs 提交 [<samp>(70261)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7026126)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **依赖**:
|
||||
- 更新依赖 - 由 @soybeanjs 提交 [<samp>(813d8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/813d8ce)
|
||||
- 更新依赖 - 由 @soybeanjs 提交 [<samp>(bf718)</samp>](https://github.com/soybeanjs/soybean-admin/commit/bf71837)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/paynezhuang) [](https://github.com/KickCashew)
|
||||
[Azir](mailto:2075125282@qq.com)
|
||||
|
||||
## [v1.2.2](https://github.com/honghuangdc/soybean-admin/compare/v1.2.1...v1.2.2) (2024-06-12)
|
||||
|
||||
### 🚀 特性
|
||||
|
||||
- **项目**: 切换标签时重置滚动位置 - 由 @soybeanjs 提交 [<samp>(9094b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/9094b21)
|
||||
|
||||
### 🐞 修复错误
|
||||
|
||||
- **项目**:
|
||||
- 在DEV模式下隐藏AppVersionNotification - 由 @sigma-plus 在 https://github.com/honghuangdc/soybean-admin/issues/482 中提交 [<samp>(62592)</samp>](https://github.com/honghuangdc/soybean-admin/commit/6259287)
|
||||
- 修复在移动布局中隐藏menu-toggler。fixed #483 - 由 @soybeanjs 在 https://github.com/honghuangdc/soybean-admin/issues/483 中提交 [<samp>(4470c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4470cb4)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**: 更新 README - 由 @soybeanjs 提交 [<samp>(8f9a7)</samp>](https://github.com/honghuangdc/soybean-admin/commit/8f9a705)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/sigma-plus)
|
||||
|
||||
## [v1.2.1](https://github.com/honghuangdc/soybean-admin/compare/v1.2.0...v1.2.1) (2024-06-07)
|
||||
|
||||
### 🐞 修复错误
|
||||
|
||||
- **项目**:
|
||||
- 修复页面重新加载时获取用户信息 - 由 @soybeanjs 提交 [<samp>(ff51b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ff51b72)
|
||||
- 修复setupAppVersionNotification渲染 - 由 @soybeanjs 提交 [<samp>(6a6eb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/6a6eb9a)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**: 更新CHANGELOG - 由 @soybeanjs [<samp>(fe06b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/fe06b8c)完成
|
||||
|
||||
### 🏡 日常任务
|
||||
|
||||
- **依赖**: 更新依赖 - 由 @soybeanjs [<samp>(08827)</samp>](https://github.com/honghuangdc/soybean-admin/commit/08827a4)完成
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs)
|
||||
|
||||
|
||||
## [v1.2.0](https://github.com/soybeanjs/soybean-admin/compare/v1.1.5...v1.2.0) (2024-06-06)
|
||||
|
||||
### 🚀 功能
|
||||
|
||||
- **项目**:
|
||||
- 支持系统新版本更新通知。关闭 #420 - 由 @soybeanjs 在 https://github.com/soybeanjs/soybean-admin/issues/420 [<samp>(584cd)</samp>](https://github.com/soybeanjs/soybean-admin/commit/584cd54)
|
||||
- 在路由守卫中获取用户信息并从localStorage中移除。关闭 #459 - 由 @soybeanjs 在 https://github.com/soybeanjs/soybean-admin/issues/459 [<samp>(5531a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5531a68)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**: 更新CHANGELOG - 由 @soybeanjs [<samp>(2bec8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2bec899)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs)
|
||||
|
||||
## [v1.1.5](https://github.com/soybeanjs/soybean-admin/compare/v1.1.4...v1.1.5) (2024-06-06)
|
||||
|
||||
### 🐞 错误修复
|
||||
|
||||
- **项目**: 修复注册组件名,CodeLogin => Register - 由 @m-xlsea 在 https://github.com/soybeanjs/soybean-admin/issues/478 [<samp>(ddf38)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ddf3823)
|
||||
|
||||
### 🏡 杂务
|
||||
|
||||
- **依赖**: 更新依赖 - 由 @soybeanjs [<samp>(060c0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/060c0a9)
|
||||
- **项目**: 更新 vscode 设置: vue 官方 - 由 @soybeanjs [<samp>(76649)</samp>](https://github.com/soybeanjs/soybean-admin/commit/76649e2)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/m-xlsea)
|
||||
|
||||
## [v1.1.4](https://github.com/honghuangdc/soybean-admin/compare/v1.1.3...v1.1.4) (2024-06-06)
|
||||
|
||||
### 🐞 错误修复
|
||||
|
||||
- **utils**: 修复了按esc键时modalLogout的错误 - 由 @sigma-plus 在 https://github.com/honghuangdc/soybean-admin/issues/470 中提出 [<samp>(bd69c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/bd69c00)
|
||||
|
||||
### 🛠 优化
|
||||
|
||||
- **projects**: 优化了RouteMeta的备注 - 由 @soybeanjs 提出 [<samp>(ffb48)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ffb48b1)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **projects**:
|
||||
- 更新了CHANGELOG - 由 @soybeanjs 提出 [<samp>(756f8)</samp>](https://github.com/honghuangdc/soybean-admin/commit/756f84a)
|
||||
- 更新了Node&pnpm版本 - 由 @Azir-11 在 https://github.com/honghuangdc/soybean-admin/issues/472 中提出 [<samp>(9b05d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/9b05d73)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **deps**:
|
||||
- 更新了依赖 - 由 @soybeanjs 提出 [<samp>(d0380)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d0380ce)
|
||||
- 更新了依赖 - 由 @soybeanjs 提出 [<samp>(1f464)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1f4647b)
|
||||
- **projects**:
|
||||
- 关闭了http代理 - 由 @soybeanjs 提出 [<samp>(d08a3)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d08a381)
|
||||
- 更新了mock url - 由 @soybeanjs 提出 [<samp>(e6086)</samp>](https://github.com/honghuangdc/soybean-admin/commit/e6086f0)
|
||||
- 更新了vscode设置 - 由 @soybeanjs 提出 [<samp>(910df)</samp>](https://github.com/honghuangdc/soybean-admin/commit/910dfca)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/Azir-11) [](https://github.com/sigma-plus)
|
||||
|
||||
## [v1.1.3](https://github.com/soybeanjs/soybean-admin/compare/v1.1.2...v1.1.3) (2024-06-02)
|
||||
|
||||
### 🐞 错误修复
|
||||
|
||||
- **组件**:
|
||||
- 修复了由于回车导致搜索框反复弹出的问题 - 由 @Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/468 中修复 [<samp>(5bd96)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5bd96b8)
|
||||
- **项目**:
|
||||
- 修复点击菜单搜索。修复了 #466,关闭 #467 - 由 @soybeanjs 在 https://github.com/soybeanjs/soybean-admin/issues/466 和 https://github.com/soybeanjs/soybean-admin/issues/467 中修复 [<samp>(8efdb)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8efdb10)
|
||||
- 修复 reCacheRoute。修复了 #464 - 由 @soybeanjs 在 https://github.com/soybeanjs/soybean-admin/issues/464 中修复 [<samp>(59faf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/59faf15)
|
||||
- **样式**:
|
||||
- 修复 FirstLevelMenu 样式。修复了 #450 - 由 @soybeanjs 在 https://github.com/soybeanjs/soybean-admin/issues/450 中修复 [<samp>(db64b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/db64b0e)
|
||||
- 修复 PinToggler 样式。修复了 #451 - 由 @soybeanjs 在 https://github.com/soybeanjs/soybean-admin/issues/451 中修复 [<samp>(42b12)</samp>](https://github.com/soybeanjs/soybean-admin/commit/42b121a)
|
||||
|
||||
### 🛠 优化
|
||||
|
||||
- **组件**: 提高 TableColumnSetting 的拖动区域精度,带有动画 - 由 @orangelckc 在 https://github.com/soybeanjs/soybean-admin/issues/465 中优化 [<samp>(2aa85)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2aa85c6)
|
||||
- **项目**: unocss 边框快捷方式 - 由 @soybeanjs 优化 [<samp>(40d0f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/40d0f8a)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**: 更新 CHANGELOG - 由 @soybeanjs 更新 [<samp>(87b18)</samp>](https://github.com/soybeanjs/soybean-admin/commit/87b1838)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **其他**:
|
||||
- 纠正拼写错误 - 由 @orangelckc 在 https://github.com/soybeanjs/soybean-admin/issues/460 中纠正 [<samp>(086ba)</samp>](https://github.com/soybeanjs/soybean-admin/commit/086bad4)
|
||||
- 纠正拼写错误 - 由 @Azir-11 在 https://github.com/soybeanjs/soybean-admin/issues/462 中纠正 [<samp>(f1850)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f185041)
|
||||
- **项目**:
|
||||
- 更新 vscode launch.json - 由 @soybeanjs 更新 [<samp>(4c1c7)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4c1c7e6)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/soybeanjs) [](https://github.com/Azir-11) [](https://github.com/orangelckc)
|
||||
|
||||
## [v1.1.2](https://github.com/soybeanjs/soybean-admin/compare/v1.1.1...v1.1.2) (2024-05-24)
|
||||
|
||||
### 🐞 错误修复
|
||||
|
||||
- **项目**:
|
||||
- 修复头部样式 & 修复点击全局标签时按钮高亮。修复了 #446 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/446 [<samp>(64fc0)</samp>](https://github.com/soybeanjs/soybean-admin/commit/64fc099)
|
||||
- 修复多标签页只渲染一次。修复了 #441 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/441 [<samp>(e379d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e379d6c)
|
||||
|
||||
### 🛠 优化
|
||||
|
||||
- **项目**: 优化代码 - 由 @honghuangdc [<samp>(bc8dc)</samp>](https://github.com/soybeanjs/soybean-admin/commit/bc8dc47)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
|
||||
## [v1.1.1](https://github.com/soybeanjs/soybean-admin/compare/v1.1.0...v1.1.1) (2024-05-20)
|
||||
|
||||
### 🚀 功能
|
||||
|
||||
- **hooks**: 为 useEcharts 添加 setOptions - 由 @honghuangdc 提交 [<samp>(e4d53)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e4d53aa)
|
||||
|
||||
### 🐞 修复错误
|
||||
|
||||
- **projects**:
|
||||
- 修复 useRouter。修复了 #436 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/436 提交 [<samp>(0774a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/0774a51)
|
||||
- 在动态路由模式下获取路由时添加错误处理。修复了 440 - 由 @honghuangdc 提交 [<samp>(57b4a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/57b4a9d)
|
||||
- **styles**:
|
||||
- 修复 useTable 类型 - 由 @honghuangdc 提交 [<samp>(07124)</samp>](https://github.com/soybeanjs/soybean-admin/commit/071241f)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **projects**:
|
||||
- 更新 CHANGELOG - 由 @honghuangdc 提交 [<samp>(19783)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1978397)
|
||||
- 更新 README.md - 由 @honghuangdc 提交 [<samp>(fa56e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/fa56e9c)
|
||||
- 更新 README.md - 由 @honghuangdc 提交 [<samp>(419ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/419ea42)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **projects**:
|
||||
- 更新依赖并修复 TS 错误 - 由 @honghuangdc 提交 [<samp>(4ea9c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4ea9c85)
|
||||
- 更新 eslint-config 并修复代码 - 由 @honghuangdc 提交 [<samp>(68ea9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/68ea974)
|
||||
- 更新 @elegant-router/vue 并为 resolve route 添加错误处理。修复了 #442 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/442 提交 [<samp>(24ff8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/24ff852)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
|
||||
## [v1.1.0](https://github.com/honghuangdc/soybean-admin/compare/v1.0.9...v1.1.0) (2024-05-07)
|
||||
|
||||
### 🚀 功能
|
||||
|
||||
- **项目**:
|
||||
- 支持灰度。修复了 #385 - 由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/385 [<samp>(d335d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d335df6)
|
||||
- 添加前缀到本地存储 - 由 **Azir** [<samp>(1fc34)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1fc34cc)
|
||||
- 添加表格显示总数选项 - 由 **paynezhuang** [<samp>(3e61e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3e61eab)
|
||||
- 添加推荐颜色切换。关闭了 #388 - 由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/388 [<samp>(a1920)</samp>](https://github.com/honghuangdc/soybean-admin/commit/a1920fc)
|
||||
- 添加菜单路由字段 - 由 **paynezhuang** [<samp>(dbe31)</samp>](https://github.com/honghuangdc/soybean-admin/commit/dbe31eb)
|
||||
- 支持短时间内重复请求错误只出现一次。关闭了 #368, 关闭了 #369 - 由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/368 和 https://github.com/honghuangdc/soybean-admin/issues/369 [<samp>(e3bd3)</samp>](https://github.com/honghuangdc/soybean-admin/commit/e3bd397)
|
||||
- 通过鼠标滚轮按钮点击关闭标签 - 由 **JianJroh** [<samp>(d3849)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d3849ba)
|
||||
- 页面:支持管理菜单更多选项。关闭了 #366 - 由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/366 [<samp>(c4b5c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c4b5c65)
|
||||
- useTable 添加展开显示 - 由 **paynezhuang** [<samp>(0a90d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0a90dd3)
|
||||
|
||||
### 🐞 错误修复
|
||||
|
||||
- **项目**:
|
||||
- 菜单 fixedIndexInTab 默认为 null - 由 **paynezhuang** [<samp>(3d10e)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3d10ef1)
|
||||
- 修复菜单切换器 zIndex - 由 @honghuangdc [<samp>(7bd43)</samp>](https://github.com/honghuangdc/soybean-admin/commit/7bd43df)
|
||||
- 修复管理菜单模态样式 - 由 @honghuangdc [<samp>(60f3b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/60f3b14)
|
||||
- 当角色改变时修复菜单数据。修复了 #391 - 由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/391 [<samp>(3b47b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3b47b5a)
|
||||
|
||||
### 🛠 优化
|
||||
|
||||
- **项目**: 移除废弃的代码 - 由 @honghuangdc [<samp>(72ccb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/72ccb6b)
|
||||
|
||||
### 💅 重构
|
||||
|
||||
- **项目**:
|
||||
- 重构 @sa/color-palette => @sa/color & 优化 @sa/utils 代码 - 由 @honghuangdc [<samp>(34999)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3499997)
|
||||
- menu-operate-drawer => menu-operate-modal - 由 @honghuangdc [<samp>(003e1)</samp>](https://github.com/honghuangdc/soybean-admin/commit/003e145)
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**:
|
||||
- 添加 CHANGELOG.zh_CN.md - 由 @honghuangdc [<samp>(18b3f)</samp>](https://github.com/honghuangdc/soybean-admin/commit/18b3f05)
|
||||
- 更新 CHANGELOG - 由 @honghuangdc [<samp>(4d17c)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4d17cfd)
|
||||
|
||||
### 🏡 杂项
|
||||
|
||||
- **依赖**:
|
||||
- 更新依赖 - 由 @honghuangdc [<samp>(1cb38)</samp>](https://github.com/honghuangdc/soybean-admin/commit/1cb3816)
|
||||
- 更新依赖 - 由 @honghuangdc [<samp>(599b4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/599b4e1)
|
||||
- **项目**:
|
||||
- 合并 main 到 v1.1.0 - 由 @honghuangdc [<samp>(ebe55)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ebe55af)
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
[paynezhuang](mailto:paynezhuang@gmail.com), [JianJroh](mailto:rhjian@foxmail.com), [Azir](mailto:2075125282@qq.com)
|
||||
|
||||
## [v1.1.0-beta.2](https://github.com/honghuangdc/soybean-admin/compare/v1.1.0-beta.1...v1.1.0-beta.2) (2024-05-07)
|
||||
|
||||
### 🚀 特性
|
||||
|
||||
- **项目**: useTable添加展开以显示 - 由 **paynezhuang** [<samp>(0a90d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/0a90dd3) 提供
|
||||
|
||||
### 🐞 修复的错误
|
||||
|
||||
- **项目**:
|
||||
- 修复 manage_menu 模态样式 - 由 @honghuangdc [<samp>(60f3b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/60f3b14) 提供
|
||||
- 当角色改变时修复菜单数据。修复了 #391 - 由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/391 [<samp>(3b47b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/3b47b5a) 提供
|
||||
|
||||
### 🛠 优化
|
||||
|
||||
- **项目**: 删除废弃的代码 - 由 @honghuangdc [<samp>(72ccb)</samp>](https://github.com/honghuangdc/soybean-admin/commit/72ccb6b) 提供
|
||||
|
||||
### 📖 文档
|
||||
|
||||
- **项目**: 添加 CHANGELOG.zh_CN.md - 由 @honghuangdc [<samp>(18b3f)</samp>](https://github.com/honghuangdc/soybean-admin/commit/18b3f05) 提供
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
[paynezhuang](mailto:paynezhuang@gmail.com)
|
||||
|
||||
|
||||
## [v1.1.0-beta.1](https://github.com/soybeanjs/soybean-admin/compare/v1.0.9...v1.1.0-beta.1) (2024-05-07)
|
||||
|
||||
### 🚀 功能
|
||||
|
||||
- **项目**:
|
||||
- 支持灰度。修复了 #385 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/385 [<samp>(d335d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d335df6)
|
||||
- 添加前缀到本地存储 - 由 **Azir** [<samp>(1fc34)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1fc34cc)
|
||||
- 添加表格 showTotal 选项 - 由 **paynezhuang** [<samp>(3e61e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e61eab)
|
||||
- 添加推荐颜色切换。关闭了 #388 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/388 [<samp>(a1920)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a1920fc)
|
||||
- 添加菜单路由字段 - 由 **paynezhuang** [<samp>(dbe31)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dbe31eb)
|
||||
- 支持短时间内重复请求错误只发生一次。关闭了 #368, 关闭了 #369 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/368 和 https://github.com/soybeanjs/soybean-admin/issues/369 [<samp>(e3bd3)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e3bd397)
|
||||
- 通过鼠标滚轮按钮点击关闭标签 - 由 **JianJroh** [<samp>(d3849)</samp>](https://github.com/soybeanjs/soybean-admin/commit/d3849ba)
|
||||
- 页面:支持更多的 manage_menu 选项。关闭了 #366 - 由 @honghuangdc 在 https://github.com/soybeanjs/soybean-admin/issues/366 [<samp>(c4b5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c4b5c65)
|
||||
|
||||
### 🐞 错误修复
|
||||
|
||||
- **项目**:
|
||||
- 修复菜单 fixedIndexInTab 默认为 null - 由 **paynezhuang** [<samp>(3d10e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3d10ef1)完成
|
||||
- 修复菜单切换器 zIndex - 由 @honghuangdc [<samp>(7bd43)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7bd43df)完成
|
||||
|
||||
### 💅 重构
|
||||
|
||||
- **项目**:
|
||||
- 重构 @sa/color-palette => @sa/color & 性能优化 @sa/utils - 由 @honghuangdc [<samp>(34999)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3499997)完成
|
||||
- menu-operate-drawer => menu-operate-modal - 由 @honghuangdc [<samp>(003e1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/003e145)完成
|
||||
|
||||
### 🏡 杂务
|
||||
|
||||
- **依赖**:
|
||||
- 更新依赖 - 由 @honghuangdc [<samp>(1cb38)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1cb3816)完成
|
||||
- 更新依赖 - 由 @honghuangdc [<samp>(599b4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/599b4e1)完成
|
||||
- **项目**:
|
||||
- 合并主分支到 v1.1.0 - 由 @honghuangdc [<samp>(ebe55)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ebe55af)完成
|
||||
|
||||
### ❤️ 贡献者
|
||||
|
||||
[](https://github.com/honghuangdc)
|
||||
[JianJroh](mailto:rhjian@foxmail.com), [paynezhuang](mailto:paynezhuang@gmail.com), [Azir](mailto:2075125282@qq.com)
|
16
README.md
16
README.md
@@ -11,6 +11,9 @@
|
||||
[](https://github.com/soybeanjs/soybean-admin)
|
||||
[](https://gitee.com/honghuangdc/soybean-admin)
|
||||
|
||||
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> If you think `SoybeanAdmin` is helpful to you, or you like our project, please give us a ⭐️ on GitHub. Your support is the driving force for us to continue to improve and add new features! Thank you for your support!
|
||||
|
||||
@@ -78,8 +81,8 @@
|
||||
Make sure your environment meets the following requirements:
|
||||
|
||||
- **git**: you need git to clone and manage project versions.
|
||||
- **NodeJS**: >=18.0.0, recommended 18.19.0 or higher.
|
||||
- **pnpm**: >= 8.0.0, recommended 8.14.0 or higher.
|
||||
- **NodeJS**: >=18.12.0, recommended 18.19.0 or higher.
|
||||
- **pnpm**: >= 8.7.0, recommended 8.14.0 or higher.
|
||||
|
||||
**Clone Project**
|
||||
|
||||
@@ -113,6 +116,9 @@ pnpm build
|
||||
- [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin).
|
||||
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): A backend management framework developed based on Vue3/TypeScript/NaiveUI and NET7 & Sqlsugar. It is implemented in the most original and simplest way, with a fresh and elegant front-end, a clear and elegant backend structure, and powerful functions.
|
||||
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): Adopting SpringBoot 3, SaToken, MySQL and other frameworks to develop and modify [soybean-admin](https://github.com/soybeanjs/soybean-admin) for the second time, adapting dynamic menu/button-level authorization. Retaining the original flavor, fresh and elegant, high-value back-end management system scaffold.
|
||||
- [snail-job](https://github.com/aizuda/snail-job): A distributed task retry and task scheduling platform with "high performance, high value and high activity".
|
||||
- [SuperApi](https://github.com/TmmTop/SuperApi): Quickly turn your idea into an online stable product! Entity-less library and table building, add, delete, change and check entity-less library table, support 15 kinds of condition query, as well as paging, list, unlimited tree list and other functions of the API deployment! With interface documentation, Auth authorisation, interface flow restriction, access to the client's real IP, advanced server caching components, dynamic APIs and other features, we look forward to your experience!
|
||||
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): A modern Management Platform based on FastAPI+Vue3+Naive UI.
|
||||
|
||||
|
||||
## How to Contribute
|
||||
@@ -151,10 +157,10 @@ Thanks the following people for their contributions. If you want to contribute t
|
||||
<p>QQ Group</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" />
|
||||
</div>
|
||||
<!-- <div>
|
||||
<div>
|
||||
<p>WeChat Group</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-0402.jpg" style="width:200px" />
|
||||
</div> -->
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/picgo/soybean-admin-wechat-0620.jpg" style="width:200px" />
|
||||
</div>
|
||||
<div>
|
||||
<p>Add the following WeChat to invite to the WeChat group</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" />
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<img src="./public/favicon.svg" width="160" />
|
||||
<h1>SoybeanAdmin</h1>
|
||||
<span><a href="./README.zh_CN.md">English</a> | 中文</span>
|
||||
<span><a href="./README.md">English</a> | 中文</span>
|
||||
</div>
|
||||
|
||||
---
|
||||
@@ -11,6 +11,8 @@
|
||||
[](https://github.com/soybeanjs/soybean-admin)
|
||||
[](https://gitee.com/honghuangdc/soybean-admin)
|
||||
|
||||
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
> [!NOTE]
|
||||
> 如果您觉得 `SoybeanAdmin`对您有所帮助,或者您喜欢我们的项目,请在 GitHub 上给我们一个 ⭐️。您的支持是我们持续改进和增加新功能的动力!感谢您的支持!
|
||||
|
||||
@@ -77,8 +79,8 @@
|
||||
确保你的环境满足以下要求:
|
||||
|
||||
- **git**: 你需要git来克隆和管理项目版本。
|
||||
- **NodeJS**: >=18.0.0,推荐 18.19.0 或更高。
|
||||
- **pnpm**: >= 8.0.0,推荐 8.14.0 或更高。
|
||||
- **NodeJS**: >=18.12.0,推荐 18.19.0 或更高。
|
||||
- **pnpm**: >= 8.7.0,推荐 8.14.0 或更高。
|
||||
|
||||
**克隆项目**
|
||||
|
||||
@@ -112,6 +114,9 @@ pnpm build
|
||||
- [pea](https://github.com/haitang1894/pea) : 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等,适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 开发的简单权限系统。
|
||||
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): 基于 Vue3/TypeScript/NaiveUI 和 NET7 & Sqlsugar 开发的后台管理框架。采用最原生最简洁的方式来实现, 前端清新优雅高颜值,后端 结构清晰,优雅易懂,功能强大。
|
||||
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): 采用SpringBoot3、SaToken、MySQL等框架开发,二次修改 [soybean-admin](https://github.com/soybeanjs/soybean-admin),适配动态菜单/按钮级别的鉴权,保留原汁原味、清新优雅、高颜值的后台管理系统脚手架。
|
||||
- [snail-job](https://github.com/aizuda/snail-job): 一款兼具 “高性能、高颜值、高活跃” 的分布式任务重试和分布式任务调度平台。
|
||||
- [SuperApi](https://github.com/TmmTop/SuperApi): 快速将你的 idea 变成线上稳定运行的产品! 无实体建库建表,对无实体库表进行增删改查,支持 15 种条件查询,以及分页,列表,无限级树形列表 等功能的 API 部署! 拥有接口文档,Auth 授权,接口限流,获取客户端真实 IP,先进的服务器缓存组件,动态 API 等功能,期待您的体验!
|
||||
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): 基于 FastAPI+Vue3+Naive UI 的现代化轻量管理平台.
|
||||
|
||||
|
||||
## 如何贡献
|
||||
@@ -152,10 +157,10 @@ pnpm build
|
||||
<p>QQ交流群</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" />
|
||||
</div>
|
||||
<!-- <div>
|
||||
<div>
|
||||
<p>微信群</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-0402.jpg" style="width:200px" />
|
||||
</div> -->
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/picgo/soybean-admin-wechat-0620.jpg" style="width:200px" />
|
||||
</div>
|
||||
<div>
|
||||
<p>添加下面微信邀请进微信群</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" />
|
||||
|
@@ -1 +1,2 @@
|
||||
export * from './proxy';
|
||||
export * from './time';
|
||||
|
12
build/config/time.ts
Normal file
12
build/config/time.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
|
||||
export function getBuildTime() {
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
const buildTime = dayjs.tz(Date.now(), 'Asia/Shanghai').format('YYYY-MM-DD HH:mm:ss');
|
||||
|
||||
return buildTime;
|
||||
}
|
13
build/plugins/html.ts
Normal file
13
build/plugins/html.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Plugin } from 'vite';
|
||||
|
||||
export function setupHtmlPlugin(buildTime: string) {
|
||||
const plugin: Plugin = {
|
||||
name: 'html-plugin',
|
||||
apply: 'build',
|
||||
transformIndexHtml(html) {
|
||||
return html.replace('<head>', `<head>\n <meta name="buildTime" content="${buildTime}">`);
|
||||
}
|
||||
};
|
||||
|
||||
return plugin;
|
||||
}
|
@@ -6,8 +6,9 @@ import progress from 'vite-plugin-progress';
|
||||
import { setupElegantRouter } from './router';
|
||||
import { setupUnocss } from './unocss';
|
||||
import { setupUnplugin } from './unplugin';
|
||||
import { setupHtmlPlugin } from './html';
|
||||
|
||||
export function setupVitePlugins(viteEnv: Env.ImportMeta) {
|
||||
export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
|
||||
const plugins: PluginOption = [
|
||||
vue({
|
||||
script: {
|
||||
@@ -19,7 +20,8 @@ export function setupVitePlugins(viteEnv: Env.ImportMeta) {
|
||||
setupElegantRouter(),
|
||||
setupUnocss(viteEnv),
|
||||
...setupUnplugin(viteEnv),
|
||||
progress()
|
||||
progress(),
|
||||
setupHtmlPlugin(buildTime)
|
||||
];
|
||||
|
||||
return plugins;
|
||||
|
@@ -7,7 +7,7 @@ export default defineConfig(
|
||||
'vue/multi-word-component-names': [
|
||||
'warn',
|
||||
{
|
||||
ignores: ['index', 'App', '[id]', '[url]']
|
||||
ignores: ['index', 'App', 'Register', '[id]', '[url]']
|
||||
}
|
||||
],
|
||||
'vue/component-name-in-template-casing': [
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
<body>
|
||||
|
55
package.json
55
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "soybean-admin",
|
||||
"type": "module",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
|
||||
"author": {
|
||||
"name": "Soybean",
|
||||
@@ -49,11 +49,11 @@
|
||||
"@better-scroll/core": "2.5.1",
|
||||
"@iconify/vue": "4.1.2",
|
||||
"@sa/axios": "workspace:*",
|
||||
"@sa/color-palette": "workspace:*",
|
||||
"@sa/color": "workspace:*",
|
||||
"@sa/hooks": "workspace:*",
|
||||
"@sa/materials": "workspace:*",
|
||||
"@sa/utils": "workspace:*",
|
||||
"@vueuse/core": "10.9.0",
|
||||
"@vueuse/core": "10.11.0",
|
||||
"clipboard": "2.0.11",
|
||||
"dayjs": "1.11.11",
|
||||
"echarts": "5.5.0",
|
||||
@@ -61,43 +61,44 @@
|
||||
"naive-ui": "2.38.2",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.1.7",
|
||||
"vue": "3.4.26",
|
||||
"vue-draggable-plus": "0.4.0",
|
||||
"tailwind-merge": "2.3.0",
|
||||
"vue": "3.4.29",
|
||||
"vue-draggable-plus": "0.5.0",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.3.2"
|
||||
"vue-router": "4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elegant-router/vue": "0.3.6",
|
||||
"@iconify/json": "2.2.206",
|
||||
"@elegant-router/vue": "0.3.7",
|
||||
"@iconify/json": "2.2.219",
|
||||
"@sa/scripts": "workspace:*",
|
||||
"@sa/uno-preset": "workspace:*",
|
||||
"@soybeanjs/eslint-config": "1.3.4",
|
||||
"@soybeanjs/eslint-config": "1.3.7",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "20.12.8",
|
||||
"@types/node": "20.14.2",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@unocss/eslint-config": "0.59.4",
|
||||
"@unocss/preset-icons": "0.59.4",
|
||||
"@unocss/preset-uno": "0.59.4",
|
||||
"@unocss/transformer-directives": "0.59.4",
|
||||
"@unocss/transformer-variant-group": "0.59.4",
|
||||
"@unocss/vite": "0.59.4",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "3.1.0",
|
||||
"eslint": "9.2.0",
|
||||
"eslint-plugin-vue": "9.25.0",
|
||||
"lint-staged": "15.2.2",
|
||||
"sass": "1.76.0",
|
||||
"@unocss/eslint-config": "0.61.0",
|
||||
"@unocss/preset-icons": "0.61.0",
|
||||
"@unocss/preset-uno": "0.61.0",
|
||||
"@unocss/transformer-directives": "0.61.0",
|
||||
"@unocss/transformer-variant-group": "0.61.0",
|
||||
"@unocss/vite": "0.61.0",
|
||||
"@vitejs/plugin-vue": "5.0.5",
|
||||
"@vitejs/plugin-vue-jsx": "4.0.0",
|
||||
"eslint": "9.4.0",
|
||||
"eslint-plugin-vue": "9.26.0",
|
||||
"lint-staged": "15.2.7",
|
||||
"sass": "1.77.5",
|
||||
"simple-git-hooks": "2.11.1",
|
||||
"tsx": "4.9.1",
|
||||
"tsx": "4.15.4",
|
||||
"typescript": "5.4.5",
|
||||
"unplugin-icons": "0.19.0",
|
||||
"unplugin-vue-components": "0.27.0",
|
||||
"vite": "5.2.11",
|
||||
"vite": "5.3.1",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-devtools": "7.1.3",
|
||||
"vue-eslint-parser": "9.4.2",
|
||||
"vue-tsc": "2.0.16"
|
||||
"vite-plugin-vue-devtools": "7.2.1",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-tsc": "2.0.21"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"commit-msg": "pnpm sa git-commit-verify",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/axios",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
@@ -11,8 +11,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"axios": "1.6.8",
|
||||
"axios-retry": "4.1.0",
|
||||
"axios": "1.7.2",
|
||||
"axios-retry": "4.4.0",
|
||||
"qs": "6.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -1,6 +0,0 @@
|
||||
import { colorPalettes } from './constant';
|
||||
import { getColorName, getHex, getHsl, getRgb } from './shared';
|
||||
|
||||
export * from './palette';
|
||||
export { getColorName, getHex, getHsl, getRgb, colorPalettes };
|
||||
export * from './types';
|
@@ -1,29 +0,0 @@
|
||||
import { colord, extend } from 'colord';
|
||||
import type { HslColor } from 'colord';
|
||||
import labPlugin from 'colord/plugins/lab';
|
||||
|
||||
extend([labPlugin]);
|
||||
|
||||
export function isValidColor(color: string) {
|
||||
return colord(color).isValid();
|
||||
}
|
||||
|
||||
export function getHex(color: string) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
export function getRgb(color: string) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
|
||||
export function getHsl(color: string) {
|
||||
return colord(color).toHsl();
|
||||
}
|
||||
|
||||
export function getDeltaE(color1: string, color2: string) {
|
||||
return colord(color1).delta(color2);
|
||||
}
|
||||
|
||||
export function transformHslToHex(color: HslColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/color-palette",
|
||||
"version": "1.0.9",
|
||||
"name": "@sa/color",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
@@ -10,6 +10,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"colord": "2.9.3"
|
||||
}
|
||||
}
|
7
packages/color/src/index.ts
Normal file
7
packages/color/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { colorPalettes } from './constant';
|
||||
|
||||
export * from './palette';
|
||||
export * from './shared';
|
||||
export { colorPalettes };
|
||||
|
||||
export * from './types';
|
@@ -1,74 +1,6 @@
|
||||
import { colord, extend } from 'colord';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import mixPlugin from 'colord/plugins/mix';
|
||||
import type { AnyColor, HsvColor, RgbColor } from 'colord';
|
||||
|
||||
extend([namesPlugin, mixPlugin]);
|
||||
|
||||
/**
|
||||
* Add color alpha
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
*/
|
||||
export function addColorAlpha(color: string, alpha: number) {
|
||||
return colord(color).alpha(alpha).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix color
|
||||
*
|
||||
* @param firstColor - First color
|
||||
* @param secondColor - Second color
|
||||
* @param ratio - The ratio of the second color (0 - 1)
|
||||
*/
|
||||
export function mixColor(firstColor: string, secondColor: string, ratio: number) {
|
||||
return colord(firstColor).mix(secondColor, ratio).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform color with opacity to similar color without opacity
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
* @param bgColor Background color (usually white or black)
|
||||
*/
|
||||
export function transformColorWithOpacity(color: string, alpha: number, bgColor = '#ffffff') {
|
||||
const originColor = addColorAlpha(color, alpha);
|
||||
const { r: oR, g: oG, b: oB } = colord(originColor).toRgb();
|
||||
|
||||
const { r: bgR, g: bgG, b: bgB } = colord(bgColor).toRgb();
|
||||
|
||||
function calRgb(or: number, bg: number, al: number) {
|
||||
return bg + (or - bg) * al;
|
||||
}
|
||||
|
||||
const resultRgb: RgbColor = {
|
||||
r: calRgb(oR, bgR, alpha),
|
||||
g: calRgb(oG, bgG, alpha),
|
||||
b: calRgb(oB, bgB, alpha)
|
||||
};
|
||||
|
||||
return colord(resultRgb).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is white color
|
||||
*
|
||||
* @param color - Color
|
||||
*/
|
||||
export function isWhiteColor(color: string) {
|
||||
return colord(color).isEqual('#ffffff');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rgb of color
|
||||
*
|
||||
* @param color Color
|
||||
*/
|
||||
export function getRgbOfColor(color: string) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
import type { AnyColor, HsvColor } from 'colord';
|
||||
import { getHex, getHsv, isValidColor, mixColor } from '../shared';
|
||||
import type { ColorIndex } from '../types';
|
||||
|
||||
/** Hue step */
|
||||
const hueStep = 2;
|
||||
@@ -86,32 +18,23 @@ const lightColorCount = 5;
|
||||
const darkColorCount = 4;
|
||||
|
||||
/**
|
||||
* The color index of color palette
|
||||
*
|
||||
* From left to right, the color is from light to dark, 6 is main color
|
||||
*/
|
||||
type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
|
||||
|
||||
/**
|
||||
* Get color palette (from left to right, the color is from light to dark, 6 is main color)
|
||||
* Get AntD palette color by index
|
||||
*
|
||||
* @param color - Color
|
||||
* @param index - The color index of color palette (the main color index is 6)
|
||||
* @returns Hex color
|
||||
*/
|
||||
export function getColorPalette(color: AnyColor, index: ColorIndex): string {
|
||||
const transformColor = colord(color);
|
||||
|
||||
if (!transformColor.isValid()) {
|
||||
export function getAntDPaletteColorByIndex(color: AnyColor, index: ColorIndex): string {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('invalid input color value');
|
||||
}
|
||||
|
||||
if (index === 6) {
|
||||
return colord(transformColor).toHex();
|
||||
return getHex(color);
|
||||
}
|
||||
|
||||
const isLight = index < 6;
|
||||
const hsv = transformColor.toHsv();
|
||||
const hsv = getHsv(color);
|
||||
const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1;
|
||||
|
||||
const newHsv: HsvColor = {
|
||||
@@ -120,7 +43,7 @@ export function getColorPalette(color: AnyColor, index: ColorIndex): string {
|
||||
v: getValue(hsv, i, isLight)
|
||||
};
|
||||
|
||||
return colord(newHsv).toHex();
|
||||
return getHex(newHsv);
|
||||
}
|
||||
|
||||
/** Map of dark color index and opacity */
|
||||
@@ -131,32 +54,33 @@ const darkColorMap = [
|
||||
{ index: 5, opacity: 0.45 },
|
||||
{ index: 5, opacity: 0.65 },
|
||||
{ index: 5, opacity: 0.85 },
|
||||
{ index: 4, opacity: 0.9 },
|
||||
{ index: 5, opacity: 0.9 },
|
||||
{ index: 4, opacity: 0.93 },
|
||||
{ index: 3, opacity: 0.95 },
|
||||
{ index: 2, opacity: 0.97 },
|
||||
{ index: 1, opacity: 0.98 }
|
||||
];
|
||||
|
||||
/**
|
||||
* Get color palettes
|
||||
* Get AntD color palette
|
||||
*
|
||||
* @param color - Color
|
||||
* @param darkTheme - Dark theme
|
||||
* @param darkThemeMixColor - Dark theme mix color (default: #141414)
|
||||
*/
|
||||
export function getColorPalettes(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
|
||||
const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
export function getAntDColorPalette(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
|
||||
const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
|
||||
const patterns = indexes.map(index => getColorPalette(color, index));
|
||||
const patterns = indexes.map(index => getAntDPaletteColorByIndex(color, index));
|
||||
|
||||
if (darkTheme) {
|
||||
const darkPatterns = darkColorMap.map(({ index, opacity }) => {
|
||||
const darkColor = colord(darkThemeMixColor).mix(patterns[index], opacity);
|
||||
const darkColor = mixColor(darkThemeMixColor, patterns[index], opacity);
|
||||
|
||||
return darkColor;
|
||||
});
|
||||
|
||||
return darkPatterns.map(item => colord(item).toHex());
|
||||
return darkPatterns.map(item => getHex(item));
|
||||
}
|
||||
|
||||
return patterns;
|
45
packages/color/src/palette/index.ts
Normal file
45
packages/color/src/palette/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { AnyColor } from 'colord';
|
||||
import { getHex } from '../shared';
|
||||
import type { ColorPaletteNumber } from '../types';
|
||||
import { getRecommendedColorPalette } from './recommend';
|
||||
import { getAntDColorPalette } from './antd';
|
||||
|
||||
/**
|
||||
* get color palette by provided color
|
||||
*
|
||||
* @param color
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getColorPalette(color: AnyColor, recommended = false) {
|
||||
const colorMap = new Map<ColorPaletteNumber, string>();
|
||||
|
||||
if (recommended) {
|
||||
const colorPalette = getRecommendedColorPalette(getHex(color));
|
||||
colorPalette.palettes.forEach(palette => {
|
||||
colorMap.set(palette.number, palette.hex);
|
||||
});
|
||||
} else {
|
||||
const colors = getAntDColorPalette(color);
|
||||
|
||||
const colorNumbers: ColorPaletteNumber[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
|
||||
|
||||
colorNumbers.forEach((number, index) => {
|
||||
colorMap.set(number, colors[index]);
|
||||
});
|
||||
}
|
||||
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette color by number
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getPaletteColorByNumber(color: AnyColor, number: ColorPaletteNumber, recommended = false) {
|
||||
const colorMap = getColorPalette(color, recommended);
|
||||
|
||||
return colorMap.get(number as ColorPaletteNumber)!;
|
||||
}
|
@@ -9,12 +9,12 @@ import type {
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* get color palette by provided color and color name
|
||||
* get recommended color palette by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getColorPalette(color: string) {
|
||||
const colorPaletteFamily = getColorPaletteFamily(color);
|
||||
export function getRecommendedColorPalette(color: string) {
|
||||
const colorPaletteFamily = getRecommendedColorPaletteFamily(color);
|
||||
|
||||
const colorMap = new Map<ColorPaletteNumber, ColorPalette>();
|
||||
|
||||
@@ -36,13 +36,13 @@ export function getColorPalette(color: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* get color by number
|
||||
* get recommended palette color by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
*/
|
||||
export function getColorByPaletteNumber(color: string, number: ColorPaletteNumber) {
|
||||
const colorPalette = getColorPalette(color);
|
||||
export function getRecommendedPaletteColorByNumber(color: string, number: ColorPaletteNumber) {
|
||||
const colorPalette = getRecommendedColorPalette(color);
|
||||
|
||||
const { hex } = colorPalette.colorMap.get(number)!;
|
||||
|
||||
@@ -54,7 +54,7 @@ export function getColorByPaletteNumber(color: string, number: ColorPaletteNumbe
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getColorPaletteFamily(color: string) {
|
||||
export function getRecommendedColorPaletteFamily(color: string) {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('Invalid color, please check color value!');
|
||||
}
|
93
packages/color/src/shared/colord.ts
Normal file
93
packages/color/src/shared/colord.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { colord, extend } from 'colord';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import mixPlugin from 'colord/plugins/mix';
|
||||
import labPlugin from 'colord/plugins/lab';
|
||||
import type { AnyColor, HslColor, RgbColor } from 'colord';
|
||||
|
||||
extend([namesPlugin, mixPlugin, labPlugin]);
|
||||
|
||||
export function isValidColor(color: AnyColor) {
|
||||
return colord(color).isValid();
|
||||
}
|
||||
|
||||
export function getHex(color: AnyColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
export function getRgb(color: AnyColor) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
|
||||
export function getHsl(color: AnyColor) {
|
||||
return colord(color).toHsl();
|
||||
}
|
||||
|
||||
export function getHsv(color: AnyColor) {
|
||||
return colord(color).toHsv();
|
||||
}
|
||||
|
||||
export function getDeltaE(color1: AnyColor, color2: AnyColor) {
|
||||
return colord(color1).delta(color2);
|
||||
}
|
||||
|
||||
export function transformHslToHex(color: HslColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add color alpha
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
*/
|
||||
export function addColorAlpha(color: AnyColor, alpha: number) {
|
||||
return colord(color).alpha(alpha).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix color
|
||||
*
|
||||
* @param firstColor - First color
|
||||
* @param secondColor - Second color
|
||||
* @param ratio - The ratio of the second color (0 - 1)
|
||||
*/
|
||||
export function mixColor(firstColor: AnyColor, secondColor: AnyColor, ratio: number) {
|
||||
return colord(firstColor).mix(secondColor, ratio).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform color with opacity to similar color without opacity
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
* @param bgColor Background color (usually white or black)
|
||||
*/
|
||||
export function transformColorWithOpacity(color: AnyColor, alpha: number, bgColor = '#ffffff') {
|
||||
const originColor = addColorAlpha(color, alpha);
|
||||
const { r: oR, g: oG, b: oB } = colord(originColor).toRgb();
|
||||
|
||||
const { r: bgR, g: bgG, b: bgB } = colord(bgColor).toRgb();
|
||||
|
||||
function calRgb(or: number, bg: number, al: number) {
|
||||
return bg + (or - bg) * al;
|
||||
}
|
||||
|
||||
const resultRgb: RgbColor = {
|
||||
r: calRgb(oR, bgR, alpha),
|
||||
g: calRgb(oG, bgG, alpha),
|
||||
b: calRgb(oB, bgB, alpha)
|
||||
};
|
||||
|
||||
return colord(resultRgb).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is white color
|
||||
*
|
||||
* @param color - Color
|
||||
*/
|
||||
export function isWhiteColor(color: AnyColor) {
|
||||
return colord(color).isEqual('#ffffff');
|
||||
}
|
||||
|
||||
export { colord };
|
@@ -1,4 +1,8 @@
|
||||
/** the color palette number */
|
||||
/**
|
||||
* the color palette number
|
||||
*
|
||||
* the main color number is 500
|
||||
*/
|
||||
export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
||||
|
||||
/** the color palette */
|
||||
@@ -45,3 +49,10 @@ export type ColorPaletteMatch = ColorPaletteFamily & {
|
||||
/** the match color of the palette */
|
||||
match: ColorPalette;
|
||||
};
|
||||
|
||||
/**
|
||||
* The color index of color palette
|
||||
*
|
||||
* From left to right, the color is from light to dark, 6 is main color
|
||||
*/
|
||||
export type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/hooks",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/materials",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"simplebar-vue": "2.3.3"
|
||||
"simplebar-vue": "2.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typed-css-modules": "0.9.1"
|
||||
|
@@ -36,7 +36,7 @@ defineSlots<Slots>();
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-12px whitespace-nowrap border-1px rounded-4px border-solid px-12px py-4px"
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-12px whitespace-nowrap border-(1px solid) rounded-4px px-12px py-4px"
|
||||
:class="[
|
||||
style['button-tab'],
|
||||
{ [style['button-tab_dark']]: darkMode },
|
||||
|
@@ -53,10 +53,23 @@ const bindProps = computed(() => {
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function handleMouseup(e: MouseEvent) {
|
||||
// close tab by mouse wheel button click
|
||||
if (e.button === 1) {
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="activeTabComponent.component" :class="activeTabComponent.class" :style="cssVars" v-bind="bindProps">
|
||||
<component
|
||||
:is="activeTabComponent.component"
|
||||
:class="activeTabComponent.class"
|
||||
:style="cssVars"
|
||||
v-bind="bindProps"
|
||||
@mouseup="handleMouseup"
|
||||
>
|
||||
<template #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { addColorAlpha, transformColorWithOpacity } from '@sa/utils';
|
||||
import { addColorAlpha, transformColorWithOpacity } from '@sa/color';
|
||||
import type { PageTabCssVars, PageTabCssVarsProps } from '../../types';
|
||||
|
||||
/** The active color of the tab */
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/scripts",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"bin": {
|
||||
"sa": "./bin.ts"
|
||||
},
|
||||
@@ -13,15 +13,15 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@soybeanjs/changelog": "0.3.23",
|
||||
"@soybeanjs/changelog": "0.3.24",
|
||||
"bumpp": "9.4.1",
|
||||
"c12": "1.10.0",
|
||||
"cac": "6.7.14",
|
||||
"consola": "3.2.3",
|
||||
"enquirer": "2.4.1",
|
||||
"execa": "8.0.1",
|
||||
"execa": "9.2.0",
|
||||
"kolorist": "1.8.0",
|
||||
"npm-check-updates": "16.14.20",
|
||||
"rimraf": "5.0.5"
|
||||
"rimraf": "5.0.7"
|
||||
}
|
||||
}
|
||||
|
@@ -3,5 +3,5 @@ import type { Options } from 'execa';
|
||||
export async function execCommand(cmd: string, args: string[], options?: Options) {
|
||||
const { execa } = await import('execa');
|
||||
const res = await execa(cmd, args, options);
|
||||
return res?.stdout?.trim() || '';
|
||||
return (res?.stdout as string)?.trim() || '';
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/uno-preset",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/utils",
|
||||
"version": "1.0.9",
|
||||
"version": "1.2.5",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@@ -1,4 +1,3 @@
|
||||
export * from './color';
|
||||
export * from './crypto';
|
||||
export * from './storage';
|
||||
export * from './nanoid';
|
||||
|
@@ -3,7 +3,7 @@ import localforage from 'localforage';
|
||||
/** The storage type */
|
||||
export type StorageType = 'local' | 'session';
|
||||
|
||||
export function createStorage<T extends object>(type: StorageType) {
|
||||
export function createStorage<T extends object>(type: StorageType, storagePrefix: string) {
|
||||
const stg = type === 'session' ? window.sessionStorage : window.localStorage;
|
||||
|
||||
const storage = {
|
||||
@@ -16,7 +16,7 @@ export function createStorage<T extends object>(type: StorageType) {
|
||||
set<K extends keyof T>(key: K, value: T[K]) {
|
||||
const json = JSON.stringify(value);
|
||||
|
||||
stg.setItem(key as string, json);
|
||||
stg.setItem(`${storagePrefix}${key as string}`, json);
|
||||
},
|
||||
/**
|
||||
* Get session
|
||||
@@ -24,7 +24,7 @@ export function createStorage<T extends object>(type: StorageType) {
|
||||
* @param key Session key
|
||||
*/
|
||||
get<K extends keyof T>(key: K): T[K] | null {
|
||||
const json = stg.getItem(key as string);
|
||||
const json = stg.getItem(`${storagePrefix}${key as string}`);
|
||||
if (json) {
|
||||
let storageData: T[K] | null = null;
|
||||
|
||||
@@ -37,12 +37,12 @@ export function createStorage<T extends object>(type: StorageType) {
|
||||
}
|
||||
}
|
||||
|
||||
stg.removeItem(key as string);
|
||||
stg.removeItem(`${storagePrefix}${key as string}`);
|
||||
|
||||
return null;
|
||||
},
|
||||
remove(key: keyof T) {
|
||||
stg.removeItem(key as string);
|
||||
stg.removeItem(`${storagePrefix}${key as string}`);
|
||||
},
|
||||
clear() {
|
||||
stg.clear();
|
||||
|
20
packages/utils/tsconfig.json
Normal file
20
packages/utils/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
2844
pnpm-lock.yaml
generated
2844
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -21,10 +21,10 @@ const columns = defineModel<NaiveUI.TableColumnCheck[]>('columns', {
|
||||
{{ $t('common.columnSetting') }}
|
||||
</NButton>
|
||||
</template>
|
||||
<VueDraggable v-model="columns">
|
||||
<VueDraggable v-model="columns" :animation="150" filter=".none_draggable">
|
||||
<div v-for="item in columns" :key="item.key" class="h-36px flex-y-center rd-4px hover:(bg-primary bg-opacity-20)">
|
||||
<icon-mdi-drag class="mr-8px cursor-move text-icon" />
|
||||
<NCheckbox v-model:checked="item.checked">
|
||||
<icon-mdi-drag class="mr-8px h-full cursor-move text-icon" />
|
||||
<NCheckbox v-model:checked="item.checked" class="none_draggable flex-1">
|
||||
{{ item.title }}
|
||||
</NCheckbox>
|
||||
</div>
|
||||
|
@@ -9,9 +9,13 @@ interface Props {
|
||||
collapsed?: boolean;
|
||||
/** Arrow style icon */
|
||||
arrowIcon?: boolean;
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
arrowIcon: false,
|
||||
zIndex: 98
|
||||
});
|
||||
|
||||
type NumberBool = 0 | 1;
|
||||
|
||||
@@ -37,9 +41,10 @@ const icon = computed(() => {
|
||||
|
||||
<template>
|
||||
<ButtonIcon
|
||||
:key="String(collapsed)"
|
||||
:tooltip-content="collapsed ? $t('icon.expand') : $t('icon.collapse')"
|
||||
tooltip-placement="bottom-start"
|
||||
:z-index="99"
|
||||
:z-index="zIndex"
|
||||
>
|
||||
<SvgIcon :icon="icon" />
|
||||
</ButtonIcon>
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import type { PopoverPlacement } from 'naive-ui';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
defineOptions({
|
||||
name: 'ButtonIcon',
|
||||
@@ -21,60 +20,29 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
class: 'h-36px text-icon',
|
||||
class: '',
|
||||
icon: '',
|
||||
tooltipContent: '',
|
||||
tooltipPlacement: 'bottom',
|
||||
zIndex: 98
|
||||
});
|
||||
|
||||
interface ButtonProps {
|
||||
className: string;
|
||||
}
|
||||
|
||||
const [DefineButton, Button] = createReusableTemplate<ButtonProps>();
|
||||
|
||||
const cls = computed(() => {
|
||||
let clsStr = props.class;
|
||||
|
||||
if (!clsStr.includes('h-')) {
|
||||
clsStr += ' h-36px';
|
||||
}
|
||||
|
||||
if (!clsStr.includes('text-')) {
|
||||
clsStr += ' text-icon';
|
||||
}
|
||||
|
||||
return clsStr;
|
||||
});
|
||||
const DEFAULT_CLASS = 'h-[36px] text-icon';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- define component start: Button -->
|
||||
<DefineButton v-slot="{ $slots, className }">
|
||||
<NButton quaternary :class="className">
|
||||
<div class="flex-center gap-8px">
|
||||
<component :is="$slots.default" />
|
||||
</div>
|
||||
</NButton>
|
||||
</DefineButton>
|
||||
<!-- define component end: Button -->
|
||||
|
||||
<NTooltip v-if="tooltipContent" :placement="tooltipPlacement" :z-index="zIndex">
|
||||
<NTooltip :placement="tooltipPlacement" :z-index="zIndex" :disabled="!tooltipContent">
|
||||
<template #trigger>
|
||||
<Button :class-name="cls" v-bind="$attrs">
|
||||
<slot>
|
||||
<SvgIcon :icon="icon" />
|
||||
</slot>
|
||||
</Button>
|
||||
<NButton quaternary :class="twMerge(DEFAULT_CLASS, props.class)" v-bind="$attrs">
|
||||
<div class="flex-center gap-8px">
|
||||
<slot>
|
||||
<SvgIcon :icon="icon" />
|
||||
</slot>
|
||||
</div>
|
||||
</NButton>
|
||||
</template>
|
||||
{{ tooltipContent }}
|
||||
</NTooltip>
|
||||
<Button v-else :class-name="cls" v-bind="$attrs">
|
||||
<slot>
|
||||
<SvgIcon :icon="icon" />
|
||||
</slot>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { getColorPalette } from '@sa/utils';
|
||||
import { getPaletteColorByNumber } from '@sa/color';
|
||||
|
||||
defineOptions({ name: 'WaveBg' });
|
||||
|
||||
@@ -11,8 +11,8 @@ interface Props {
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const lightColor = computed(() => getColorPalette(props.themeColor, 3));
|
||||
const darkColor = computed(() => getColorPalette(props.themeColor, 6));
|
||||
const lightColor = computed(() => getPaletteColorByNumber(props.themeColor, 200));
|
||||
const darkColor = computed(() => getPaletteColorByNumber(props.themeColor, 500));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@@ -146,6 +146,10 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
await onUpdated?.(chart!);
|
||||
}
|
||||
|
||||
function setOptions(options: T) {
|
||||
chart?.setOption(options);
|
||||
}
|
||||
|
||||
/** render chart */
|
||||
async function render() {
|
||||
if (!isRendered()) {
|
||||
@@ -225,6 +229,7 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
|
||||
|
||||
return {
|
||||
domRef,
|
||||
updateOptions
|
||||
updateOptions,
|
||||
setOptions
|
||||
};
|
||||
}
|
||||
|
@@ -14,10 +14,14 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
||||
const scope = effectScope();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { apiFn, apiParams, immediate } = config;
|
||||
const isMobile = computed(() => appStore.isMobile);
|
||||
|
||||
const { apiFn, apiParams, immediate, showTotal } = config;
|
||||
|
||||
const SELECTION_KEY = '__selection__';
|
||||
|
||||
const EXPAND_KEY = '__expand__';
|
||||
|
||||
const {
|
||||
loading,
|
||||
empty,
|
||||
@@ -66,6 +70,12 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
||||
title: $t('common.check'),
|
||||
checked: true
|
||||
});
|
||||
} else if (column.type === 'expand') {
|
||||
checks.push({
|
||||
key: EXPAND_KEY,
|
||||
title: $t('common.expandColumn'),
|
||||
checked: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -79,6 +89,8 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
||||
columnMap.set(column.key as string, column);
|
||||
} else if (column.type === 'selection') {
|
||||
columnMap.set(SELECTION_KEY, column);
|
||||
} else if (column.type === 'expand') {
|
||||
columnMap.set(EXPAND_KEY, column);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -125,14 +137,20 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
||||
});
|
||||
|
||||
getData();
|
||||
}
|
||||
},
|
||||
...(showTotal
|
||||
? {
|
||||
prefix: page => $t('datatable.itemCount', { total: page.itemCount })
|
||||
}
|
||||
: {})
|
||||
});
|
||||
|
||||
// this is for mobile, if the system does not support mobile, you can use `pagination` directly
|
||||
const mobilePagination = computed(() => {
|
||||
const p: PaginationProps = {
|
||||
...pagination,
|
||||
pageSlot: appStore.isMobile ? 3 : 9
|
||||
pageSlot: isMobile.value ? 3 : 9,
|
||||
prefix: !isMobile.value && showTotal ? pagination.prefix : undefined
|
||||
};
|
||||
|
||||
return p;
|
||||
|
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { LAYOUT_SCROLL_EL_ID } from '@sa/materials';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useTabStore } from '@/store/modules/tab';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalContent'
|
||||
@@ -20,8 +22,15 @@ withDefaults(defineProps<Props>(), {
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const routeStore = useRouteStore();
|
||||
const tabStore = useTabStore();
|
||||
|
||||
const transitionName = computed(() => (themeStore.page.animate ? themeStore.page.animateMode : ''));
|
||||
|
||||
function resetScroll() {
|
||||
const el = document.querySelector(`#${LAYOUT_SCROLL_EL_ID}`);
|
||||
|
||||
el?.scrollTo({ left: 0, top: 0 });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -30,13 +39,14 @@ const transitionName = computed(() => (themeStore.page.animate ? themeStore.page
|
||||
:name="transitionName"
|
||||
mode="out-in"
|
||||
@before-leave="appStore.setContentXScrollable(true)"
|
||||
@after-leave="resetScroll"
|
||||
@after-enter="appStore.setContentXScrollable(false)"
|
||||
>
|
||||
<KeepAlive :include="routeStore.cacheRoutes">
|
||||
<component
|
||||
:is="Component"
|
||||
v-if="appStore.reloadFlag"
|
||||
:key="route.path"
|
||||
:key="tabStore.getTabIdByRoute(route)"
|
||||
:class="{ 'p-16px': showPadding }"
|
||||
class="flex-grow bg-layout transition-300"
|
||||
/>
|
||||
|
@@ -47,7 +47,7 @@ const headerMenus = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DarkModeContainer class="h-full flex-y-center shadow-header">
|
||||
<DarkModeContainer class="h-full flex-y-center px-12px shadow-header">
|
||||
<GlobalLogo v-if="showLogo" class="h-full" :style="{ width: themeStore.sider.width + 'px' }" />
|
||||
<HorizontalMenu v-if="showMenu" mode="horizontal" :menus="headerMenus" class="px-12px" />
|
||||
<div v-else class="h-full flex-y-center flex-1-hidden">
|
||||
|
@@ -57,7 +57,7 @@ function updateExpandedKeys() {
|
||||
}
|
||||
|
||||
function handleClickMenu(key: RouteKey) {
|
||||
const { query } = routeStore.getSelectedMenuMetaByKey(key) || {};
|
||||
const query = routeStore.getRouteQueryOfMetaByKey(key);
|
||||
|
||||
routerPushByKey(key, { query });
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { createReusableTemplate } from '@vueuse/core';
|
||||
import { SimpleScrollbar } from '@sa/materials';
|
||||
import { transformColorWithOpacity } from '@sa/utils';
|
||||
import { transformColorWithOpacity } from '@sa/color';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
@@ -68,7 +68,7 @@ function handleClickMixMenu(menu: App.Global.Menu) {
|
||||
<component :is="icon" :class="[isMini ? 'text-icon-small' : 'text-icon-large']" />
|
||||
<p
|
||||
class="w-full ellipsis-text text-center text-12px transition-height-300"
|
||||
:class="[isMini ? 'h-0 pt-0' : 'h-24px pt-4px']"
|
||||
:class="[isMini ? 'h-0 pt-0' : 'h-20px pt-4px']"
|
||||
>
|
||||
{{ label }}
|
||||
</p>
|
||||
@@ -92,6 +92,7 @@ function handleClickMixMenu(menu: App.Global.Menu) {
|
||||
<MenuToggler
|
||||
arrow-icon
|
||||
:collapsed="appStore.siderCollapse"
|
||||
:z-index="99"
|
||||
:class="{ 'text-white:88 !hover:text-white': inverted }"
|
||||
@click="appStore.toggleSiderCollapse"
|
||||
/>
|
||||
|
@@ -55,8 +55,8 @@ function handleResetActiveMenu() {
|
||||
:inverted="siderInverted"
|
||||
:style="{ width: showDrawer ? themeStore.sider.mixChildMenuWidth + 'px' : '0px' }"
|
||||
>
|
||||
<header class="flex-y-center justify-between" :style="{ height: themeStore.header.height + 'px' }">
|
||||
<h2 class="pl-8px text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
|
||||
<header class="flex-y-center justify-between px-12px" :style="{ height: themeStore.header.height + 'px' }">
|
||||
<h2 class="text-16px text-primary font-bold">{{ $t('system.title') }}</h2>
|
||||
<PinToggler
|
||||
:pin="appStore.mixSiderFixed"
|
||||
:class="{ 'text-white:88 !hover:text-white': siderInverted }"
|
||||
|
@@ -34,9 +34,9 @@ function search() {
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
visible.value = false;
|
||||
// handle with setTimeout to prevent user from seeing some operations
|
||||
setTimeout(() => {
|
||||
visible.value = false;
|
||||
resultOptions.value = [];
|
||||
keyword.value = '';
|
||||
}, 200);
|
||||
@@ -73,10 +73,8 @@ function getActivePathIndex() {
|
||||
}
|
||||
|
||||
/** key enter */
|
||||
function handleEnter(e: Event | undefined) {
|
||||
function handleEnter() {
|
||||
if (resultOptions.value?.length === 0 || activePath.value === '') return;
|
||||
|
||||
e?.preventDefault();
|
||||
handleClose();
|
||||
router.push(activePath.value);
|
||||
}
|
||||
|
@@ -141,6 +141,10 @@ function init() {
|
||||
tabStore.initTabStore(route);
|
||||
}
|
||||
|
||||
function removeFocus() {
|
||||
(document.activeElement as HTMLElement)?.blur();
|
||||
}
|
||||
|
||||
// watch
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
@@ -162,7 +166,11 @@ init();
|
||||
<template>
|
||||
<DarkModeContainer class="size-full flex-y-center px-16px shadow-tab">
|
||||
<div ref="bsWrapper" class="h-full flex-1-hidden">
|
||||
<BetterScroll ref="bsScroll" :options="{ scrollX: true, scrollY: false, click: appStore.isMobile }">
|
||||
<BetterScroll
|
||||
ref="bsScroll"
|
||||
:options="{ scrollX: true, scrollY: false, click: appStore.isMobile }"
|
||||
@click="removeFocus"
|
||||
>
|
||||
<div
|
||||
ref="tabRef"
|
||||
class="h-full flex pr-18px"
|
||||
|
@@ -21,6 +21,10 @@ function handleSegmentChange(value: string | number) {
|
||||
themeStore.setThemeScheme(value as UnionKey.ThemeScheme);
|
||||
}
|
||||
|
||||
function handleGrayscaleChange(value: boolean) {
|
||||
themeStore.setGrayscale(value);
|
||||
}
|
||||
|
||||
const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layout.mode.includes('vertical'));
|
||||
</script>
|
||||
|
||||
@@ -46,6 +50,9 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo
|
||||
<NSwitch v-model:value="themeStore.sider.inverted" />
|
||||
</SettingItem>
|
||||
</Transition>
|
||||
<SettingItem :label="$t('theme.grayscale')">
|
||||
<NSwitch :value="themeStore.grayscale" @update:value="handleGrayscaleChange" />
|
||||
</SettingItem>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@@ -36,6 +36,27 @@ const swatches: string[] = [
|
||||
<template>
|
||||
<NDivider>{{ $t('theme.themeColor.title') }}</NDivider>
|
||||
<div class="flex-col-stretch gap-12px">
|
||||
<NTooltip placement="top-start">
|
||||
<template #trigger>
|
||||
<SettingItem key="recommend-color" :label="$t('theme.recommendColor')">
|
||||
<NSwitch v-model:value="themeStore.recommendColor" />
|
||||
</SettingItem>
|
||||
</template>
|
||||
<p>
|
||||
<span class="pr-12px">{{ $t('theme.recommendColorDesc') }}</span>
|
||||
<br />
|
||||
<NButton
|
||||
text
|
||||
tag="a"
|
||||
href="https://uicolors.app/create"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-gray"
|
||||
>
|
||||
https://uicolors.app/create
|
||||
</NButton>
|
||||
</p>
|
||||
</NTooltip>
|
||||
<SettingItem v-for="(_, key) in themeStore.themeColors" :key="key" :label="$t(`theme.themeColor.${key}`)">
|
||||
<template v-if="key === 'info'" #suffix>
|
||||
<NCheckbox v-model:checked="themeStore.isInfoFollowPrimary">
|
||||
|
@@ -1,6 +1,10 @@
|
||||
const local: App.I18n.Schema = {
|
||||
system: {
|
||||
title: 'SoybeanAdmin'
|
||||
title: 'SoybeanAdmin',
|
||||
updateTitle: 'System Version Update Notification',
|
||||
updateContent: 'A new version of the system has been detected. Do you want to refresh the page immediately?',
|
||||
updateConfirm: 'Refresh immediately',
|
||||
updateCancel: 'Later'
|
||||
},
|
||||
common: {
|
||||
action: 'Action',
|
||||
@@ -11,6 +15,7 @@ const local: App.I18n.Schema = {
|
||||
cancel: 'Cancel',
|
||||
close: 'Close',
|
||||
check: 'Check',
|
||||
expandColumn: 'Expand Column',
|
||||
columnSetting: 'Column Setting',
|
||||
config: 'Config',
|
||||
confirm: 'Confirm',
|
||||
@@ -57,6 +62,7 @@ const local: App.I18n.Schema = {
|
||||
dark: 'Dark',
|
||||
auto: 'Follow System'
|
||||
},
|
||||
grayscale: 'Grayscale',
|
||||
layoutMode: {
|
||||
title: 'Layout Mode',
|
||||
vertical: 'Vertical Menu Mode',
|
||||
@@ -64,6 +70,8 @@ const local: App.I18n.Schema = {
|
||||
'vertical-mix': 'Vertical Mix Menu Mode',
|
||||
'horizontal-mix': 'Horizontal Mix menu Mode'
|
||||
},
|
||||
recommendColor: 'Apply Recommended Color Algorithm',
|
||||
recommendColorDesc: 'The recommended color algorithm refers to',
|
||||
themeColor: {
|
||||
title: 'Theme Color',
|
||||
primary: 'Primary',
|
||||
@@ -293,6 +301,12 @@ const local: App.I18n.Schema = {
|
||||
superAdminVisible: 'Super Admin Visible',
|
||||
adminVisible: 'Admin Visible',
|
||||
adminOrUserVisible: 'Admin and User Visible'
|
||||
},
|
||||
request: {
|
||||
repeatedErrorOccurOnce: 'Repeated Request Error Occurs Once',
|
||||
repeatedError: 'Repeated Request Error',
|
||||
repeatedErrorMsg1: 'Custom Request Error 1',
|
||||
repeatedErrorMsg2: 'Custom Request Error 2'
|
||||
}
|
||||
},
|
||||
manage: {
|
||||
@@ -353,7 +367,7 @@ const local: App.I18n.Schema = {
|
||||
menuName: 'Menu Name',
|
||||
routeName: 'Route Name',
|
||||
routePath: 'Route Path',
|
||||
routeParams: 'Route Params',
|
||||
pathParam: 'Path Param',
|
||||
layout: 'Layout Component',
|
||||
page: 'Page Component',
|
||||
i18nKey: 'I18n Key',
|
||||
@@ -361,12 +375,14 @@ const local: App.I18n.Schema = {
|
||||
localIcon: 'Local Icon',
|
||||
iconTypeTitle: 'Icon Type',
|
||||
order: 'Order',
|
||||
constant: 'Constant',
|
||||
keepAlive: 'Keep Alive',
|
||||
href: 'Href',
|
||||
hideInMenu: 'Hide In Menu',
|
||||
activeMenu: 'Active Menu',
|
||||
multiTab: 'Multi Tab',
|
||||
fixedIndexInTab: 'Fixed Index In Tab',
|
||||
query: 'Query Params',
|
||||
button: 'Button',
|
||||
buttonCode: 'Button Code',
|
||||
buttonDesc: 'Button Desc',
|
||||
@@ -377,6 +393,7 @@ const local: App.I18n.Schema = {
|
||||
menuName: 'Please enter menu name',
|
||||
routeName: 'Please enter route name',
|
||||
routePath: 'Please enter route path',
|
||||
pathParam: 'Please enter path param',
|
||||
page: 'Please select page component',
|
||||
layout: 'Please select layout component',
|
||||
i18nKey: 'Please enter i18n key',
|
||||
@@ -386,10 +403,12 @@ const local: App.I18n.Schema = {
|
||||
keepAlive: 'Please select whether to cache route',
|
||||
href: 'Please enter href',
|
||||
hideInMenu: 'Please select whether to hide menu',
|
||||
activeMenu: 'Please enter the route name of the highlighted menu',
|
||||
activeMenu: 'Please select route name of the highlighted menu',
|
||||
multiTab: 'Please select whether to support multiple tabs',
|
||||
fixedInTab: 'Please select whether to fix in the tab',
|
||||
fixedIndexInTab: 'Please enter the index fixed in the tab',
|
||||
queryKey: 'Please enter route parameter Key',
|
||||
queryValue: 'Please enter route parameter Value',
|
||||
button: 'Please select whether it is a button',
|
||||
buttonCode: 'Please enter button code',
|
||||
buttonDesc: 'Please enter button description',
|
||||
@@ -454,6 +473,9 @@ const local: App.I18n.Schema = {
|
||||
expand: 'Expand Menu',
|
||||
pin: 'Pin',
|
||||
unpin: 'Unpin'
|
||||
},
|
||||
datatable: {
|
||||
itemCount: 'Total {total} items'
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,10 @@
|
||||
const local: App.I18n.Schema = {
|
||||
system: {
|
||||
title: 'Soybean 管理系统'
|
||||
title: 'Soybean 管理系统',
|
||||
updateTitle: '系统版本更新通知',
|
||||
updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
|
||||
updateConfirm: '立即刷新',
|
||||
updateCancel: '稍后再说'
|
||||
},
|
||||
common: {
|
||||
action: '操作',
|
||||
@@ -11,6 +15,7 @@ const local: App.I18n.Schema = {
|
||||
cancel: '取消',
|
||||
close: '关闭',
|
||||
check: '勾选',
|
||||
expandColumn: '展开列',
|
||||
columnSetting: '列设置',
|
||||
config: '配置',
|
||||
confirm: '确认',
|
||||
@@ -57,6 +62,7 @@ const local: App.I18n.Schema = {
|
||||
dark: '暗黑模式',
|
||||
auto: '跟随系统'
|
||||
},
|
||||
grayscale: '灰度模式',
|
||||
layoutMode: {
|
||||
title: '布局模式',
|
||||
vertical: '左侧菜单模式',
|
||||
@@ -64,6 +70,8 @@ const local: App.I18n.Schema = {
|
||||
horizontal: '顶部菜单模式',
|
||||
'horizontal-mix': '顶部菜单混合模式'
|
||||
},
|
||||
recommendColor: '应用推荐算法的颜色',
|
||||
recommendColorDesc: '推荐颜色的算法参照',
|
||||
themeColor: {
|
||||
title: '主题颜色',
|
||||
primary: '主色',
|
||||
@@ -293,6 +301,12 @@ const local: App.I18n.Schema = {
|
||||
superAdminVisible: '超级管理员可见',
|
||||
adminVisible: '管理员可见',
|
||||
adminOrUserVisible: '管理员和用户可见'
|
||||
},
|
||||
request: {
|
||||
repeatedErrorOccurOnce: '重复请求错误只出现一次',
|
||||
repeatedError: '重复请求错误',
|
||||
repeatedErrorMsg1: '自定义请求错误 1',
|
||||
repeatedErrorMsg2: '自定义请求错误 2'
|
||||
}
|
||||
},
|
||||
manage: {
|
||||
@@ -353,7 +367,7 @@ const local: App.I18n.Schema = {
|
||||
menuName: '菜单名称',
|
||||
routeName: '路由名称',
|
||||
routePath: '路由路径',
|
||||
routeParams: '路由参数',
|
||||
pathParam: '路径参数',
|
||||
layout: '布局',
|
||||
page: '页面组件',
|
||||
i18nKey: '国际化key',
|
||||
@@ -361,12 +375,14 @@ const local: App.I18n.Schema = {
|
||||
localIcon: '本地图标',
|
||||
iconTypeTitle: '图标类型',
|
||||
order: '排序',
|
||||
constant: '常量路由',
|
||||
keepAlive: '缓存路由',
|
||||
href: '外链',
|
||||
hideInMenu: '隐藏菜单',
|
||||
activeMenu: '高亮的菜单',
|
||||
multiTab: '支持多页签',
|
||||
fixedIndexInTab: '固定在页签中的序号',
|
||||
query: '路由参数',
|
||||
button: '按钮',
|
||||
buttonCode: '按钮编码',
|
||||
buttonDesc: '按钮描述',
|
||||
@@ -377,6 +393,7 @@ const local: App.I18n.Schema = {
|
||||
menuName: '请输入菜单名称',
|
||||
routeName: '请输入路由名称',
|
||||
routePath: '请输入路由路径',
|
||||
pathParam: '请输入路径参数',
|
||||
page: '请选择页面组件',
|
||||
layout: '请选择布局组件',
|
||||
i18nKey: '请输入国际化key',
|
||||
@@ -386,10 +403,12 @@ const local: App.I18n.Schema = {
|
||||
keepAlive: '请选择是否缓存路由',
|
||||
href: '请输入外链',
|
||||
hideInMenu: '请选择是否隐藏菜单',
|
||||
activeMenu: '请输入高亮的菜单的路由名称',
|
||||
activeMenu: '请选择高亮的菜单的路由名称',
|
||||
multiTab: '请选择是否支持多标签',
|
||||
fixedInTab: '请选择是否固定在页签中',
|
||||
fixedIndexInTab: '请输入固定在页签中的序号',
|
||||
queryKey: '请输入路由参数Key',
|
||||
queryValue: '请输入路由参数Value',
|
||||
button: '请选择是否按钮',
|
||||
buttonCode: '请输入按钮编码',
|
||||
buttonDesc: '请输入按钮描述',
|
||||
@@ -454,6 +473,9 @@ const local: App.I18n.Schema = {
|
||||
expand: '展开菜单',
|
||||
pin: '固定',
|
||||
unpin: '取消固定'
|
||||
},
|
||||
datatable: {
|
||||
itemCount: '共 {total} 条'
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { createApp } from 'vue';
|
||||
import './plugins/assets';
|
||||
import { setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins';
|
||||
import { setupAppVersionNotification, setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins';
|
||||
import { setupStore } from './store';
|
||||
import { setupRouter } from './router';
|
||||
import { setupI18n } from './locales';
|
||||
@@ -23,6 +23,8 @@ async function setupApp() {
|
||||
|
||||
setupI18n(app);
|
||||
|
||||
setupAppVersionNotification();
|
||||
|
||||
app.mount('#app');
|
||||
}
|
||||
|
||||
|
66
src/plugins/app.ts
Normal file
66
src/plugins/app.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { h } from 'vue';
|
||||
import { NButton } from 'naive-ui';
|
||||
import { $t } from '../locales';
|
||||
|
||||
export function setupAppVersionNotification() {
|
||||
let isShow = false;
|
||||
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
const preConditions = [!isShow, document.visibilityState === 'visible', !import.meta.env.DEV];
|
||||
|
||||
if (!preConditions.every(Boolean)) return;
|
||||
|
||||
const buildTime = await getHtmlBuildTime();
|
||||
|
||||
if (buildTime === BUILD_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
isShow = true;
|
||||
|
||||
const n = window.$notification?.create({
|
||||
title: $t('system.updateTitle'),
|
||||
content: $t('system.updateContent'),
|
||||
action() {
|
||||
return h('div', { style: { display: 'flex', justifyContent: 'end', gap: '12px', width: '325px' } }, [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
onClick() {
|
||||
n?.destroy();
|
||||
}
|
||||
},
|
||||
() => $t('system.updateCancel')
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
type: 'primary',
|
||||
onClick() {
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
() => $t('system.updateConfirm')
|
||||
)
|
||||
]);
|
||||
},
|
||||
onClose() {
|
||||
isShow = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getHtmlBuildTime() {
|
||||
const baseURL = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
const res = await fetch(`${baseURL}index.html`);
|
||||
|
||||
const html = await res.text();
|
||||
|
||||
const match = html.match(/<meta name="buildTime" content="(.*)">/);
|
||||
|
||||
const buildTime = match?.[1] || '';
|
||||
|
||||
return buildTime;
|
||||
}
|
@@ -2,3 +2,4 @@ export * from './loading';
|
||||
export * from './nprogress';
|
||||
export * from './iconify';
|
||||
export * from './dayjs';
|
||||
export * from './app';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// @unocss-include
|
||||
import { getRgbOfColor } from '@sa/utils';
|
||||
import { getRgb } from '@sa/color';
|
||||
import { $t } from '@/locales';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import systemLogo from '@/assets/svg-icon/logo.svg?raw';
|
||||
@@ -7,7 +7,7 @@ import systemLogo from '@/assets/svg-icon/logo.svg?raw';
|
||||
export function setupLoading() {
|
||||
const themeColor = localStg.get('themeColor') || '#646cff';
|
||||
|
||||
const { r, g, b } = getRgbOfColor(themeColor);
|
||||
const { r, g, b } = getRgb(themeColor);
|
||||
|
||||
const primaryColor = `--primary-color: ${r} ${g} ${b}`;
|
||||
|
||||
|
@@ -42,7 +42,13 @@ function transformElegantRouteToVueRoute(
|
||||
}
|
||||
|
||||
function getLayoutName(component: string) {
|
||||
return component.replace(LAYOUT_PREFIX, '');
|
||||
const layout = component.replace(LAYOUT_PREFIX, '');
|
||||
|
||||
if(!layouts[layout]) {
|
||||
throw new Error(`Layout component "${layout}" not found`);
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
function isView(component: string) {
|
||||
@@ -50,7 +56,13 @@ function transformElegantRouteToVueRoute(
|
||||
}
|
||||
|
||||
function getViewName(component: string) {
|
||||
return component.replace(VIEW_PREFIX, '');
|
||||
const view = component.replace(VIEW_PREFIX, '');
|
||||
|
||||
if(!views[view]) {
|
||||
throw new Error(`View component "${view}" not found`);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
function isFirstLevelRoute(item: ElegantConstRoute) {
|
||||
@@ -81,39 +93,45 @@ function transformElegantRouteToVueRoute(
|
||||
|
||||
const vueRoute = { name, path, ...rest } as RouteRecordRaw;
|
||||
|
||||
if (component) {
|
||||
if (isSingleLevelRoute(route)) {
|
||||
const { layout, view } = getSingleLevelRouteComponent(component);
|
||||
|
||||
const singleLevelRoute: RouteRecordRaw = {
|
||||
path,
|
||||
component: layouts[layout],
|
||||
children: [
|
||||
{
|
||||
name,
|
||||
path: '',
|
||||
component: views[view],
|
||||
...rest
|
||||
} as RouteRecordRaw
|
||||
]
|
||||
};
|
||||
|
||||
return [singleLevelRoute];
|
||||
try {
|
||||
if (component) {
|
||||
if (isSingleLevelRoute(route)) {
|
||||
const { layout, view } = getSingleLevelRouteComponent(component);
|
||||
|
||||
const singleLevelRoute: RouteRecordRaw = {
|
||||
path,
|
||||
component: layouts[layout],
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@@ -46,7 +46,7 @@ export function createRouteGuard(router: Router) {
|
||||
next({ name: rootRoute });
|
||||
}
|
||||
},
|
||||
// if is is constant route, then it is allowed to access directly
|
||||
// if it is constant route, then it is allowed to access directly
|
||||
{
|
||||
condition: !needLogin,
|
||||
callback: () => {
|
||||
@@ -92,6 +92,7 @@ export function createRouteGuard(router: Router) {
|
||||
* @param to to route
|
||||
*/
|
||||
async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
|
||||
const authStore = useAuthStore();
|
||||
const routeStore = useRouteStore();
|
||||
|
||||
const notFoundRoute: RouteKey = 'not-found';
|
||||
@@ -160,6 +161,8 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
|
||||
return location;
|
||||
}
|
||||
|
||||
await authStore.initUserInfo();
|
||||
|
||||
// initialize the auth route
|
||||
await routeStore.initAuthRoute();
|
||||
|
||||
|
@@ -30,18 +30,6 @@ export function fetchGetUserList(params?: Api.SystemManage.UserSearchParams) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get menu list
|
||||
*
|
||||
* @deprecated this will removed in next version 1.1.0
|
||||
*/
|
||||
export function fetchGetMenuListV1() {
|
||||
return request<Api.SystemManage.Menu[]>({
|
||||
url: '/systemManage/getMenuList',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/** get menu list */
|
||||
export function fetchGetMenuList() {
|
||||
return request<Api.SystemManage.MenuList>({
|
||||
|
@@ -1,20 +1,16 @@
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { $t } from '@/locales';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
import { $t } from '@/locales';
|
||||
import { handleRefreshToken } from './shared';
|
||||
import { handleRefreshToken, showErrorMsg } from './shared';
|
||||
import type { RequestInstanceState } from './type';
|
||||
|
||||
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
|
||||
|
||||
interface InstanceState {
|
||||
/** whether the request is refreshing token */
|
||||
isRefreshingToken: boolean;
|
||||
}
|
||||
|
||||
export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
|
||||
{
|
||||
baseURL,
|
||||
headers: {
|
||||
@@ -35,7 +31,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
isBackendSuccess(response) {
|
||||
// when the backend response code is "0000"(default), it means the request is success
|
||||
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
|
||||
return response.data.code === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
||||
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
|
||||
},
|
||||
async onBackendFail(response, instance) {
|
||||
const authStore = useAuthStore();
|
||||
@@ -47,6 +43,8 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
function logoutAndCleanup() {
|
||||
handleLogout();
|
||||
window.removeEventListener('beforeunload', handleLogout);
|
||||
|
||||
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
|
||||
@@ -58,7 +56,9 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
|
||||
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
|
||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||
if (modalLogoutCodes.includes(response.data.code)) {
|
||||
if (modalLogoutCodes.includes(response.data.code) && !request.state.errMsgStack?.includes(response.data.msg)) {
|
||||
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
|
||||
|
||||
// prevent the user from refreshing the page
|
||||
window.addEventListener('beforeunload', handleLogout);
|
||||
|
||||
@@ -67,6 +67,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
content: response.data.msg,
|
||||
positiveText: $t('common.confirm'),
|
||||
maskClosable: false,
|
||||
closeOnEsc: false,
|
||||
onPositiveClick() {
|
||||
logoutAndCleanup();
|
||||
},
|
||||
@@ -122,7 +123,7 @@ export const request = createFlatRequest<App.Service.Response, InstanceState>(
|
||||
return;
|
||||
}
|
||||
|
||||
window.$message?.error?.(message);
|
||||
showErrorMsg(request.state, message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -2,6 +2,7 @@ import type { AxiosRequestConfig } from 'axios';
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { fetchRefreshToken } from '../api';
|
||||
import type { RequestInstanceState } from './type';
|
||||
|
||||
/**
|
||||
* refresh token
|
||||
@@ -29,3 +30,25 @@ export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function showErrorMsg(state: RequestInstanceState, message: string) {
|
||||
if (!state.errMsgStack?.length) {
|
||||
state.errMsgStack = [];
|
||||
}
|
||||
|
||||
const isExist = state.errMsgStack.includes(message);
|
||||
|
||||
if (!isExist) {
|
||||
state.errMsgStack.push(message);
|
||||
|
||||
window.$message?.error(message, {
|
||||
onLeave: () => {
|
||||
state.errMsgStack = state.errMsgStack.filter(msg => msg !== message);
|
||||
|
||||
setTimeout(() => {
|
||||
state.errMsgStack = [];
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
6
src/service/request/type.ts
Normal file
6
src/service/request/type.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface RequestInstanceState {
|
||||
/** whether the request is refreshing token */
|
||||
isRefreshingToken: boolean;
|
||||
/** the request error message stack */
|
||||
errMsgStack: string[];
|
||||
}
|
@@ -123,7 +123,7 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
||||
// update tabs by locale
|
||||
tabStore.updateTabsByLocale();
|
||||
|
||||
// sey dayjs locale
|
||||
// set dayjs locale
|
||||
setDayjsLocale(locale.value);
|
||||
});
|
||||
});
|
||||
|
@@ -8,17 +8,24 @@ import { fetchGetUserInfo, fetchLogin } from '@/service/api';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { $t } from '@/locales';
|
||||
import { useRouteStore } from '../route';
|
||||
import { clearAuthStorage, getToken, getUserInfo } from './shared';
|
||||
import { useTabStore } from '../tab';
|
||||
import { clearAuthStorage, getToken } from './shared';
|
||||
|
||||
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
const route = useRoute();
|
||||
const routeStore = useRouteStore();
|
||||
const tabStore = useTabStore();
|
||||
const { toLogin, redirectFromLogin } = useRouterPush(false);
|
||||
const { loading: loginLoading, startLoading, endLoading } = useLoading();
|
||||
|
||||
const token = ref(getToken());
|
||||
|
||||
const userInfo: Api.Auth.UserInfo = reactive(getUserInfo());
|
||||
const userInfo: Api.Auth.UserInfo = reactive({
|
||||
userId: '',
|
||||
userName: '',
|
||||
roles: [],
|
||||
buttons: []
|
||||
});
|
||||
|
||||
/** is super role in static route */
|
||||
const isStaticSuper = computed(() => {
|
||||
@@ -42,6 +49,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
await toLogin();
|
||||
}
|
||||
|
||||
tabStore.cacheTabs();
|
||||
routeStore.resetStore();
|
||||
}
|
||||
|
||||
@@ -87,14 +95,23 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
localStg.set('token', loginToken.token);
|
||||
localStg.set('refreshToken', loginToken.refreshToken);
|
||||
|
||||
// 2. get user info
|
||||
const pass = await getUserInfo();
|
||||
|
||||
if (pass) {
|
||||
token.value = loginToken.token;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getUserInfo() {
|
||||
const { data: info, error } = await fetchGetUserInfo();
|
||||
|
||||
if (!error) {
|
||||
// 2. store user info
|
||||
localStg.set('userInfo', info);
|
||||
|
||||
// 3. update store
|
||||
token.value = loginToken.token;
|
||||
// update store
|
||||
Object.assign(userInfo, info);
|
||||
|
||||
return true;
|
||||
@@ -103,6 +120,18 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function initUserInfo() {
|
||||
const hasToken = getToken();
|
||||
|
||||
if (hasToken) {
|
||||
const pass = await getUserInfo();
|
||||
|
||||
if (!pass) {
|
||||
resetStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
userInfo,
|
||||
@@ -110,6 +139,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
isLogin,
|
||||
loginLoading,
|
||||
resetStore,
|
||||
login
|
||||
login,
|
||||
initUserInfo
|
||||
};
|
||||
});
|
||||
|
@@ -5,27 +5,8 @@ export function getToken() {
|
||||
return localStg.get('token') || '';
|
||||
}
|
||||
|
||||
/** Get user info */
|
||||
export function getUserInfo() {
|
||||
const emptyInfo: Api.Auth.UserInfo = {
|
||||
userId: '',
|
||||
userName: '',
|
||||
roles: [],
|
||||
buttons: []
|
||||
};
|
||||
const userInfo = localStg.get('userInfo') || emptyInfo;
|
||||
|
||||
// fix new property: buttons, this will be removed in the next version `1.1.0`
|
||||
if (!userInfo.buttons) {
|
||||
userInfo.buttons = [];
|
||||
}
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/** Clear auth storage */
|
||||
export function clearAuthStorage() {
|
||||
localStg.remove('token');
|
||||
localStg.remove('refreshToken');
|
||||
localStg.remove('userInfo');
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
const constantRoutes = shallowRef<ElegantConstRoute[]>([]);
|
||||
|
||||
function addConstantRoutes(routes: ElegantConstRoute[]) {
|
||||
const constantRoutesMap = new Map(constantRoutes.value.map(route => [route.name, route]));
|
||||
const constantRoutesMap = new Map<string, ElegantConstRoute>([]);
|
||||
|
||||
routes.forEach(route => {
|
||||
constantRoutesMap.set(route.name, route);
|
||||
@@ -69,7 +69,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
const authRoutes = shallowRef<ElegantConstRoute[]>([]);
|
||||
|
||||
function addAuthRoutes(routes: ElegantConstRoute[]) {
|
||||
const authRoutesMap = new Map(authRoutes.value.map(route => [route.name, route]));
|
||||
const authRoutesMap = new Map<string, ElegantConstRoute>([]);
|
||||
|
||||
routes.forEach(route => {
|
||||
authRoutesMap.set(route.name, route);
|
||||
@@ -97,13 +97,19 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
/** Cache routes */
|
||||
const cacheRoutes = ref<RouteKey[]>([]);
|
||||
|
||||
/** All cache routes */
|
||||
const allCacheRoutes = shallowRef<RouteKey[]>([]);
|
||||
|
||||
/**
|
||||
* Get cache routes
|
||||
*
|
||||
* @param routes Vue routes
|
||||
*/
|
||||
function getCacheRoutes(routes: RouteRecordRaw[]) {
|
||||
cacheRoutes.value = getCacheRouteNames(routes);
|
||||
const alls = getCacheRouteNames(routes);
|
||||
|
||||
cacheRoutes.value = alls;
|
||||
allCacheRoutes.value = [...alls];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,12 +136,23 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
cacheRoutes.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is cached route
|
||||
*
|
||||
* @param routeKey
|
||||
*/
|
||||
function isCachedRoute(routeKey: RouteKey) {
|
||||
return allCacheRoutes.value.includes(routeKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re cache routes by route key
|
||||
*
|
||||
* @param routeKey
|
||||
*/
|
||||
async function reCacheRoutesByKey(routeKey: RouteKey) {
|
||||
if (!isCachedRoute(routeKey)) return;
|
||||
|
||||
removeCacheRoutes(routeKey);
|
||||
|
||||
await appStore.reloadPage();
|
||||
@@ -179,15 +196,18 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
async function initConstantRoute() {
|
||||
if (isInitConstantRoute.value) return;
|
||||
|
||||
if (authRouteMode.value === 'static') {
|
||||
const staticRoute = createStaticRoutes();
|
||||
const staticRoute = createStaticRoutes();
|
||||
|
||||
if (authRouteMode.value === 'static') {
|
||||
addConstantRoutes(staticRoute.constantRoutes);
|
||||
} else {
|
||||
const { data, error } = await fetchGetConstantRoutes();
|
||||
|
||||
if (!error) {
|
||||
addConstantRoutes(data);
|
||||
} else {
|
||||
// if fetch constant routes failed, use static constant routes
|
||||
addConstantRoutes(staticRoute.constantRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +219,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
/** Init auth route */
|
||||
async function initAuthRoute() {
|
||||
if (authRouteMode.value === 'static') {
|
||||
await initStaticAuthRoute();
|
||||
initStaticAuthRoute();
|
||||
} else {
|
||||
await initDynamicAuthRoute();
|
||||
}
|
||||
@@ -208,7 +228,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
}
|
||||
|
||||
/** Init static auth route */
|
||||
async function initStaticAuthRoute() {
|
||||
function initStaticAuthRoute() {
|
||||
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
|
||||
|
||||
if (authStore.isStaticSuper) {
|
||||
@@ -240,6 +260,9 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
handleUpdateRootRouteRedirect(home);
|
||||
|
||||
setIsInitAuthRoute(true);
|
||||
} else {
|
||||
// if fetch user routes failed, reset store
|
||||
authStore.resetStore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,15 +355,31 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected menu meta by key
|
||||
* Get route meta by key
|
||||
*
|
||||
* @param selectedKey Selected menu key
|
||||
* @param key Route key
|
||||
*/
|
||||
function getSelectedMenuMetaByKey(selectedKey: string) {
|
||||
// The routes in router.options.routes are static, you need to use router.getRoutes() to get all the routes.
|
||||
function getRouteMetaByKey(key: string) {
|
||||
const allRoutes = router.getRoutes();
|
||||
|
||||
return allRoutes.find(route => route.name === selectedKey)?.meta || null;
|
||||
return allRoutes.find(route => route.name === key)?.meta || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route query of meta by key
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
function getRouteQueryOfMetaByKey(key: string) {
|
||||
const meta = getRouteMetaByKey(key);
|
||||
|
||||
const query: Record<string, string> = {};
|
||||
|
||||
meta?.query?.forEach(item => {
|
||||
query[item.key] = item.value;
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -360,6 +399,6 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
setIsInitAuthRoute,
|
||||
getIsAuthRouteExist,
|
||||
getSelectedMenuKeyPath,
|
||||
getSelectedMenuMetaByKey
|
||||
getRouteQueryOfMetaByKey
|
||||
};
|
||||
});
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { router } from '@/router';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import { localStg } from '@/utils/storage';
|
||||
@@ -17,13 +17,13 @@ import {
|
||||
getDefaultHomeTab,
|
||||
getFixedTabIds,
|
||||
getTabByRoute,
|
||||
getTabIdByRoute,
|
||||
isTabInTabs,
|
||||
updateTabByI18nKey,
|
||||
updateTabsByI18nKey
|
||||
} from './shared';
|
||||
|
||||
export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
||||
const router = useRouter();
|
||||
const routeStore = useRouteStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPush } = useRouterPush(false);
|
||||
@@ -289,6 +289,8 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
|
||||
setTabLabel,
|
||||
resetTabLabel,
|
||||
isTabRetain,
|
||||
updateTabsByLocale
|
||||
updateTabsByLocale,
|
||||
getTabIdByRoute,
|
||||
cacheTabs
|
||||
};
|
||||
});
|
||||
|
@@ -2,10 +2,17 @@ import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
|
||||
import { getColorPalette } from '@sa/color-palette';
|
||||
import { getPaletteColorByNumber } from '@sa/color';
|
||||
import { SetupStoreId } from '@/enum';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { addThemeVarsToHtml, createThemeToken, getNaiveTheme, initThemeSettings, toggleCssDarkMode } from './shared';
|
||||
import {
|
||||
addThemeVarsToHtml,
|
||||
createThemeToken,
|
||||
getNaiveTheme,
|
||||
initThemeSettings,
|
||||
toggleCssDarkMode,
|
||||
toggleGrayscaleMode
|
||||
} from './shared';
|
||||
|
||||
/** Theme store */
|
||||
export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
@@ -23,6 +30,9 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
return settings.value.themeScheme === 'dark';
|
||||
});
|
||||
|
||||
/** grayscale mode */
|
||||
const grayscaleMode = computed(() => settings.value.grayscale);
|
||||
|
||||
/** Theme colors */
|
||||
const themeColors = computed(() => {
|
||||
const { themeColor, otherColor, isInfoFollowPrimary } = settings.value;
|
||||
@@ -35,7 +45,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
});
|
||||
|
||||
/** Naive theme */
|
||||
const naiveTheme = computed(() => getNaiveTheme(themeColors.value));
|
||||
const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value.recommendColor));
|
||||
|
||||
/**
|
||||
* Settings json
|
||||
@@ -60,6 +70,15 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
settings.value.themeScheme = themeScheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grayscale value
|
||||
*
|
||||
* @param isGrayscale
|
||||
*/
|
||||
function setGrayscale(isGrayscale: boolean) {
|
||||
settings.value.grayscale = isGrayscale;
|
||||
}
|
||||
|
||||
/** Toggle theme scheme */
|
||||
function toggleThemeScheme() {
|
||||
const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto'];
|
||||
@@ -80,13 +99,18 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
* @param color Theme color
|
||||
*/
|
||||
function updateThemeColors(key: App.Theme.ThemeColorKey, color: string) {
|
||||
// get a color palette by provided color and color name, and use the suitable color
|
||||
const { main } = getColorPalette(color);
|
||||
let colorValue = color;
|
||||
|
||||
if (settings.value.recommendColor) {
|
||||
// get a color palette by provided color and color name, and use the suitable color
|
||||
|
||||
colorValue = getPaletteColorByNumber(color, 500, true);
|
||||
}
|
||||
|
||||
if (key === 'primary') {
|
||||
settings.value.themeColor = main.hex;
|
||||
settings.value.themeColor = colorValue;
|
||||
} else {
|
||||
settings.value.otherColor[key] = main.hex;
|
||||
settings.value.otherColor[key] = colorValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +125,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
|
||||
/** Setup theme vars to html */
|
||||
function setupThemeVarsToHtml() {
|
||||
const { themeTokens, darkThemeTokens } = createThemeToken(themeColors.value);
|
||||
const { themeTokens, darkThemeTokens } = createThemeToken(themeColors.value, settings.value.recommendColor);
|
||||
addThemeVarsToHtml(themeTokens, darkThemeTokens);
|
||||
}
|
||||
|
||||
@@ -130,12 +154,19 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
grayscaleMode,
|
||||
val => {
|
||||
toggleGrayscaleMode(val);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// themeColors change, update css vars and storage theme color
|
||||
watch(
|
||||
themeColors,
|
||||
val => {
|
||||
setupThemeVarsToHtml();
|
||||
|
||||
localStg.set('themeColor', val.primary);
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -153,6 +184,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
themeColors,
|
||||
naiveTheme,
|
||||
settingsJson,
|
||||
setGrayscale,
|
||||
resetStore,
|
||||
setThemeScheme,
|
||||
toggleThemeScheme,
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import type { GlobalThemeOverrides } from 'naive-ui';
|
||||
import { getColorByPaletteNumber, getColorPalette } from '@sa/color-palette';
|
||||
import { addColorAlpha, getRgbOfColor } from '@sa/utils';
|
||||
import { addColorAlpha, getColorPalette, getPaletteColorByNumber, getRgb } from '@sa/color';
|
||||
import { overrideThemeSettings, themeSettings } from '@/theme/settings';
|
||||
import { themeVars } from '@/theme/vars';
|
||||
import { toggleHtmlClass } from '@/utils/common';
|
||||
import { localStg } from '@/utils/storage';
|
||||
|
||||
const DARK_CLASS = 'dark';
|
||||
@@ -33,9 +33,10 @@ export function initThemeSettings() {
|
||||
* Create theme token
|
||||
*
|
||||
* @param colors Theme colors
|
||||
* @param [recommended=false] Use recommended color. Default is `false`
|
||||
*/
|
||||
export function createThemeToken(colors: App.Theme.ThemeColor) {
|
||||
const paletteColors = createThemePaletteColors(colors);
|
||||
export function createThemeToken(colors: App.Theme.ThemeColor, recommended = false) {
|
||||
const paletteColors = createThemePaletteColors(colors, recommended);
|
||||
|
||||
const themeTokens: App.Theme.ThemeToken = {
|
||||
colors: {
|
||||
@@ -75,18 +76,19 @@ export function createThemeToken(colors: App.Theme.ThemeColor) {
|
||||
* Create theme palette colors
|
||||
*
|
||||
* @param colors Theme colors
|
||||
* @param [recommended=false] Use recommended color. Default is `false`
|
||||
*/
|
||||
function createThemePaletteColors(colors: App.Theme.ThemeColor) {
|
||||
function createThemePaletteColors(colors: App.Theme.ThemeColor, recommended = false) {
|
||||
const colorKeys = Object.keys(colors) as App.Theme.ThemeColorKey[];
|
||||
const colorPaletteVar = {} as App.Theme.ThemePaletteColor;
|
||||
|
||||
colorKeys.forEach(key => {
|
||||
const { palettes, main } = getColorPalette(colors[key]);
|
||||
const colorMap = getColorPalette(colors[key], recommended);
|
||||
|
||||
colorPaletteVar[key] = main.hex;
|
||||
colorPaletteVar[key] = colorMap.get(500)!;
|
||||
|
||||
palettes.forEach(item => {
|
||||
colorPaletteVar[`${key}-${item.number}`] = item.hex;
|
||||
colorMap.forEach((hex, number) => {
|
||||
colorPaletteVar[`${key}-${number}`] = hex;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,7 +118,7 @@ function getCssVarByTokens(tokens: App.Theme.BaseToken) {
|
||||
|
||||
if (key === 'colors') {
|
||||
cssVarsKey = removeRgbPrefix(cssVarsKey);
|
||||
const { r, g, b } = getRgbOfColor(cssValue);
|
||||
const { r, g, b } = getRgb(cssValue);
|
||||
cssValue = `${r} ${g} ${b}`;
|
||||
}
|
||||
|
||||
@@ -167,18 +169,29 @@ export function addThemeVarsToHtml(tokens: App.Theme.BaseToken, darkTokens: App.
|
||||
* @param darkMode Is dark mode
|
||||
*/
|
||||
export function toggleCssDarkMode(darkMode = false) {
|
||||
function addDarkClass() {
|
||||
document.documentElement.classList.add(DARK_CLASS);
|
||||
}
|
||||
|
||||
function removeDarkClass() {
|
||||
document.documentElement.classList.remove(DARK_CLASS);
|
||||
}
|
||||
const { add, remove } = toggleHtmlClass(DARK_CLASS);
|
||||
|
||||
if (darkMode) {
|
||||
addDarkClass();
|
||||
add();
|
||||
} else {
|
||||
removeDarkClass();
|
||||
remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle grayscale mode
|
||||
*
|
||||
* @param grayscaleMode Is grayscale mode
|
||||
*/
|
||||
export function toggleGrayscaleMode(grayscaleMode = false) {
|
||||
const GRAYSCALE_CLASS = 'grayscale';
|
||||
|
||||
const { add, remove } = toggleHtmlClass(GRAYSCALE_CLASS);
|
||||
|
||||
if (grayscaleMode) {
|
||||
add();
|
||||
} else {
|
||||
remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,13 +207,14 @@ interface NaiveColorAction {
|
||||
* Get naive theme colors
|
||||
*
|
||||
* @param colors Theme colors
|
||||
* @param [recommended=false] Use recommended color. Default is `false`
|
||||
*/
|
||||
function getNaiveThemeColors(colors: App.Theme.ThemeColor) {
|
||||
function getNaiveThemeColors(colors: App.Theme.ThemeColor, recommended = false) {
|
||||
const colorActions: NaiveColorAction[] = [
|
||||
{ scene: '', handler: color => color },
|
||||
{ scene: 'Suppl', handler: color => color },
|
||||
{ scene: 'Hover', handler: color => getColorByPaletteNumber(color, 500) },
|
||||
{ scene: 'Pressed', handler: color => getColorByPaletteNumber(color, 700) },
|
||||
{ scene: 'Hover', handler: color => getPaletteColorByNumber(color, 500, recommended) },
|
||||
{ scene: 'Pressed', handler: color => getPaletteColorByNumber(color, 700, recommended) },
|
||||
{ scene: 'Active', handler: color => addColorAlpha(color, 0.1) }
|
||||
];
|
||||
|
||||
@@ -223,13 +237,14 @@ function getNaiveThemeColors(colors: App.Theme.ThemeColor) {
|
||||
* Get naive theme
|
||||
*
|
||||
* @param colors Theme colors
|
||||
* @param [recommended=false] Use recommended color. Default is `false`
|
||||
*/
|
||||
export function getNaiveTheme(colors: App.Theme.ThemeColor) {
|
||||
export function getNaiveTheme(colors: App.Theme.ThemeColor, recommended = false) {
|
||||
const { primary: colorLoading } = colors;
|
||||
|
||||
const theme: GlobalThemeOverrides = {
|
||||
common: {
|
||||
...getNaiveThemeColors(colors),
|
||||
...getNaiveThemeColors(colors, recommended),
|
||||
borderRadius: '6px'
|
||||
},
|
||||
LoadingBar: {
|
||||
|
@@ -11,3 +11,7 @@ body,
|
||||
html {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
html.grayscale {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
/** Default theme settings */
|
||||
export const themeSettings: App.Theme.ThemeSetting = {
|
||||
themeScheme: 'light',
|
||||
grayscale: false,
|
||||
recommendColor: false,
|
||||
themeColor: '#646cff',
|
||||
otherColor: {
|
||||
info: '#2080f0',
|
||||
|
50
src/typings/api.d.ts
vendored
50
src/typings/api.d.ts
vendored
@@ -171,6 +171,20 @@ declare namespace Api {
|
||||
*/
|
||||
type IconType = '1' | '2';
|
||||
|
||||
type MenuPropsOfRoute = Pick<
|
||||
import('vue-router').RouteMeta,
|
||||
| 'i18nKey'
|
||||
| 'keepAlive'
|
||||
| 'constant'
|
||||
| 'order'
|
||||
| 'href'
|
||||
| 'hideInMenu'
|
||||
| 'activeMenu'
|
||||
| 'multiTab'
|
||||
| 'fixedIndexInTab'
|
||||
| 'query'
|
||||
>;
|
||||
|
||||
type Menu = Common.CommonRecord<{
|
||||
/** parent menu id */
|
||||
parentId: number;
|
||||
@@ -184,42 +198,16 @@ declare namespace Api {
|
||||
routePath: string;
|
||||
/** component */
|
||||
component?: string;
|
||||
/**
|
||||
* i18n key
|
||||
*
|
||||
* it is for internationalization
|
||||
*/
|
||||
i18nKey?: App.I18n.I18nKey;
|
||||
/** iconify icon name or local icon name */
|
||||
icon: string;
|
||||
/** icon type */
|
||||
iconType: IconType;
|
||||
/** menu order */
|
||||
order: number;
|
||||
/** whether to cache the route */
|
||||
keepAlive?: boolean;
|
||||
/** outer link */
|
||||
href?: string;
|
||||
/** whether to hide the route in the menu */
|
||||
hideInMenu?: boolean;
|
||||
/**
|
||||
* The menu key will be activated when entering the route
|
||||
*
|
||||
* The route is not in the menu
|
||||
*
|
||||
* @example
|
||||
* the route is "user_detail", if it is set to "user_list", the menu "user_list" will be activated
|
||||
*/
|
||||
activeMenu?: import('@elegant-router/types').LastLevelRouteKey;
|
||||
/** By default, the same route path will use one tab, if set to true, it will use multiple tabs */
|
||||
multiTab?: boolean;
|
||||
/** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */
|
||||
fixedIndexInTab?: number;
|
||||
/** menu buttons */
|
||||
buttons?: MenuButton[];
|
||||
/** buttons */
|
||||
buttons?: MenuButton[] | null;
|
||||
/** children menu */
|
||||
children?: Menu[];
|
||||
}>;
|
||||
children?: Menu[] | null;
|
||||
}> &
|
||||
MenuPropsOfRoute;
|
||||
|
||||
/** menu list */
|
||||
type MenuList = Common.PaginatingQueryRecord<Menu>;
|
||||
|
34
src/typings/app.d.ts
vendored
34
src/typings/app.d.ts
vendored
@@ -2,7 +2,7 @@
|
||||
declare namespace App {
|
||||
/** Theme namespace */
|
||||
namespace Theme {
|
||||
type ColorPaletteNumber = import('@sa/color-palette').ColorPaletteNumber;
|
||||
type ColorPaletteNumber = import('@sa/color').ColorPaletteNumber;
|
||||
|
||||
/** Theme token */
|
||||
type ThemeToken = {
|
||||
@@ -18,6 +18,10 @@ declare namespace App {
|
||||
interface ThemeSetting {
|
||||
/** Theme scheme */
|
||||
themeScheme: UnionKey.ThemeScheme;
|
||||
/** grayscale mode */
|
||||
grayscale: boolean;
|
||||
/** Whether to recommend color */
|
||||
recommendColor: boolean;
|
||||
/** Theme color */
|
||||
themeColor: string;
|
||||
/** Other color */
|
||||
@@ -154,7 +158,7 @@ declare namespace App {
|
||||
/** The menu label */
|
||||
label: string;
|
||||
/** The menu i18n key */
|
||||
i18nKey?: I18n.I18nKey;
|
||||
i18nKey?: I18n.I18nKey | null;
|
||||
/** The route key */
|
||||
routeKey: RouteKey;
|
||||
/** The route path */
|
||||
@@ -212,7 +216,7 @@ declare namespace App {
|
||||
*/
|
||||
localIcon?: string;
|
||||
/** I18n key */
|
||||
i18nKey?: I18n.I18nKey;
|
||||
i18nKey?: I18n.I18nKey | null;
|
||||
};
|
||||
|
||||
/** Form rule */
|
||||
@@ -247,6 +251,10 @@ declare namespace App {
|
||||
type Schema = {
|
||||
system: {
|
||||
title: string;
|
||||
updateTitle: string;
|
||||
updateContent: string;
|
||||
updateConfirm: string;
|
||||
updateCancel: string;
|
||||
};
|
||||
common: {
|
||||
action: string;
|
||||
@@ -257,6 +265,7 @@ declare namespace App {
|
||||
cancel: string;
|
||||
close: string;
|
||||
check: string;
|
||||
expandColumn: string;
|
||||
columnSetting: string;
|
||||
config: string;
|
||||
confirm: string;
|
||||
@@ -298,7 +307,10 @@ declare namespace App {
|
||||
};
|
||||
theme: {
|
||||
themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>;
|
||||
grayscale: string;
|
||||
layoutMode: { title: string } & Record<UnionKey.ThemeLayoutMode, string>;
|
||||
recommendColor: string;
|
||||
recommendColorDesc: string;
|
||||
themeColor: {
|
||||
title: string;
|
||||
followPrimary: string;
|
||||
@@ -465,6 +477,12 @@ declare namespace App {
|
||||
adminVisible: string;
|
||||
adminOrUserVisible: string;
|
||||
};
|
||||
request: {
|
||||
repeatedErrorOccurOnce: string;
|
||||
repeatedError: string;
|
||||
repeatedErrorMsg1: string;
|
||||
repeatedErrorMsg2: string;
|
||||
};
|
||||
};
|
||||
manage: {
|
||||
common: {
|
||||
@@ -524,7 +542,7 @@ declare namespace App {
|
||||
menuName: string;
|
||||
routeName: string;
|
||||
routePath: string;
|
||||
routeParams: string;
|
||||
pathParam: string;
|
||||
layout: string;
|
||||
page: string;
|
||||
i18nKey: string;
|
||||
@@ -532,12 +550,14 @@ declare namespace App {
|
||||
localIcon: string;
|
||||
iconTypeTitle: string;
|
||||
order: string;
|
||||
constant: string;
|
||||
keepAlive: string;
|
||||
href: string;
|
||||
hideInMenu: string;
|
||||
activeMenu: string;
|
||||
multiTab: string;
|
||||
fixedIndexInTab: string;
|
||||
query: string;
|
||||
button: string;
|
||||
buttonCode: string;
|
||||
buttonDesc: string;
|
||||
@@ -548,6 +568,7 @@ declare namespace App {
|
||||
menuName: string;
|
||||
routeName: string;
|
||||
routePath: string;
|
||||
pathParam: string;
|
||||
layout: string;
|
||||
page: string;
|
||||
i18nKey: string;
|
||||
@@ -561,6 +582,8 @@ declare namespace App {
|
||||
multiTab: string;
|
||||
fixedInTab: string;
|
||||
fixedIndexInTab: string;
|
||||
queryKey: string;
|
||||
queryValue: string;
|
||||
button: string;
|
||||
buttonCode: string;
|
||||
buttonDesc: string;
|
||||
@@ -602,6 +625,9 @@ declare namespace App {
|
||||
pin: string;
|
||||
unpin: string;
|
||||
};
|
||||
datatable: {
|
||||
itemCount: string;
|
||||
};
|
||||
};
|
||||
|
||||
type GetI18nKey<T extends Record<string, unknown>, K extends keyof T = keyof T> = K extends string
|
||||
|
3
src/typings/components.d.ts
vendored
3
src/typings/components.d.ts
vendored
@@ -19,9 +19,11 @@ declare module 'vue' {
|
||||
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
|
||||
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
|
||||
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
|
||||
'IconIc:roundPlus': typeof import('~icons/ic/round-plus')['default']
|
||||
IconIcRoundDelete: typeof import('~icons/ic/round-delete')['default']
|
||||
IconIcRoundPlus: typeof import('~icons/ic/round-plus')['default']
|
||||
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
|
||||
IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default']
|
||||
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
|
||||
IconLocalBanner: typeof import('~icons/local/banner')['default']
|
||||
IconLocalLogo: typeof import('~icons/local/logo')['default']
|
||||
@@ -49,6 +51,7 @@ declare module 'vue' {
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
|
6
src/typings/env.d.ts
vendored
6
src/typings/env.d.ts
vendored
@@ -101,5 +101,11 @@ declare namespace Env {
|
||||
* @link https://docs.iconify.design/api/providers.html
|
||||
*/
|
||||
readonly VITE_ICONIFY_URL?: string;
|
||||
/** Used to differentiate storage across different domains */
|
||||
readonly VITE_STORAGE_PREFIX?: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: Env.ImportMeta;
|
||||
}
|
||||
|
46
src/typings/global.d.ts
vendored
46
src/typings/global.d.ts
vendored
@@ -1,27 +1,27 @@
|
||||
interface Window {
|
||||
/** NProgress instance */
|
||||
NProgress?: import('nprogress').NProgress;
|
||||
/** Loading bar instance */
|
||||
$loadingBar?: import('naive-ui').LoadingBarProviderInst;
|
||||
/** Dialog instance */
|
||||
$dialog?: import('naive-ui').DialogProviderInst;
|
||||
/** Message instance */
|
||||
$message?: import('naive-ui').MessageProviderInst;
|
||||
/** Notification instance */
|
||||
$notification?: import('naive-ui').NotificationProviderInst;
|
||||
}
|
||||
export {};
|
||||
|
||||
interface ViewTransition {
|
||||
ready: Promise<void>;
|
||||
}
|
||||
declare global {
|
||||
export interface Window {
|
||||
/** NProgress instance */
|
||||
NProgress?: import('nprogress').NProgress;
|
||||
/** Loading bar instance */
|
||||
$loadingBar?: import('naive-ui').LoadingBarProviderInst;
|
||||
/** Dialog instance */
|
||||
$dialog?: import('naive-ui').DialogProviderInst;
|
||||
/** Message instance */
|
||||
$message?: import('naive-ui').MessageProviderInst;
|
||||
/** Notification instance */
|
||||
$notification?: import('naive-ui').NotificationProviderInst;
|
||||
}
|
||||
|
||||
interface Document {
|
||||
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition;
|
||||
}
|
||||
interface ViewTransition {
|
||||
ready: Promise<void>;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: Env.ImportMeta;
|
||||
}
|
||||
export interface Document {
|
||||
startViewTransition?: (callback: () => Promise<void> | void) => ViewTransition;
|
||||
}
|
||||
|
||||
/** Build time of the project */
|
||||
declare const BUILD_TIME: string;
|
||||
/** Build time of the project */
|
||||
export const BUILD_TIME: string;
|
||||
}
|
||||
|
9
src/typings/naive-ui.d.ts
vendored
9
src/typings/naive-ui.d.ts
vendored
@@ -43,5 +43,12 @@ declare namespace NaiveUI {
|
||||
type NaiveTableConfig<A extends TableApiFn> = Pick<
|
||||
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
|
||||
'apiFn' | 'apiParams' | 'columns' | 'immediate'
|
||||
>;
|
||||
> & {
|
||||
/**
|
||||
* whether to display the total items count
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
showTotal?: boolean;
|
||||
};
|
||||
}
|
||||
|
27
src/typings/router.d.ts
vendored
27
src/typings/router.d.ts
vendored
@@ -13,21 +13,23 @@ declare module 'vue-router' {
|
||||
*
|
||||
* It's used in i18n, if it is set, the title will be ignored
|
||||
*/
|
||||
i18nKey?: App.I18n.I18nKey;
|
||||
i18nKey?: App.I18n.I18nKey | null;
|
||||
/**
|
||||
* Roles of the route
|
||||
*
|
||||
* Route can be accessed if the current user has at least one of the roles
|
||||
*
|
||||
* It only works when the route mode is "static", if the route mode is "dynamic", it will be ignored
|
||||
*/
|
||||
roles?: string[];
|
||||
/** Whether to cache the route */
|
||||
keepAlive?: boolean;
|
||||
keepAlive?: boolean | null;
|
||||
/**
|
||||
* Is constant route
|
||||
*
|
||||
* Does not need to login, and the route is defined in the front-end
|
||||
* when it is set to true, there will be no login verification and no permission verification to access the route
|
||||
*/
|
||||
constant?: boolean;
|
||||
constant?: boolean | null;
|
||||
/**
|
||||
* Iconify icon
|
||||
*
|
||||
@@ -41,11 +43,11 @@ declare module 'vue-router' {
|
||||
*/
|
||||
localIcon?: string;
|
||||
/** Router order */
|
||||
order?: number;
|
||||
order?: number | null;
|
||||
/** The outer link of the route */
|
||||
href?: string;
|
||||
href?: string | null;
|
||||
/** Whether to hide the route in the menu */
|
||||
hideInMenu?: boolean;
|
||||
hideInMenu?: boolean | null;
|
||||
/**
|
||||
* The menu key will be activated when entering the route
|
||||
*
|
||||
@@ -54,12 +56,15 @@ declare module 'vue-router' {
|
||||
* @example
|
||||
* the route is "user_detail", if it is set to "user_list", the menu "user_list" will be activated
|
||||
*/
|
||||
activeMenu?: import('@elegant-router/types').RouteKey;
|
||||
/** By default, the same route path will use one tab, if set to true, it will use multiple tabs */
|
||||
multiTab?: boolean;
|
||||
activeMenu?: import('@elegant-router/types').RouteKey | null;
|
||||
/**
|
||||
* By default, the same route path will use one tab, even with different query, if set true, the route with
|
||||
* different query will use different tabs
|
||||
*/
|
||||
multiTab?: boolean | null;
|
||||
/** If set, the route will be fixed in tabs, and the value is the order of fixed tabs */
|
||||
fixedIndexInTab?: number | null;
|
||||
/** if set query parameters, it will be automatically carried when entering the route */
|
||||
query?: Record<string, string>;
|
||||
query?: { key: string; value: string }[] | null;
|
||||
}
|
||||
}
|
||||
|
2
src/typings/storage.d.ts
vendored
2
src/typings/storage.d.ts
vendored
@@ -18,8 +18,6 @@ declare namespace StorageType {
|
||||
mixSiderFixed: CommonType.YesOrNo;
|
||||
/** The refresh token */
|
||||
refreshToken: string;
|
||||
/** The user info */
|
||||
userInfo: Api.Auth.UserInfo;
|
||||
/** The theme color */
|
||||
themeColor: string;
|
||||
/** The theme settings */
|
||||
|
@@ -36,3 +36,23 @@ export function translateOptions(options: CommonType.Option<string>[]) {
|
||||
label: $t(option.label as App.I18n.I18nKey)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle html class
|
||||
*
|
||||
* @param className
|
||||
*/
|
||||
export function toggleHtmlClass(className: string) {
|
||||
function add() {
|
||||
document.documentElement.classList.add(className);
|
||||
}
|
||||
|
||||
function remove() {
|
||||
document.documentElement.classList.remove(className);
|
||||
}
|
||||
|
||||
return {
|
||||
add,
|
||||
remove
|
||||
};
|
||||
}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import { createLocalforage, createStorage } from '@sa/utils';
|
||||
|
||||
export const localStg = createStorage<StorageType.Local>('local');
|
||||
const storagePrefix = import.meta.env.VITE_STORAGE_PREFIX || '';
|
||||
|
||||
export const sessionStg = createStorage<StorageType.Session>('session');
|
||||
export const localStg = createStorage<StorageType.Local>('local', storagePrefix);
|
||||
|
||||
export const sessionStg = createStorage<StorageType.Session>('session', storagePrefix);
|
||||
|
||||
export const localforage = createLocalforage<StorageType.Local>('local');
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { getColorPalette, mixColor } from '@sa/utils';
|
||||
import { getPaletteColorByNumber, mixColor } from '@sa/color';
|
||||
import { $t } from '@/locales';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
@@ -38,7 +38,7 @@ const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
||||
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
||||
|
||||
const bgThemeColor = computed(() =>
|
||||
themeStore.darkMode ? getColorPalette(themeStore.themeColor, 7) : themeStore.themeColor
|
||||
themeStore.darkMode ? getPaletteColorByNumber(themeStore.themeColor, 600) : themeStore.themeColor
|
||||
);
|
||||
|
||||
const bgColor = computed(() => {
|
||||
|
@@ -6,7 +6,7 @@ import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { useCaptcha } from '@/hooks/business/captcha';
|
||||
|
||||
defineOptions({
|
||||
name: 'CodeLogin'
|
||||
name: 'Register'
|
||||
});
|
||||
|
||||
const { toggleLoginModule } = useRouterPush();
|
||||
|
@@ -13,6 +13,25 @@ async function logoutWithModal() {
|
||||
async function refreshToken() {
|
||||
await fetchCustomBackendError('9999', $t('request.tokenExpired'));
|
||||
}
|
||||
|
||||
async function handleRepeatedMessageError() {
|
||||
await Promise.all([
|
||||
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
|
||||
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
|
||||
fetchCustomBackendError('2222', $t('page.function.request.repeatedErrorMsg1')),
|
||||
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
|
||||
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2')),
|
||||
fetchCustomBackendError('3333', $t('page.function.request.repeatedErrorMsg2'))
|
||||
]);
|
||||
}
|
||||
|
||||
async function handleRepeatedModalError() {
|
||||
await Promise.all([
|
||||
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
|
||||
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg')),
|
||||
fetchCustomBackendError('7777', $t('request.logoutWithModalMsg'))
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -26,6 +45,18 @@ async function refreshToken() {
|
||||
<NCard :title="$t('request.refreshToken')" :bordered="false" size="small" segmented class="card-wrapper">
|
||||
<NButton @click="refreshToken">{{ $t('common.trigger') }}</NButton>
|
||||
</NCard>
|
||||
<NCard
|
||||
:title="$t('page.function.request.repeatedErrorOccurOnce')"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
segmented
|
||||
class="card-wrapper"
|
||||
>
|
||||
<NButton @click="handleRepeatedMessageError">{{ $t('page.function.request.repeatedError') }}(Message)</NButton>
|
||||
<NButton class="ml-12px" @click="handleRepeatedModalError">
|
||||
{{ $t('page.function.request.repeatedError') }}(Modal)
|
||||
</NButton>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
|
@@ -10,11 +10,11 @@ import { $t } from '@/locales';
|
||||
import { yesOrNoRecord } from '@/constants/common';
|
||||
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import MenuOperateDrawer, { type OperateType } from './modules/menu-operate-drawer.vue';
|
||||
import MenuOperateModal, { type OperateType } from './modules/menu-operate-modal.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { bool: drawerVisible, setTrue: openDrawer, setFalse: _closeDrawer } = useBoolean();
|
||||
const { bool: visible, setTrue: openModal } = useBoolean();
|
||||
|
||||
const wrapperRef = ref<HTMLElement | null>(null);
|
||||
|
||||
@@ -176,7 +176,7 @@ const operateType = ref<OperateType>('add');
|
||||
|
||||
function handleAdd() {
|
||||
operateType.value = 'add';
|
||||
openDrawer();
|
||||
openModal();
|
||||
}
|
||||
|
||||
async function handleBatchDelete() {
|
||||
@@ -200,7 +200,7 @@ function handleEdit(item: Api.SystemManage.Menu) {
|
||||
operateType.value = 'edit';
|
||||
editingData.value = { ...item };
|
||||
|
||||
openDrawer();
|
||||
openModal();
|
||||
}
|
||||
|
||||
function handleAddChildMenu(item: Api.SystemManage.Menu) {
|
||||
@@ -208,7 +208,7 @@ function handleAddChildMenu(item: Api.SystemManage.Menu) {
|
||||
|
||||
editingData.value = { ...item };
|
||||
|
||||
openDrawer();
|
||||
openModal();
|
||||
}
|
||||
|
||||
const allPages = ref<string[]>([]);
|
||||
@@ -252,8 +252,8 @@ init();
|
||||
:pagination="pagination"
|
||||
class="sm:h-full"
|
||||
/>
|
||||
<MenuOperateDrawer
|
||||
v-model:visible="drawerVisible"
|
||||
<MenuOperateModal
|
||||
v-model:visible="visible"
|
||||
:operate-type="operateType"
|
||||
:row-data="editingData"
|
||||
:all-pages="allPages"
|
||||
|
@@ -1,256 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import type { SelectOption } from 'naive-ui';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import { getLocalIcons } from '@/utils/icon';
|
||||
import { getLayoutAndPage, transformLayoutAndPageToComponent } from './shared';
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuOperateDrawer'
|
||||
});
|
||||
|
||||
export type OperateType = NaiveUI.TableOperateType | 'addChild';
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: OperateType;
|
||||
/** the edit menu data or the parent menu data when adding a child menu */
|
||||
rowData?: Api.SystemManage.Menu | null;
|
||||
/** all pages */
|
||||
allPages: string[];
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { defaultRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<OperateType, string> = {
|
||||
add: $t('page.manage.menu.addMenu'),
|
||||
addChild: $t('page.manage.menu.addChildMenu'),
|
||||
edit: $t('page.manage.menu.editMenu')
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Pick<
|
||||
Api.SystemManage.Menu,
|
||||
| 'menuType'
|
||||
| 'menuName'
|
||||
| 'icon'
|
||||
| 'iconType'
|
||||
| 'routeName'
|
||||
| 'routePath'
|
||||
| 'component'
|
||||
| 'status'
|
||||
| 'hideInMenu'
|
||||
| 'order'
|
||||
| 'parentId'
|
||||
> & {
|
||||
layout: string;
|
||||
page: string;
|
||||
};
|
||||
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
menuType: '1',
|
||||
menuName: '',
|
||||
icon: '',
|
||||
iconType: '1',
|
||||
routeName: '',
|
||||
routePath: '',
|
||||
layout: '',
|
||||
page: '',
|
||||
status: null,
|
||||
hideInMenu: false,
|
||||
order: 0,
|
||||
parentId: 0
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'menuName' | 'status' | 'routeName' | 'routePath'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
menuName: defaultRequiredRule,
|
||||
status: defaultRequiredRule,
|
||||
routeName: defaultRequiredRule,
|
||||
routePath: defaultRequiredRule
|
||||
};
|
||||
|
||||
const disabledMenuType = computed(() => props.operateType === 'edit');
|
||||
|
||||
const localIcons = getLocalIcons();
|
||||
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
||||
label: () => (
|
||||
<div class="flex-y-center gap-16px">
|
||||
<SvgIcon localIcon={item} class="text-icon" />
|
||||
<span>{item}</span>
|
||||
</div>
|
||||
),
|
||||
value: item
|
||||
}));
|
||||
|
||||
const showLayout = computed(() => model.parentId === 0);
|
||||
|
||||
const showPage = computed(() => model.menuType === '2');
|
||||
|
||||
const pageOptions = computed(() => {
|
||||
const allPages = [...props.allPages];
|
||||
|
||||
if (model.routeName && !allPages.includes(model.routeName)) {
|
||||
allPages.unshift(model.routeName);
|
||||
}
|
||||
|
||||
const opts: CommonType.Option[] = allPages.map(page => ({
|
||||
label: page,
|
||||
value: page
|
||||
}));
|
||||
|
||||
return opts;
|
||||
});
|
||||
|
||||
const layoutOptions: CommonType.Option[] = [
|
||||
{
|
||||
label: 'base',
|
||||
value: 'base'
|
||||
},
|
||||
{
|
||||
label: 'blank',
|
||||
value: 'blank'
|
||||
}
|
||||
];
|
||||
|
||||
function handleInitModel() {
|
||||
Object.assign(model, createDefaultModel());
|
||||
|
||||
if (!props.rowData) return;
|
||||
|
||||
if (props.operateType === 'addChild') {
|
||||
const { id } = props.rowData;
|
||||
|
||||
Object.assign(model, { parentId: id });
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { component, ...rest } = props.rowData;
|
||||
|
||||
const { layout, page } = getLayoutAndPage(component);
|
||||
|
||||
Object.assign(model, rest, { layout, page });
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
model.component = transformLayoutAndPageToComponent(model.layout, model.page);
|
||||
|
||||
// request
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleInitModel();
|
||||
restoreValidation();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NDrawer v-model:show="visible" display-directive="show" :width="360">
|
||||
<NDrawerContent :title="title" :native-scrollbar="false" closable>
|
||||
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="80">
|
||||
<NFormItem :label="$t('page.manage.menu.menuType')" path="menuType">
|
||||
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
|
||||
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.menuName')" path="menuName">
|
||||
<NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" />
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.iconTypeTitle')" path="iconType">
|
||||
<NRadioGroup v-model:value="model.iconType">
|
||||
<NRadio v-for="item in menuIconTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.icon')" path="icon">
|
||||
<template v-if="model.iconType === '1'">
|
||||
<NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1">
|
||||
<template #suffix>
|
||||
<SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
|
||||
</template>
|
||||
</NInput>
|
||||
</template>
|
||||
<template v-if="model.iconType === '2'">
|
||||
<NSelect
|
||||
v-model:value="model.icon"
|
||||
:placeholder="$t('page.manage.menu.form.localIcon')"
|
||||
:options="localIconOptions"
|
||||
/>
|
||||
</template>
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.routeName')" path="routeName">
|
||||
<NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" />
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.routePath')" path="routePath">
|
||||
<NInput v-model:value="model.routePath" :placeholder="$t('page.manage.menu.form.routePath')" />
|
||||
</NFormItem>
|
||||
<NFormItem v-if="showLayout" :label="$t('page.manage.menu.layout')" path="layout">
|
||||
<NSelect
|
||||
v-model:value="model.layout"
|
||||
:options="layoutOptions"
|
||||
:placeholder="$t('page.manage.menu.form.layout')"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="showPage" :label="$t('page.manage.menu.page')" path="page">
|
||||
<NSelect v-model:value="model.page" :options="pageOptions" :placeholder="$t('page.manage.menu.form.page')" />
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.menuStatus')" path="status">
|
||||
<NRadioGroup v-model:value="model.status">
|
||||
<NRadio v-for="item in enableStatusOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu">
|
||||
<NRadioGroup v-model:value="model.hideInMenu">
|
||||
<NRadio value :label="$t('common.yesOrNo.yes')" />
|
||||
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
||||
</NRadioGroup>
|
||||
</NFormItem>
|
||||
<NFormItem :label="$t('page.manage.menu.order')" path="order">
|
||||
<NInputNumber v-model:value="model.order" :placeholder="$t('page.manage.menu.form.order')" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
467
src/views/manage/menu/modules/menu-operate-modal.vue
Normal file
467
src/views/manage/menu/modules/menu-operate-modal.vue
Normal file
@@ -0,0 +1,467 @@
|
||||
<script setup lang="tsx">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import type { SelectOption } from 'naive-ui';
|
||||
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
|
||||
import { $t } from '@/locales';
|
||||
import { enableStatusOptions, menuIconTypeOptions, menuTypeOptions } from '@/constants/business';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import { getLocalIcons } from '@/utils/icon';
|
||||
import { fetchGetAllRoles } from '@/service/api';
|
||||
import {
|
||||
getLayoutAndPage,
|
||||
getPathParamFromRoutePath,
|
||||
getRoutePathByRouteName,
|
||||
getRoutePathWithParam,
|
||||
transformLayoutAndPageToComponent
|
||||
} from './shared';
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuOperateModal'
|
||||
});
|
||||
|
||||
export type OperateType = NaiveUI.TableOperateType | 'addChild';
|
||||
|
||||
interface Props {
|
||||
/** the type of operation */
|
||||
operateType: OperateType;
|
||||
/** the edit menu data or the parent menu data when adding a child menu */
|
||||
rowData?: Api.SystemManage.Menu | null;
|
||||
/** all pages */
|
||||
allPages: string[];
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
interface Emits {
|
||||
(e: 'submitted'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = defineModel<boolean>('visible', {
|
||||
default: false
|
||||
});
|
||||
|
||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||
const { defaultRequiredRule } = useFormRules();
|
||||
|
||||
const title = computed(() => {
|
||||
const titles: Record<OperateType, string> = {
|
||||
add: $t('page.manage.menu.addMenu'),
|
||||
addChild: $t('page.manage.menu.addChildMenu'),
|
||||
edit: $t('page.manage.menu.editMenu')
|
||||
};
|
||||
return titles[props.operateType];
|
||||
});
|
||||
|
||||
type Model = Pick<
|
||||
Api.SystemManage.Menu,
|
||||
| 'menuType'
|
||||
| 'menuName'
|
||||
| 'routeName'
|
||||
| 'routePath'
|
||||
| 'component'
|
||||
| 'order'
|
||||
| 'i18nKey'
|
||||
| 'icon'
|
||||
| 'iconType'
|
||||
| 'status'
|
||||
| 'parentId'
|
||||
| 'keepAlive'
|
||||
| 'constant'
|
||||
| 'href'
|
||||
| 'hideInMenu'
|
||||
| 'activeMenu'
|
||||
| 'multiTab'
|
||||
| 'fixedIndexInTab'
|
||||
> & {
|
||||
query: NonNullable<Api.SystemManage.Menu['query']>;
|
||||
buttons: NonNullable<Api.SystemManage.Menu['buttons']>;
|
||||
layout: string;
|
||||
page: string;
|
||||
pathParam: string;
|
||||
};
|
||||
|
||||
const model: Model = reactive(createDefaultModel());
|
||||
|
||||
function createDefaultModel(): Model {
|
||||
return {
|
||||
menuType: '1',
|
||||
menuName: '',
|
||||
routeName: '',
|
||||
routePath: '',
|
||||
pathParam: '',
|
||||
component: '',
|
||||
layout: '',
|
||||
page: '',
|
||||
i18nKey: null,
|
||||
icon: '',
|
||||
iconType: '1',
|
||||
parentId: 0,
|
||||
status: '1',
|
||||
keepAlive: false,
|
||||
constant: false,
|
||||
order: 0,
|
||||
href: null,
|
||||
hideInMenu: false,
|
||||
activeMenu: null,
|
||||
multiTab: false,
|
||||
fixedIndexInTab: null,
|
||||
query: [],
|
||||
buttons: []
|
||||
};
|
||||
}
|
||||
|
||||
type RuleKey = Extract<keyof Model, 'menuName' | 'status' | 'routeName' | 'routePath'>;
|
||||
|
||||
const rules: Record<RuleKey, App.Global.FormRule> = {
|
||||
menuName: defaultRequiredRule,
|
||||
status: defaultRequiredRule,
|
||||
routeName: defaultRequiredRule,
|
||||
routePath: defaultRequiredRule
|
||||
};
|
||||
|
||||
const disabledMenuType = computed(() => props.operateType === 'edit');
|
||||
|
||||
const localIcons = getLocalIcons();
|
||||
const localIconOptions = localIcons.map<SelectOption>(item => ({
|
||||
label: () => (
|
||||
<div class="flex-y-center gap-16px">
|
||||
<SvgIcon localIcon={item} class="text-icon" />
|
||||
<span>{item}</span>
|
||||
</div>
|
||||
),
|
||||
value: item
|
||||
}));
|
||||
|
||||
const showLayout = computed(() => model.parentId === 0);
|
||||
|
||||
const showPage = computed(() => model.menuType === '2');
|
||||
|
||||
const pageOptions = computed(() => {
|
||||
const allPages = [...props.allPages];
|
||||
|
||||
if (model.routeName && !allPages.includes(model.routeName)) {
|
||||
allPages.unshift(model.routeName);
|
||||
}
|
||||
|
||||
const opts: CommonType.Option[] = allPages.map(page => ({
|
||||
label: page,
|
||||
value: page
|
||||
}));
|
||||
|
||||
return opts;
|
||||
});
|
||||
|
||||
const layoutOptions: CommonType.Option[] = [
|
||||
{
|
||||
label: 'base',
|
||||
value: 'base'
|
||||
},
|
||||
{
|
||||
label: 'blank',
|
||||
value: 'blank'
|
||||
}
|
||||
];
|
||||
|
||||
/** the enabled role options */
|
||||
const roleOptions = ref<CommonType.Option<string>[]>([]);
|
||||
|
||||
async function getRoleOptions() {
|
||||
const { error, data } = await fetchGetAllRoles();
|
||||
|
||||
if (!error) {
|
||||
const options = data.map(item => ({
|
||||
label: item.roleName,
|
||||
value: item.roleCode
|
||||
}));
|
||||
|
||||
roleOptions.value = [...options];
|
||||
}
|
||||
}
|
||||
|
||||
function handleInitModel() {
|
||||
Object.assign(model, createDefaultModel());
|
||||
|
||||
if (!props.rowData) return;
|
||||
|
||||
if (props.operateType === 'addChild') {
|
||||
const { id } = props.rowData;
|
||||
|
||||
Object.assign(model, { parentId: id });
|
||||
}
|
||||
|
||||
if (props.operateType === 'edit') {
|
||||
const { component, ...rest } = props.rowData;
|
||||
|
||||
const { layout, page } = getLayoutAndPage(component);
|
||||
const { path, param } = getPathParamFromRoutePath(rest.routePath);
|
||||
|
||||
Object.assign(model, rest, { layout, page, routePath: path, pathParam: param });
|
||||
}
|
||||
|
||||
if (!model.query) {
|
||||
model.query = [];
|
||||
}
|
||||
if (!model.buttons) {
|
||||
model.buttons = [];
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleUpdateRoutePathByRouteName() {
|
||||
if (model.routeName) {
|
||||
model.routePath = getRoutePathByRouteName(model.routeName);
|
||||
} else {
|
||||
model.routePath = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdateI18nKeyByRouteName() {
|
||||
if (model.routeName) {
|
||||
model.i18nKey = `route.${model.routeName}` as App.I18n.I18nKey;
|
||||
} else {
|
||||
model.i18nKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCreateButton() {
|
||||
const buttonItem: Api.SystemManage.MenuButton = {
|
||||
code: '',
|
||||
desc: ''
|
||||
};
|
||||
|
||||
return buttonItem;
|
||||
}
|
||||
|
||||
function getSubmitParams() {
|
||||
const { layout, page, pathParam, ...params } = model;
|
||||
|
||||
const component = transformLayoutAndPageToComponent(layout, page);
|
||||
const routePath = getRoutePathWithParam(model.routePath, pathParam);
|
||||
|
||||
params.component = component;
|
||||
params.routePath = routePath;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
await validate();
|
||||
|
||||
const params = getSubmitParams();
|
||||
|
||||
console.log('params: ', params);
|
||||
|
||||
// request
|
||||
window.$message?.success($t('common.updateSuccess'));
|
||||
closeDrawer();
|
||||
emit('submitted');
|
||||
}
|
||||
|
||||
watch(visible, () => {
|
||||
if (visible.value) {
|
||||
handleInitModel();
|
||||
restoreValidation();
|
||||
getRoleOptions();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => model.routeName,
|
||||
() => {
|
||||
handleUpdateRoutePathByRouteName();
|
||||
handleUpdateI18nKeyByRouteName();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal v-model:show="visible" :title="title" preset="card" class="w-800px">
|
||||
<NScrollbar class="h-480px pr-20px">
|
||||
<NForm ref="formRef" :model="model" :rules="rules" label-placement="left" :label-width="100">
|
||||
<NGrid responsive="screen" item-responsive>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuType')" path="menuType">
|
||||
<NRadioGroup v-model:value="model.menuType" :disabled="disabledMenuType">
|
||||
<NRadio v-for="item in menuTypeOptions" :key="item.value" :value="item.value" :label="$t(item.label)" />
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuName')" path="menuName">
|
||||
<NInput v-model:value="model.menuName" :placeholder="$t('page.manage.menu.form.menuName')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routeName')" path="routeName">
|
||||
<NInput v-model:value="model.routeName" :placeholder="$t('page.manage.menu.form.routeName')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.routePath')" path="routePath">
|
||||
<NInput v-model:value="model.routePath" disabled :placeholder="$t('page.manage.menu.form.routePath')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.pathParam')" path="pathParam">
|
||||
<NInput v-model:value="model.pathParam" :placeholder="$t('page.manage.menu.form.pathParam')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi v-if="showLayout" span="24 m:12" :label="$t('page.manage.menu.layout')" path="layout">
|
||||
<NSelect
|
||||
v-model:value="model.layout"
|
||||
:options="layoutOptions"
|
||||
:placeholder="$t('page.manage.menu.form.layout')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi v-if="showPage" span="24 m:12" :label="$t('page.manage.menu.page')" path="page">
|
||||
<NSelect
|
||||
v-model:value="model.page"
|
||||
:options="pageOptions"
|
||||
:placeholder="$t('page.manage.menu.form.page')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.i18nKey')" path="i18nKey">
|
||||
<NInput v-model:value="model.i18nKey" :placeholder="$t('page.manage.menu.form.i18nKey')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.order')" path="order">
|
||||
<NInputNumber v-model:value="model.order" class="w-full" :placeholder="$t('page.manage.menu.form.order')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.iconTypeTitle')" path="iconType">
|
||||
<NRadioGroup v-model:value="model.iconType">
|
||||
<NRadio
|
||||
v-for="item in menuIconTypeOptions"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="$t(item.label)"
|
||||
/>
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.icon')" path="icon">
|
||||
<template v-if="model.iconType === '1'">
|
||||
<NInput v-model:value="model.icon" :placeholder="$t('page.manage.menu.form.icon')" class="flex-1">
|
||||
<template #suffix>
|
||||
<SvgIcon v-if="model.icon" :icon="model.icon" class="text-icon" />
|
||||
</template>
|
||||
</NInput>
|
||||
</template>
|
||||
<template v-if="model.iconType === '2'">
|
||||
<NSelect
|
||||
v-model:value="model.icon"
|
||||
:placeholder="$t('page.manage.menu.form.localIcon')"
|
||||
:options="localIconOptions"
|
||||
/>
|
||||
</template>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.menuStatus')" path="status">
|
||||
<NRadioGroup v-model:value="model.status">
|
||||
<NRadio
|
||||
v-for="item in enableStatusOptions"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="$t(item.label)"
|
||||
/>
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.keepAlive')" path="keepAlive">
|
||||
<NRadioGroup v-model:value="model.keepAlive">
|
||||
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
|
||||
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.constant')" path="constant">
|
||||
<NRadioGroup v-model:value="model.constant">
|
||||
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
|
||||
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.href')" path="href">
|
||||
<NInput v-model:value="model.href" :placeholder="$t('page.manage.menu.form.href')" />
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.hideInMenu')" path="hideInMenu">
|
||||
<NRadioGroup v-model:value="model.hideInMenu">
|
||||
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
|
||||
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi
|
||||
v-if="model.hideInMenu"
|
||||
span="24 m:12"
|
||||
:label="$t('page.manage.menu.activeMenu')"
|
||||
path="activeMenu"
|
||||
>
|
||||
<NSelect
|
||||
v-model:value="model.activeMenu"
|
||||
:options="pageOptions"
|
||||
clearable
|
||||
:placeholder="$t('page.manage.menu.form.activeMenu')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.multiTab')" path="multiTab">
|
||||
<NRadioGroup v-model:value="model.multiTab">
|
||||
<NRadio :value="true" :label="$t('common.yesOrNo.yes')" />
|
||||
<NRadio :value="false" :label="$t('common.yesOrNo.no')" />
|
||||
</NRadioGroup>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24 m:12" :label="$t('page.manage.menu.fixedIndexInTab')" path="fixedIndexInTab">
|
||||
<NInputNumber
|
||||
v-model:value="model.fixedIndexInTab"
|
||||
class="w-full"
|
||||
clearable
|
||||
:placeholder="$t('page.manage.menu.form.fixedIndexInTab')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24" :label="$t('page.manage.menu.query')">
|
||||
<NDynamicInput
|
||||
v-model:value="model.query"
|
||||
preset="pair"
|
||||
:key-placeholder="$t('page.manage.menu.form.queryKey')"
|
||||
:value-placeholder="$t('page.manage.menu.form.queryValue')"
|
||||
>
|
||||
<template #action="{ index, create, remove }">
|
||||
<NSpace class="ml-12px">
|
||||
<NButton size="medium" @click="() => create(index)">
|
||||
<icon-ic:round-plus class="text-icon" />
|
||||
</NButton>
|
||||
<NButton size="medium" @click="() => remove(index)">
|
||||
<icon-ic-round-remove class="text-icon" />
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi span="24" :label="$t('page.manage.menu.button')">
|
||||
<NDynamicInput v-model:value="model.buttons" :on-create="handleCreateButton">
|
||||
<template #default="{ value }">
|
||||
<div class="ml-8px flex-y-center flex-1 gap-12px">
|
||||
<NInput
|
||||
v-model:value="value.code"
|
||||
:placeholder="$t('page.manage.menu.form.buttonCode')"
|
||||
class="flex-1"
|
||||
/>
|
||||
<NInput
|
||||
v-model:value="value.desc"
|
||||
:placeholder="$t('page.manage.menu.form.buttonDesc')"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #action="{ index, create, remove }">
|
||||
<NSpace class="ml-12px">
|
||||
<NButton size="medium" @click="() => create(index)">
|
||||
<icon-ic:round-plus class="text-icon" />
|
||||
</NButton>
|
||||
<NButton size="medium" @click="() => remove(index)">
|
||||
<icon-ic-round-remove class="text-icon" />
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NDynamicInput>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
</NScrollbar>
|
||||
<template #footer>
|
||||
<NSpace justify="end" :size="16">
|
||||
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
|
||||
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@@ -6,7 +6,7 @@ export function getLayoutAndPage(component?: string | null) {
|
||||
let layout = '';
|
||||
let page = '';
|
||||
|
||||
const [layoutOrPage, pageItem] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || [];
|
||||
const [layoutOrPage = '', pageItem = ''] = component?.split(FIRST_LEVEL_ROUTE_COMPONENT_SPLIT) || [];
|
||||
|
||||
layout = getLayout(layoutOrPage);
|
||||
page = getPage(pageItem || layoutOrPage);
|
||||
@@ -40,3 +40,40 @@ export function transformLayoutAndPageToComponent(layout: string, page: string)
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route name by route path
|
||||
*
|
||||
* @param routeName
|
||||
*/
|
||||
export function getRoutePathByRouteName(routeName: string) {
|
||||
return `/${routeName.replace(/_/g, '/')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path param from route path
|
||||
*
|
||||
* @param routePath route path
|
||||
*/
|
||||
export function getPathParamFromRoutePath(routePath: string) {
|
||||
const [path, param = ''] = routePath.split('/:');
|
||||
|
||||
return {
|
||||
path,
|
||||
param
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route path with param
|
||||
*
|
||||
* @param routePath route path
|
||||
* @param param path param
|
||||
*/
|
||||
export function getRoutePathWithParam(routePath: string, param: string) {
|
||||
if (param.trim()) {
|
||||
return `${routePath}/:${param}`;
|
||||
}
|
||||
|
||||
return routePath;
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ const appStore = useAppStore();
|
||||
|
||||
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
|
||||
apiFn: fetchGetUserList,
|
||||
showTotal: true,
|
||||
apiParams: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user