Compare commits

..

48 Commits

Author SHA1 Message Date
Soybean
fd087f59fa fix(packages): axios: fix json response. fixed #815 2025-09-17 17:03:30 +08:00
Soybean
9fa951aa06 chore(deps): update deps 2025-09-17 14:23:06 +08:00
Soybean
12b25e0d58 fix(types): fix proxy types 2025-08-28 00:26:29 +08:00
Soybean
e33f944a74 chore(deps): update deps 2025-08-28 00:26:12 +08:00
Azir-11
8d7f91dccf chore(other): update the ESLint validation configuration to support more file types. 2025-08-13 10:10:10 +08:00
Soybean
33ade53904 chore(readme): remove DartNode sponsorship badge from README files 2025-08-11 10:46:15 +08:00
Soybean
923eb98a5c fix(readme): update GitHub stars and forks links for gitee 2025-07-20 01:12:19 +08:00
Soybean
e89b86ce56 chore(deps): update deps 2025-07-14 00:44:44 +08:00
Azir
03dd64c543 chore(packages): update Vite version to 7 in package.json and documentation.
(cherry picked from commit ef806edd9d0c48ad8669863516d52e2eb8870d6f)
2025-07-03 22:08:50 +08:00
Soybean
aeb6369005 chore(deps): update deps 2025-07-03 22:06:30 +08:00
Soybean
133196f337 chore(vscode): remove unused vue.server.hybridMode setting from .vscode/settings.json 2025-07-03 22:02:30 +08:00
xiaobao
41191d54fb
fix(projects): Fix i18n-ally not working when setting moduleResolution to bundler. fixed #780 2025-07-03 21:59:18 +08:00
Soybean
5cb1cebd88 chore(deps): update deps 2025-06-27 18:17:05 +08:00
Junior25306
a5c4b4e3b7 chore(deps): update NodeJS and pnpm version requirements in package.json and documentation 2025-06-25 18:12:25 +08:00
Soybean
87a675bf62 chore(projects): release v1.3.15 2025-06-24 21:40:19 +08:00
Soybean
4d42dcbea8 docs(readme): add warning about upcoming V2 version and link to plan list 2025-06-24 21:39:21 +08:00
Soybean
276d836c87 refactor(iframe-page): remove unused lifecycle hooks and clean up script setup 2025-06-24 21:31:57 +08:00
Soybean
7d84062e2c fix(app): replace console.error with window.console.error for consistency 2025-06-24 21:31:17 +08:00
wenyuan
afd604212b fix(projects): ensure proper text color when themes are inverted 2025-06-24 21:20:24 +08:00
wenyuan
fcb89883fa optimize(components): optimize spacing for lang-switch dropdown options 2025-06-24 21:20:20 +08:00
Soybean
dc674ce870 chore(deps): update deps 2025-06-24 21:11:08 +08:00
Soybean
dbd995c12c chore(projects): update deps & fix moduleResolution 2025-06-24 19:29:12 +08:00
Azir
7b2e510a2f docs(other): update docs with video tutorial link. 2025-06-24 10:17:50 +08:00
chenziwen
da149e5bbd fix(types): The environment variable VITE_ICON_LOCAL_PREFIX has the wrong type. 2025-06-14 22:01:01 +08:00
wenyuan
7c3dac4212 feat(projects): add configurable user name watermark option 2025-06-10 19:05:08 +08:00
Soybean
39b89a1234 chore(projects): release v1.3.14 2025-06-09 22:40:03 +08:00
Soybean
c57f88aad2 fix(auth): remove redundant authStore declaration in resetStore function 2025-06-09 22:34:57 +08:00
Soybean
3e4e17abd8 chore(deps): update deps 2025-06-09 22:10:30 +08:00
wenyuan
e6044d0fc7 optimize(projects): optimize tab deletion logic. closed #755 2025-06-09 14:19:44 +08:00
Soybean
2ed0b6484c feat(docs): add DartNode sponsorship badge to README files 2025-06-06 22:50:17 +08:00
恕瑞玛的皇帝
222187d3b0 optimize(hooks): update detection function to cover the exceptions that occur when the request fails. 2025-05-31 13:41:02 +08:00
t8y2
75455b006c feat(theme): global search button toggle
- 在主题设置中添加全局搜索按钮的显示控制选项
- 更新多语言文件,添加全局搜索按钮显示控制的翻译
- 修改全局头部组件,根据主题设置决定是否显示全局搜索按钮
- 在主题抽屉中添加全局搜索按钮显示控制的开关
2025-05-31 13:40:42 +08:00
Azir
dfb647a82c fix(hooks): refactor useCountDown hook for improved countdown logic and clarity. 2025-05-26 15:11:01 +08:00
Azir
7fb5c72f7e fix(projects): tab closure did not remove cache correctly. 2025-05-26 15:11:01 +08:00
Soybean
41b5f49341 chore(deps): update deps 2025-05-11 22:03:37 +08:00
Soybean
f35c250a89 docs(projects): add gitcode link 2025-05-11 22:01:47 +08:00
青菜白玉汤
1ff4d82d19
feat(projects): clear tabs cache when switching users. (#744) 2025-04-30 14:46:32 +08:00
青菜白玉汤
c3abc3df09
ci(hooks): remove lint-staged in git hook. (#743) close #724 2025-04-28 19:17:43 +08:00
WgoW
a013ea2c46
docs(README): Add supporting ecosystem tools to the open-source repository (#740)
Co-authored-by: a <a@gmail.com>
2025-04-23 22:23:13 +08:00
tu6ge
61244f0f2a
chore(deps): add vscode recommend plugin (#739) close #738 2025-04-23 21:34:48 +08:00
青菜白玉汤
85e40b1943
fix(hooks): fixed the issue where loading was not properly closed in some cases. (#737) 2025-04-23 21:34:18 +08:00
WgoW
123d2c9074
feat(types): enhance Option type to support customizable label types (#735)
Co-authored-by: a <a@gmail.com>
2025-04-23 21:34:06 +08:00
Soybean
05dc11e258 feat(docs): add GitCode star badge to README files 2025-04-18 11:25:38 +08:00
青菜白玉汤
8527aa80b8
feat(utils): support quick generation of code templates. (#733) 2025-04-14 08:42:10 +08:00
青菜白玉汤
804860994e
docs(deps): update the Vite version of the project description. (#732) 2025-04-12 15:27:27 +08:00
青菜白玉汤
29698bef69
feat(projects): support vite devtools specify the editor by launchEditor option. (#730) 2025-04-04 12:00:52 +08:00
青菜白玉汤
4e1b65b6c4
optimize(hooks): remove obsolete disabling cache. (#729) 2025-04-01 11:36:27 +08:00
XiaTianYa
3cbaf4f4bf
docs(projects): update README (#726) 2025-03-21 12:40:38 +08:00
46 changed files with 2672 additions and 2521 deletions

4
.env
View File

@ -54,3 +54,7 @@ VITE_AUTOMATICALLY_DETECT_UPDATE=Y
# show proxy url log in terminal
VITE_PROXY_LOG=Y
# used to control whether to launch editor
# by the way, this plugin is only available in dev mode, not in build mode
VITE_DEVTOOLS_LAUNCH_EDITOR=code

View File

@ -14,6 +14,7 @@
"sdras.vue-vscode-snippets",
"vue.volar",
"whtouche.vscode-js-console-utils",
"zhuangtongfa.material-theme"
"zhuangtongfa.material-theme",
"tu6ge.naive-ui-intelligence"
]
}

19
.vscode/settings.json vendored
View File

@ -4,15 +4,28 @@
"source.organizeImports": "never"
},
"editor.formatOnSave": false,
"eslint.validate": ["html", "css", "scss", "json", "jsonc"],
"eslint.validate": [
"html",
"css",
"scss",
"json",
"jsonc",
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue"
],
"i18n-ally.displayLanguage": "zh-cn",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.editor.preferEditor": true,
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": ["src/locales/langs"],
"i18n-ally.parsers.typescript.compilerOptions": {
"moduleResolution": "node"
},
"prettier.enable": false,
"typescript.tsdk": "node_modules/typescript/lib",
"unocss.root": ["./"],
"vue.server.hybridMode": true
"unocss.root": ["./"]
}

51
.vscode/vue3.code-snippets vendored Normal file
View File

@ -0,0 +1,51 @@
{
"Print soy Vue3 SFC page": {
"scope": "vue",
"prefix": ["v3","page","view"],
"body": [
"<script lang=\"ts\" setup>",
"//$1",
"</script>\n",
"<template>",
" <div class=\"\">",
" <!-- page only one root element -->",
" $2",
" </div>",
"</template>\n",
],
},
"Print soy Vue3 SFC Component": {
"scope": "vue",
"prefix": ["component","comp"],
"body": [
"<script lang=\"ts\" setup>",
"//$1",
"</script>\n",
"<template>",
" <div class=\"\">",
" $2",
" </div>",
"</template>\n",
],
},
"Print soy style": {
"scope": "vue",
"prefix": "st",
"body": ["<style scoped>", "//", "</style>\n"],
},
"Print soy script": {
"scope": "vue",
"prefix": "sc",
"body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
},
"Print soy template": {
"scope": "vue",
"prefix": "te",
"body": [
"<template>",
" <div class=\"\">$1</div>",
"</template>\n",
],
},
}

View File

@ -1,6 +1,102 @@
# Changelog
## [v1.3.15](https://github.com/soybeanjs/soybean-admin/compare/v1.3.14...v1.3.15) (2025-06-24)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **projects**: add configurable user name watermark option &nbsp;-&nbsp; by @wenyuanw [<samp>(7c3da)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7c3dac42)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **app**: replace console.error with window.console.error for consistency &nbsp;-&nbsp; by @soybeanjs [<samp>(7d840)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7d84062e)
- **projects**: ensure proper text color when themes are inverted &nbsp;-&nbsp; by @wenyuanw [<samp>(afd60)</samp>](https://github.com/soybeanjs/soybean-admin/commit/afd60421)
- **types**: The environment variable VITE_ICON_LOCAL_PREFIX has the wrong type. &nbsp;-&nbsp; by **chenziwen** [<samp>(da149)</samp>](https://github.com/soybeanjs/soybean-admin/commit/da149e5b)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **components**: optimize spacing for lang-switch dropdown options &nbsp;-&nbsp; by @wenyuanw [<samp>(fcb89)</samp>](https://github.com/soybeanjs/soybean-admin/commit/fcb89883)
### &nbsp;&nbsp;&nbsp;💅 Refactors
- **iframe-page**: remove unused lifecycle hooks and clean up script setup &nbsp;-&nbsp; by @soybeanjs [<samp>(276d8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/276d836c)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **other**: update docs with video tutorial link. &nbsp;-&nbsp; by **Azir** [<samp>(7b2e5)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7b2e510a)
- **readme**: add warning about upcoming `V2` version and link to plan list &nbsp;-&nbsp; by @soybeanjs [<samp>(4d42d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4d42dcbe)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**: update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(dc674)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dc674ce8)
- **projects**: update deps & fix `moduleResolution` &nbsp;-&nbsp; by @soybeanjs [<samp>(dbd99)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dbd995c1)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)&nbsp;&nbsp;
[Azir](mailto:2075125282@qq.com),&nbsp;[chenziwen](mailto:chenziwen@qesong.com)
## [v1.3.14](https://github.com/soybeanjs/soybean-admin/compare/v1.3.13...v1.3.14) (2025-06-09)
### &nbsp;&nbsp;&nbsp;🚀 Features
- **docs**:
- add GitCode star badge to README files &nbsp;-&nbsp; by @soybeanjs [<samp>(05dc1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/05dc11e2)
- add DartNode sponsorship badge to README files &nbsp;-&nbsp; by @soybeanjs [<samp>(2ed0b)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2ed0b648)
- **projects**:
- support vite devtools specify the editor by launchEditor option. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/730 [<samp>(29698)</samp>](https://github.com/soybeanjs/soybean-admin/commit/29698bef)
- clear tabs cache when switching users. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/744 [<samp>(1ff4d)</samp>](https://github.com/soybeanjs/soybean-admin/commit/1ff4d82d)
- **theme**:
- global search button toggle &nbsp;-&nbsp; by **t8y2** [<samp>(75455)</samp>](https://github.com/soybeanjs/soybean-admin/commit/75455b00)
- **types**:
- enhance Option type to support customizable label types &nbsp;-&nbsp; by @WgoW and @testbrate in https://github.com/soybeanjs/soybean-admin/issues/735 [<samp>(123d2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/123d2c90)
- **utils**:
- support quick generation of code templates. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/733 [<samp>(8527a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/8527aa80)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes
- **auth**:
- remove redundant authStore declaration in resetStore function &nbsp;-&nbsp; by @soybeanjs [<samp>(c57f8)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c57f88aa)
- **hooks**:
- fixed the issue where loading was not properly closed in some cases. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/737 [<samp>(85e40)</samp>](https://github.com/soybeanjs/soybean-admin/commit/85e40b19)
- refactor useCountDown hook for improved countdown logic and clarity. &nbsp;-&nbsp; by **Azir** [<samp>(dfb64)</samp>](https://github.com/soybeanjs/soybean-admin/commit/dfb647a8)
- **projects**:
- tab closure did not remove cache correctly. &nbsp;-&nbsp; by **Azir** [<samp>(7fb5c)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7fb5c72f)
### &nbsp;&nbsp;&nbsp;🛠 Optimizations
- **hooks**:
- remove obsolete disabling cache. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/729 [<samp>(4e1b6)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4e1b65b6)
- update detection function to cover the exceptions that occur when the request fails. &nbsp;-&nbsp; by **恕瑞玛的皇帝** [<samp>(22218)</samp>](https://github.com/soybeanjs/soybean-admin/commit/222187d3)
- **projects**:
- optimize tab deletion logic. closed #755 &nbsp;-&nbsp; by @wenyuanw in https://github.com/soybeanjs/soybean-admin/issues/755 [<samp>(e6044)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e6044d0f)
### &nbsp;&nbsp;&nbsp;📖 Documentation
- **README**:
- Add supporting ecosystem tools to the open-source repository &nbsp;-&nbsp; by @WgoW and @testbrate in https://github.com/soybeanjs/soybean-admin/issues/740 [<samp>(a013e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a013ea2c)
- **deps**:
- update the Vite version of the project description. &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/732 [<samp>(80486)</samp>](https://github.com/soybeanjs/soybean-admin/commit/80486099)
- **projects**:
- update README &nbsp;-&nbsp; by @xiatianYa in https://github.com/soybeanjs/soybean-admin/issues/726 [<samp>(3cbaf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3cbaf4f4)
- add gitcode link &nbsp;-&nbsp; by @soybeanjs [<samp>(f35c2)</samp>](https://github.com/soybeanjs/soybean-admin/commit/f35c250a)
### &nbsp;&nbsp;&nbsp;🏡 Chore
- **deps**:
- add vscode recommend plugin close #738 &nbsp;-&nbsp; by @tu6ge in https://github.com/soybeanjs/soybean-admin/issues/739 and https://github.com/soybeanjs/soybean-admin/issues/738 [<samp>(61244)</samp>](https://github.com/soybeanjs/soybean-admin/commit/61244f0f)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(41b5f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/41b5f493)
- update deps &nbsp;-&nbsp; by @soybeanjs [<samp>(3e4e1)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3e4e17ab)
### &nbsp;&nbsp;&nbsp;🤖 CI
- **hooks**: remove lint-staged in git hook. close #724 &nbsp;-&nbsp; by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/743 and https://github.com/soybeanjs/soybean-admin/issues/724 [<samp>(c3abc)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c3abc3df)
### &nbsp;&nbsp;&nbsp;❤️ Contributors
[![soybeanjs](https://github.com/soybeanjs.png?size=48)](https://github.com/soybeanjs)&nbsp;&nbsp;[![wenyuanw](https://github.com/wenyuanw.png?size=48)](https://github.com/wenyuanw)&nbsp;&nbsp;[![Azir-11](https://github.com/Azir-11.png?size=48)](https://github.com/Azir-11)&nbsp;&nbsp;[![WgoW](https://github.com/WgoW.png?size=48)](https://github.com/WgoW)&nbsp;&nbsp;[![testbrate](https://github.com/testbrate.png?size=48)](https://github.com/testbrate)&nbsp;&nbsp;[![tu6ge](https://github.com/tu6ge.png?size=48)](https://github.com/tu6ge)&nbsp;&nbsp;[![xiatianYa](https://github.com/xiatianYa.png?size=48)](https://github.com/xiatianYa)&nbsp;&nbsp;
[恕瑞玛的皇帝](mailto:2075125282@qq.com),&nbsp;[t8y2](mailto:1156263951@qq.com),&nbsp;
## [v1.3.13](https://github.com/soybeanjs/soybean-admin/compare/v1.3.12...v1.3.13) (2025-03-19)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes

View File

@ -7,9 +7,10 @@
---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
[![github stars](https://img.shields.io/github/stars/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github stars](https://img.shields.io/github/stars/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
@ -17,14 +18,20 @@
> [!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!
> [!NOTE]
> The `SoybeanAdmin` quick start series videos have been uploaded to [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) Go online [click here](https://www.bilibili.com/video/BV1YKdRYXELC) Go check it out
> [!WARNING]
> `SoybeanAdmin` is planning to develop a `V2` version, see [plan list](https://github.com/soybeanjs/soybean-admin/issues/767)
## Introduction
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite5, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) is a clean, elegant, beautiful and powerful admin template, based on the latest front-end technology stack, including Vue3, Vite7, TypeScript, Pinia and UnoCSS. It has built-in rich theme configuration and components, strict code specifications, and an automated file routing system. In addition, it also uses the online mock data solution based on ApiFox. `SoybeanAdmin` provides you with a one-stop admin solution, no additional configuration, and out of the box. It is also a best practice for learning cutting-edge technologies quickly.
## Features
- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite5, TypeScript, Pinia and UnoCSS.
- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite7, TypeScript, Pinia and UnoCSS.
- **Clear project architecture**: using pnpm monorepo architecture, clear structure, elegant and easy to understand.
- **Strict code specifications**: follow the [SoybeanJS specification](https://docs.soybeanjs.cn/standard), integrate eslint, prettier and simple-git-hooks to ensure the code is standardized.
- **TypeScript**: support strict type checking to improve code maintainability.
@ -43,19 +50,25 @@
- [Preview Link](https://naive.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin)
- [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin)
- **AntDesignVue Version:**
- [Preview Link](https://antd.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin-antd)
- [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin-antd)
- **ElementPlus Version:**
- [Preview Link](https://elp.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-element-plus)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin-element-plus)
- [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin-element-plus)
- **Legacy Version:**
- [Preview Link](https://legacy.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin/tree/legacy)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin/tree/legacy)
- [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin/tree/legacy)
## Documentation
@ -85,13 +98,18 @@
Make sure your environment meets the following requirements:
- **git**: you need git to clone and manage project versions.
- **NodeJS**: >=18.12.0, recommended 18.19.0 or higher.
- **pnpm**: >= 8.7.0, recommended 8.14.0 or higher.
- **NodeJS**: >=20.19.0, recommended 20.19.0 or higher.
- **pnpm**: >= 10.5.0, recommended 10.5.0 or higher.
**Clone Project**
```bash
# github
git clone https://github.com/soybeanjs/soybean-admin.git
# gitee
git clone https://gitee.com/honghuangdc/soybean-admin.git
# gitcode
git clone https://gitcode.com/soybeanjs/soybean-admin.git
```
**Install Dependencies**
@ -128,6 +146,8 @@ Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) docume
- [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.
- [ba](https://github.com/xiatianYa/Ba-Server): Backend service docking with soybean admin based on goFrame framework, adapted to dynamic routing, and interface authentication permissions.
- [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):A Go backend service developed based on the Gin and GORM frameworks, integrated with the example branch of Soybean Admin. It supports dynamic routing and API permission authentication.
## How to Contribute

View File

@ -10,19 +10,26 @@
[![github stars](https://img.shields.io/github/stars/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
> [!NOTE]
> 如果您觉得 `SoybeanAdmin`对您有所帮助,或者您喜欢我们的项目,请在 GitHub 上给我们一个 ⭐️。您的支持是我们持续改进和增加新功能的动力!感谢您的支持!
> [!NOTE]
> `SoybeanAdmin` 快速上手系列视频已在 [Bilibili](https://www.bilibili.com/video/BV1YKdRYXELC) 上线 [点击这里](https://www.bilibili.com/video/BV1YKdRYXELC) 前往查看
> [!WARNING]
> `SoybeanAdmin` 正在计划开发 `V2` 版本,详情见[计划清单](https://github.com/soybeanjs/soybean-admin/issues/767)
## 简介
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite7, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。
## 特性
- **前沿技术应用**:采用 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。
- **前沿技术应用**:采用 Vue3, Vite7, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。
- **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。
- **严格的代码规范**:遵循 [SoybeanJS 规范](https://docs.soybeanjs.cn/zh/standard)集成了eslint, prettier 和 simple-git-hooks保证代码的规范性。
- **TypeScript** 支持严格的类型检查,提高代码的可维护性。
@ -41,16 +48,22 @@
- [预览地址](https://naive.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin)
- [Gitcode 仓库](https://gitcode.com/soybeanjs/soybean-admin)
- **AntDesignVue 版本:**
- [预览地址](https://antd.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-antd)
- [Gitcode 仓库](https://gitcode.com/soybeanjs/soybean-admin-antd)
- **ElementPlus 版本:**
- [预览地址](https://elp.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-element-plus)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-element-plus)
- [Gitcode 仓库](https://gitcode.com/soybeanjs/soybean-admin-element-plus)
- **旧版:**
- [预览地址](https://legacy.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin/tree/legacy)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin/tree/legacy)
- [Gitcode 仓库](https://gitcode.com/soybeanjs/soybean-admin/tree/legacy)
## 文档
@ -110,13 +123,18 @@
确保你的环境满足以下要求:
- **git**: 你需要git来克隆和管理项目版本。
- **NodeJS**: >=18.12.0,推荐 18.19.0 或更高。
- **pnpm**: >= 8.7.0,推荐 8.14.0 或更高。
- **NodeJS**: >=20.19.0,推荐 20.19.0 或更高。
- **pnpm**: >= 10.5.0,推荐 10.5.0 或更高。
**克隆项目**
```bash
# github
git clone https://github.com/soybeanjs/soybean-admin.git
# gitee
git clone https://gitee.com/honghuangdc/soybean-admin.git
# gitcode
git clone https://gitcode.com/soybeanjs/soybean-admin.git
```
**安装依赖**
@ -153,6 +171,8 @@ pnpm build
- [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 的现代化轻量管理平台.
- [ba](https://github.com/xiatianYa/Ba-Server): 基于goFrame框架开发的后端服务对接soybean-admin,适配动态路由,接口鉴权限。
- [soybean-admin-go](https://github.com/WgoW/soybean-admin-go):基于gin+gorm框架开发的go语言后端服务对接soybean-admin的example分支,适配动态路由,接口鉴权限。
## 如何贡献

View File

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

View File

@ -0,0 +1,9 @@
import VueDevtools from 'vite-plugin-vue-devtools';
export function setupDevtoolsPlugin(viteEnv: Env.ImportMeta) {
const { VITE_DEVTOOLS_LAUNCH_EDITOR } = viteEnv;
return VueDevtools({
launchEditor: VITE_DEVTOOLS_LAUNCH_EDITOR
});
}

View File

@ -1,18 +1,18 @@
import type { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import VueDevtools from 'vite-plugin-vue-devtools';
import progress from 'vite-plugin-progress';
import { setupElegantRouter } from './router';
import { setupUnocss } from './unocss';
import { setupUnplugin } from './unplugin';
import { setupHtmlPlugin } from './html';
import { setupDevtoolsPlugin } from './devtools';
export function setupVitePlugins(viteEnv: Env.ImportMeta, buildTime: string) {
const plugins: PluginOption = [
vue(),
vueJsx(),
VueDevtools(),
setupDevtoolsPlugin(viteEnv),
setupElegantRouter(),
setupUnocss(viteEnv),
...setupUnplugin(viteEnv),

View File

@ -1,8 +1,8 @@
{
"name": "soybean-admin",
"type": "module",
"version": "1.3.13",
"description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
"version": "1.3.15",
"description": "A fresh and elegant admin template, based on Vue3、Vite7、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite7、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
"author": {
"name": "Soybean",
"email": "soybeanjs@outlook.com",
@ -19,7 +19,7 @@
"keywords": [
"Vue3 admin ",
"vue-admin-template",
"Vite5",
"Vite7",
"TypeScript",
"naive-ui",
"naive-ui-admin",
@ -27,8 +27,8 @@
"UnoCSS"
],
"engines": {
"node": ">=18.20.0",
"pnpm": ">=8.7.0"
"node": ">=20.19.0",
"pnpm": ">=10.5.0"
},
"scripts": {
"build": "vite build --mode prod",
@ -48,67 +48,63 @@
},
"dependencies": {
"@better-scroll/core": "2.5.1",
"@iconify/vue": "4.3.0",
"@iconify/vue": "5.0.0",
"@sa/axios": "workspace:*",
"@sa/color": "workspace:*",
"@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*",
"@sa/utils": "workspace:*",
"@vueuse/core": "13.0.0",
"@vueuse/core": "13.9.0",
"clipboard": "2.0.11",
"dayjs": "1.11.13",
"dayjs": "1.11.18",
"defu": "6.1.4",
"echarts": "5.6.0",
"echarts": "6.0.0",
"json5": "2.2.3",
"naive-ui": "2.41.0",
"naive-ui": "2.43.1",
"nprogress": "0.2.0",
"pinia": "3.0.1",
"tailwind-merge": "3.0.2",
"vue": "3.5.13",
"pinia": "3.0.3",
"tailwind-merge": "3.3.1",
"vue": "3.5.21",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.2",
"vue-router": "4.5.0"
"vue-i18n": "11.1.12",
"vue-router": "4.5.1"
},
"devDependencies": {
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.318",
"@iconify/json": "2.2.385",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.6.0",
"@types/node": "22.13.10",
"@soybeanjs/eslint-config": "1.7.1",
"@types/node": "24.5.1",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.0.0",
"@unocss/preset-icons": "66.0.0",
"@unocss/preset-uno": "66.0.0",
"@unocss/transformer-directives": "66.0.0",
"@unocss/transformer-variant-group": "66.0.0",
"@unocss/vite": "66.0.0",
"@vitejs/plugin-vue": "5.2.3",
"@vitejs/plugin-vue-jsx": "4.1.2",
"@unocss/eslint-config": "66.5.1",
"@unocss/preset-icons": "66.5.1",
"@unocss/preset-uno": "66.5.1",
"@unocss/transformer-directives": "66.5.1",
"@unocss/transformer-variant-group": "66.5.1",
"@unocss/vite": "66.5.1",
"@vitejs/plugin-vue": "6.0.1",
"@vitejs/plugin-vue-jsx": "5.1.1",
"consola": "3.4.2",
"eslint": "9.22.0",
"eslint-plugin-vue": "10.0.0",
"eslint": "9.35.0",
"eslint-plugin-vue": "10.4.0",
"kolorist": "1.8.0",
"lint-staged": "15.5.0",
"sass": "1.86.0",
"simple-git-hooks": "2.11.1",
"tsx": "4.19.3",
"typescript": "5.8.2",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.4.1",
"vite": "6.2.2",
"sass": "1.92.1",
"simple-git-hooks": "2.13.1",
"tsx": "4.20.5",
"typescript": "5.9.2",
"unplugin-icons": "22.3.0",
"unplugin-vue-components": "29.0.0",
"vite": "7.1.5",
"vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "7.7.2",
"vue-eslint-parser": "10.1.1",
"vue-tsc": "2.2.8"
"vite-plugin-vue-devtools": "8.0.2",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.7"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",
"pre-commit": "pnpm typecheck && pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
"pre-commit": "pnpm typecheck && pnpm lint && git diff --exit-code"
},
"website": "https://admin.soybeanjs.cn"
}

View File

@ -1,6 +1,6 @@
{
"name": "@sa/alova",
"version": "1.3.13",
"version": "1.3.15",
"exports": {
".": "./src/index.ts",
"./fetch": "./src/fetch.ts",
@ -13,8 +13,8 @@
}
},
"dependencies": {
"@alova/mock": "2.0.12",
"@alova/mock": "2.0.17",
"@sa/utils": "workspace:*",
"alova": "3.2.10"
"alova": "3.3.4"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@sa/axios",
"version": "1.3.13",
"version": "1.3.15",
"exports": {
".": "./src/index.ts"
},
@ -11,11 +11,11 @@
},
"dependencies": {
"@sa/utils": "workspace:*",
"axios": "1.8.3",
"axios": "1.12.2",
"axios-retry": "4.5.0",
"qs": "6.14.0"
},
"devDependencies": {
"@types/qs": "6.9.18"
"@types/qs": "6.14.0"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -2,40 +2,59 @@ import { computed, onScopeDispose, ref } from 'vue';
import { useRafFn } from '@vueuse/core';
/**
* count down
* A hook for implementing a countdown timer. It uses `requestAnimationFrame` for smooth and accurate timing,
* independent of the screen refresh rate.
*
* @param seconds - count down seconds
* @param initialSeconds - The total number of seconds for the countdown.
*/
export default function useCountDown(seconds: number) {
const FPS_PER_SECOND = 60;
export default function useCountDown(initialSeconds: number) {
const remainingSeconds = ref(0);
const fps = ref(0);
const count = computed(() => Math.ceil(remainingSeconds.value));
const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND));
const isCounting = computed(() => fps.value > 0);
const isCounting = computed(() => remainingSeconds.value > 0);
const { pause, resume } = useRafFn(
() => {
if (fps.value > 0) {
fps.value -= 1;
} else {
({ delta }) => {
// delta: milliseconds elapsed since the last frame.
// If countdown already reached zero or below, ensure it's 0 and stop.
if (remainingSeconds.value <= 0) {
remainingSeconds.value = 0;
pause();
return;
}
// Calculate seconds passed since the last frame.
const secondsPassed = delta / 1000;
remainingSeconds.value -= secondsPassed;
// If countdown has finished after decrementing.
if (remainingSeconds.value <= 0) {
remainingSeconds.value = 0;
pause();
}
},
{ immediate: false }
{ immediate: false } // The timer does not start automatically.
);
function start(updateSeconds: number = seconds) {
fps.value = FPS_PER_SECOND * updateSeconds;
/**
* Starts the countdown.
*
* @param [updatedSeconds=initialSeconds] - Optionally, start with a new duration. Default is `initialSeconds`
*/
function start(updatedSeconds: number = initialSeconds) {
remainingSeconds.value = updatedSeconds;
resume();
}
/** Stops the countdown and resets the remaining time to 0. */
function stop() {
fps.value = 0;
remainingSeconds.value = 0;
pause();
}
// Ensure the rAF loop is cleaned up when the component is unmounted.
onScopeDispose(() => {
pause();
});

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@sa/scripts",
"version": "1.3.13",
"version": "1.3.15",
"bin": {
"sa": "./bin.ts"
},
@ -13,15 +13,15 @@
}
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.24",
"bumpp": "10.1.0",
"c12": "3.0.2",
"@soybeanjs/changelog": "0.3.25",
"bumpp": "10.2.3",
"c12": "3.3.0",
"cac": "6.7.14",
"consola": "3.4.2",
"enquirer": "2.4.1",
"execa": "9.5.2",
"execa": "9.6.0",
"kolorist": "1.8.0",
"npm-check-updates": "17.1.15",
"npm-check-updates": "18.1.1",
"rimraf": "6.0.1"
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import { NConfigProvider, darkTheme } from 'naive-ui';
import type { WatermarkProps } from 'naive-ui';
import { useAppStore } from './store/modules/app';
import { useThemeStore } from './store/modules/theme';
import { useAuthStore } from './store/modules/auth';
import { naiveDateLocales, naiveLocales } from './locales/naive';
defineOptions({
@ -12,6 +13,7 @@ defineOptions({
const appStore = useAppStore();
const themeStore = useThemeStore();
const authStore = useAuthStore();
const naiveDarkTheme = computed(() => (themeStore.darkMode ? darkTheme : undefined));
@ -24,8 +26,13 @@ const naiveDateLocale = computed(() => {
});
const watermarkProps = computed<WatermarkProps>(() => {
const content =
themeStore.watermark.enableUserName && authStore.userInfo.userName
? authStore.userInfo.userName
: themeStore.watermark.text;
return {
content: themeStore.watermark.text,
content,
cross: true,
fullscreen: true,
fontSize: 16,

View File

@ -31,13 +31,25 @@ const tooltipContent = computed(() => {
return $t('icon.lang');
});
/** Add bottom margin to all options except the last one for proper visual separation */
const dropdownOptions = computed(() => {
const lastIndex = props.langOptions.length - 1;
return props.langOptions.map((option, index) => ({
...option,
props: {
class: index < lastIndex ? 'mb-1' : undefined
}
}));
});
function changeLang(lang: App.I18n.LangType) {
emit('changeLang', lang);
}
</script>
<template>
<NDropdown :value="lang" :options="langOptions" trigger="hover" @select="changeLang">
<NDropdown :value="lang" :options="dropdownOptions" trigger="hover" @select="changeLang">
<div>
<ButtonIcon :tooltip-content="tooltipContent" tooltip-placement="left">
<SvgIcon icon="heroicons:language" />

View File

@ -210,6 +210,10 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
// render chart
await render();
if (chart) {
await onUpdated?.(chart);
}
}
scope.run(() => {

View File

@ -38,7 +38,7 @@ const { isFullscreen, toggle } = useFullscreen();
<GlobalBreadcrumb v-if="!appStore.isMobile" class="ml-12px" />
</div>
<div class="h-full flex-y-center justify-end">
<GlobalSearch />
<GlobalSearch v-if="themeStore.header.globalSearch.visible" />
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
<LangSwitch
v-if="themeStore.header.multilingual.visible"

View File

@ -5,7 +5,6 @@ import { useElementBounding } from '@vueuse/core';
import { PageTab } 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';
import { isPC } from '@/utils/agent';
import BetterScroll from '@/components/custom/better-scroll.vue';
@ -18,7 +17,6 @@ defineOptions({
const route = useRoute();
const appStore = useAppStore();
const themeStore = useThemeStore();
const routeStore = useRouteStore();
const tabStore = useTabStore();
const bsWrapper = ref<HTMLElement>();
@ -82,12 +80,8 @@ function getContextMenuDisabledKeys(tabId: string) {
return disabledKeys;
}
async function handleCloseTab(tab: App.Global.Tab) {
await tabStore.removeTab(tab.id);
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(tab.routeKey);
}
function handleCloseTab(tab: App.Global.Tab) {
tabStore.removeTab(tab.id);
}
async function refresh() {

View File

@ -117,7 +117,10 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
<SettingItem key="8" :label="$t('theme.watermark.visible')">
<NSwitch v-model:value="themeStore.watermark.visible" />
</SettingItem>
<SettingItem v-if="themeStore.watermark.visible" key="8-1" :label="$t('theme.watermark.text')">
<SettingItem v-if="themeStore.watermark.visible" key="8-1" :label="$t('theme.watermark.enableUserName')">
<NSwitch v-model:value="themeStore.watermark.enableUserName" />
</SettingItem>
<SettingItem v-if="themeStore.watermark.visible" key="8-2" :label="$t('theme.watermark.text')">
<NInput
v-model:value="themeStore.watermark.text"
autosize
@ -130,6 +133,9 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
</SettingItem>
<SettingItem key="10" :label="$t('theme.header.globalSearch.visible')">
<NSwitch v-model:value="themeStore.header.globalSearch.visible" />
</SettingItem>
</TransitionGroup>
</template>

View File

@ -112,6 +112,9 @@ const local: App.I18n.Schema = {
},
multilingual: {
visible: 'Display multilingual button'
},
globalSearch: {
visible: 'Display GlobalSearch button'
}
},
tab: {
@ -140,7 +143,8 @@ const local: App.I18n.Schema = {
},
watermark: {
visible: 'Watermark Full Screen Visible',
text: 'Watermark Text'
text: 'Watermark Text',
enableUserName: 'Enable User Name Watermark'
},
themeDrawerTitle: 'Theme Configuration',
pageFunTitle: 'Page Function',

View File

@ -112,6 +112,9 @@ const local: App.I18n.Schema = {
},
multilingual: {
visible: '显示多语言按钮'
},
globalSearch: {
visible: '显示全局搜索按钮'
}
},
tab: {
@ -140,7 +143,8 @@ const local: App.I18n.Schema = {
},
watermark: {
visible: '显示全屏水印',
text: '水印文本'
text: '水印文本',
enableUserName: '启用用户名水印'
},
themeDrawerTitle: '主题配置',
pageFunTitle: '页面功能',

View File

@ -25,8 +25,8 @@ export function setupAppVersionNotification() {
const buildTime = await getHtmlBuildTime();
// If build time hasn't changed, no update is needed
if (buildTime === BUILD_TIME) {
// If failed to get build time or build time hasn't changed, no update is needed.
if (!buildTime || buildTime === BUILD_TIME) {
return;
}
@ -88,16 +88,21 @@ export function setupAppVersionNotification() {
}
}
async function getHtmlBuildTime() {
async function getHtmlBuildTime(): Promise<string | null> {
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
try {
const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
const html = await res.text();
const match = html.match(/<meta name="buildTime" content="(.*)">/);
const buildTime = match?.[1] || '';
return buildTime;
if (!res.ok) {
return null;
}
const html = await res.text();
const match = html.match(/<meta name="buildTime" content="(.*)">/);
return match?.[1] || null;
} catch (error) {
window.console.error('getHtmlBuildTime error:', error);
return null;
}
}

View File

@ -1,4 +1,4 @@
import { addAPIProvider, disableCache } from '@iconify/vue';
import { addAPIProvider } from '@iconify/vue';
/** Setup the iconify offline */
export function setupIconifyOffline() {
@ -6,7 +6,5 @@ export function setupIconifyOffline() {
if (VITE_ICONIFY_URL) {
addAPIProvider('', { resources: [VITE_ICONIFY_URL] });
disableCache('all');
}
}

View File

@ -13,6 +13,7 @@ import { clearAuthStorage, getToken } from './shared';
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const route = useRoute();
const authStore = useAuthStore();
const routeStore = useRouteStore();
const tabStore = useTabStore();
const { toLogin, redirectFromLogin } = useRouterPush(false);
@ -39,7 +40,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
/** Reset auth store */
async function resetStore() {
const authStore = useAuthStore();
recordUserId();
clearAuthStorage();
@ -53,6 +54,41 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
routeStore.resetStore();
}
/** Record the user ID of the previous login session Used to compare with the current user ID on next login */
function recordUserId() {
if (!userInfo.userId) {
return;
}
// Store current user ID locally for next login comparison
localStg.set('lastLoginUserId', userInfo.userId);
}
/**
* Check if current login user is different from previous login user If different, clear all tabs
*
* @returns {boolean} Whether to clear all tabs
*/
function checkTabClear(): boolean {
if (!userInfo.userId) {
return false;
}
const lastLoginUserId = localStg.get('lastLoginUserId');
// Clear all tabs if current user is different from previous user
if (!lastLoginUserId || lastLoginUserId !== userInfo.userId) {
localStg.remove('globalTabs');
tabStore.clearTabs();
localStg.remove('lastLoginUserId');
return true;
}
localStg.remove('lastLoginUserId');
return false;
}
/**
* Login
*
@ -69,7 +105,15 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const pass = await loginByToken(loginToken);
if (pass) {
await redirectFromLogin(redirect);
// Check if the tab needs to be cleared
const isClear = checkTabClear();
let needRedirect = redirect;
if (isClear) {
// If the tab needs to be cleared,it means we don't need to redirect.
needRedirect = false;
}
await redirectFromLogin(needRedirect);
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),

View File

@ -98,13 +98,24 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
if (removeTabIndex === -1) return;
const removedTabRouteKey = tabs.value[removeTabIndex].routeKey;
const isRemoveActiveTab = activeTabId.value === tabId;
const nextTab = tabs.value[removeTabIndex + 1] || homeTab.value;
// if remove the last tab, then switch to the second last tab
const nextTab = tabs.value[removeTabIndex + 1] || tabs.value[removeTabIndex - 1] || homeTab.value;
// remove tab
tabs.value.splice(removeTabIndex, 1);
// if current tab is removed, then switch to next tab
if (isRemoveActiveTab && nextTab) {
await switchRouteByTab(nextTab);
}
// reset route cache if cache strategy is close
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(removedTabRouteKey);
}
}
/** remove active tab */
@ -131,9 +142,26 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
*/
async function clearTabs(excludes: string[] = []) {
const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes];
const removedTabsIds = tabs.value.map(tab => tab.id).filter(id => !remainTabIds.includes(id));
// Identify tabs to be removed and collect their routeKeys if strategy is 'close'
const tabsToRemove = tabs.value.filter(tab => !remainTabIds.includes(tab.id));
const routeKeysToReset: RouteKey[] = [];
if (themeStore.resetCacheStrategy === 'close') {
for (const tab of tabsToRemove) {
routeKeysToReset.push(tab.routeKey);
}
}
const removedTabsIds = tabsToRemove.map(tab => tab.id);
// If no tabs are actually being removed based on excludes and fixed tabs, exit
if (removedTabsIds.length === 0) {
return;
}
const isRemoveActiveTab = removedTabsIds.includes(activeTabId.value);
// filterTabsByIds returns tabs NOT in removedTabsIds, so these are the tabs that will remain
const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);
function update() {
@ -142,13 +170,21 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
if (!isRemoveActiveTab) {
update();
return;
} else {
const activeTabCandidate = updatedTabs[updatedTabs.length - 1] || homeTab.value;
if (activeTabCandidate) {
// Ensure there's a tab to switch to
await switchRouteByTab(activeTabCandidate);
}
// Update the tabs array regardless of switch success or if a candidate was found
update();
}
const activeTab = updatedTabs[updatedTabs.length - 1] || homeTab.value;
await switchRouteByTab(activeTab);
update();
// After tabs are updated and route potentially switched, reset cache for removed tabs
for (const routeKey of routeKeysToReset) {
routeStore.resetRouteCache(routeKey);
}
}
const { routerPushByKey } = useRouterPush();

View File

@ -10,4 +10,5 @@ body,
html {
overflow-x: hidden;
color: rgb(var(--base-text-color));
}

View File

@ -30,6 +30,9 @@ export const themeSettings: App.Theme.ThemeSetting = {
},
multilingual: {
visible: true
},
globalSearch: {
visible: true
}
},
tab: {
@ -55,7 +58,8 @@ export const themeSettings: App.Theme.ThemeSetting = {
},
watermark: {
visible: false,
text: 'SoybeanAdmin'
text: 'SoybeanAdmin',
enableUserName: false
},
tokens: {
light: {

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

@ -58,6 +58,10 @@ declare namespace App {
/** Whether to show the multilingual */
visible: boolean;
};
globalSearch: {
/** Whether to show the GlobalSearch */
visible: boolean;
};
};
/** Tab */
tab: {
@ -108,6 +112,8 @@ declare namespace App {
visible: boolean;
/** Watermark text */
text: string;
/** Whether to use user name as watermark text */
enableUserName: boolean;
};
/** define some theme settings tokens, will transform to css variables */
tokens: {
@ -377,6 +383,9 @@ declare namespace App {
multilingual: {
visible: string;
};
globalSearch: {
visible: string;
};
};
tab: {
visible: string;
@ -401,6 +410,7 @@ declare namespace App {
watermark: {
visible: string;
text: string;
enableUserName: string;
};
themeDrawerTitle: string;
pageFunTitle: string;

View File

@ -14,7 +14,7 @@ declare namespace CommonType {
* @property value: The option value
* @property label: The option label
*/
type Option<K = string> = { value: K; label: string };
type Option<K = string, M = string> = { value: K; label: M };
type YesOrNo = 'Y' | 'N';

View File

@ -17,23 +17,14 @@ declare module 'vue' {
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
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']
IconMdiArrowDownThin: typeof import('~icons/mdi/arrow-down-thin')['default']
IconMdiArrowUpThin: typeof import('~icons/mdi/arrow-up-thin')['default']
IconMdiDrag: typeof import('~icons/mdi/drag')['default']
IconMdiKeyboardEsc: typeof import('~icons/mdi/keyboard-esc')['default']
IconMdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconUilSearch: typeof import('~icons/uil/search')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
@ -45,19 +36,14 @@ declare module 'vue' {
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDescriptions: typeof import('naive-ui')['NDescriptions']
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
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']
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NInput: typeof import('naive-ui')['NInput']
@ -70,10 +56,6 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
NPopover: typeof import('naive-ui')['NPopover']
NRadio: typeof import('naive-ui')['NRadio']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
@ -81,10 +63,8 @@ declare module 'vue' {
NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NThing: typeof import('naive-ui')['NThing']
NTooltip: typeof import('naive-ui')['NTooltip']
NTree: typeof import('naive-ui')['NTree']
NWatermark: typeof import('naive-ui')['NWatermark']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']

View File

@ -37,5 +37,7 @@ declare namespace StorageType {
layout: UnionKey.ThemeLayoutMode;
siderCollapse: boolean;
};
/** The last login user id */
lastLoginUserId: string;
}
}

View File

@ -25,7 +25,7 @@ declare namespace Env {
*
* This prefix is start with the icon prefix
*/
readonly VITE_ICON_LOCAL_PREFIX: 'local-icon';
readonly VITE_ICON_LOCAL_PREFIX: 'icon-local';
/** backend service base url */
readonly VITE_SERVICE_BASE_URL: string;
/**
@ -108,6 +108,8 @@ declare namespace Env {
readonly VITE_AUTOMATICALLY_DETECT_UPDATE?: CommonType.YesOrNo;
/** show proxy url log in terminal */
readonly VITE_PROXY_LOG?: CommonType.YesOrNo;
/** The launch editor */
readonly VITE_DEVTOOLS_LAUNCH_EDITOR?: import('vite-plugin-vue-devtools').VitePluginVueDevToolsOptions['launchEditor'];
}
}

View File

@ -22,7 +22,7 @@ export function transformRecordToOption<T extends Record<string, string>>(record
return Object.entries(record).map(([value, label]) => ({
value,
label
})) as CommonType.Option<keyof T>[];
})) as CommonType.Option<keyof T, T[keyof T]>[];
}
/**
@ -30,10 +30,10 @@ export function transformRecordToOption<T extends Record<string, string>>(record
*
* @param options
*/
export function translateOptions(options: CommonType.Option<string>[]) {
export function translateOptions(options: CommonType.Option<string, App.I18n.I18nKey>[]) {
return options.map(option => ({
...option,
label: $t(option.label as App.I18n.I18nKey)
label: $t(option.label)
}));
}

View File

@ -1,19 +1,9 @@
<script setup lang="ts">
import { onActivated, onMounted } from 'vue';
interface Props {
url: string;
}
defineProps<Props>();
onMounted(() => {
console.log('mounted');
});
onActivated(() => {
console.log('activated');
});
</script>
<template>

View File

@ -6,7 +6,7 @@
"lib": ["DOM", "ESNext"],
"baseUrl": ".",
"module": "ESNext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"paths": {
"@/*": ["./src/*"],
"~/*": ["./*"]