Merge branch 'main' into example

This commit is contained in:
Soybean 2025-06-09 22:49:41 +08:00
commit ec20829a22
61 changed files with 2594 additions and 2382 deletions

4
.env
View File

@ -54,3 +54,7 @@ VITE_AUTOMATICALLY_DETECT_UPDATE=Y
# show proxy url log in terminal # show proxy url log in terminal
VITE_PROXY_LOG=Y 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", "sdras.vue-vscode-snippets",
"vue.volar", "vue.volar",
"whtouche.vscode-js-console-utils", "whtouche.vscode-js-console-utils",
"zhuangtongfa.material-theme" "zhuangtongfa.material-theme",
"tu6ge.naive-ui-intelligence"
] ]
} }

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,67 @@
# Changelog # Changelog
## [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) ## [v1.3.13](https://github.com/soybeanjs/soybean-admin/compare/v1.3.12...v1.3.13) (2025-03-19)
### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes ### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes

View File

@ -10,6 +10,8 @@
[![github stars](https://img.shields.io/github/stars/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin) [![github stars](https://img.shields.io/github/stars/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin) [![github forks](https://img.shields.io/github/forks/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin) [![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a> <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>
@ -19,12 +21,12 @@
## Introduction ## 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, Vite6, 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 ## 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, Vite6, TypeScript, Pinia and UnoCSS.
- **Clear project architecture**: using pnpm monorepo architecture, clear structure, elegant and easy to understand. - **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. - **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. - **TypeScript**: support strict type checking to improve code maintainability.
@ -43,19 +45,25 @@
- [Preview Link](https://naive.soybeanjs.cn/) - [Preview Link](https://naive.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin) - [Github Repository](https://github.com/soybeanjs/soybean-admin)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin) - [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin)
- [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin)
- **AntDesignVue Version:** - **AntDesignVue Version:**
- [Preview Link](https://antd.soybeanjs.cn/) - [Preview Link](https://antd.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-antd) - [Github Repository](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin-antd) - [Gitee Repository](https://gitee.com/honghuangdc/soybean-admin-antd)
- [Gitcode Repository](https://gitcode.com/soybeanjs/soybean-admin-antd)
- **ElementPlus Version:** - **ElementPlus Version:**
- [Preview Link](https://elp.soybeanjs.cn/) - [Preview Link](https://elp.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin-element-plus) - [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:** - **Legacy Version:**
- [Preview Link](https://legacy.soybeanjs.cn/) - [Preview Link](https://legacy.soybeanjs.cn/)
- [Github Repository](https://github.com/soybeanjs/soybean-admin/tree/legacy) - [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 ## Documentation
@ -91,7 +99,12 @@ Make sure your environment meets the following requirements:
**Clone Project** **Clone Project**
```bash ```bash
# github
git clone https://github.com/soybeanjs/soybean-admin.git 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** **Install Dependencies**
@ -128,6 +141,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". - [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! - [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. - [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 ## How to Contribute

View File

@ -7,9 +7,11 @@
--- ---
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE) [![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
[![github stars](https://img.shields.io/github/stars/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin) [![github stars](https://img.shields.io/github/stars/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![github forks](https://img.shields.io/github/forks/honghuangdc/soybean-admin)](https://github.com/soybeanjs/soybean-admin) [![github forks](https://img.shields.io/github/forks/soybeanjs/soybean-admin)](https://github.com/soybeanjs/soybean-admin)
[![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin) [![gitee stars](https://gitee.com/honghuangdc/soybean-admin/badge/star.svg)](https://gitee.com/honghuangdc/soybean-admin)
[![gitcode star](https://gitcode.com/soybeanjs/soybean-admin/star/badge.svg)](https://gitcode.com/soybeanjs/soybean-admin)
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="FeaturedHelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a> <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>
@ -18,11 +20,11 @@
## 简介 ## 简介
[`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 是一个清新优雅、高颜值且功能强大的后台管理模板,基于最新的前端技术栈,包括 Vue3, Vite6, TypeScript, Pinia 和 UnoCSS。它内置了丰富的主题配置和组件代码规范严谨实现了自动化的文件路由系统。此外它还采用了基于 ApiFox 的在线Mock数据方案。`SoybeanAdmin` 为您提供了一站式的后台管理解决方案,无需额外配置,开箱即用。同样是一个快速学习前沿技术的最佳实践。
## 特性 ## 特性
- **前沿技术应用**:采用 Vue3, Vite5, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。 - **前沿技术应用**:采用 Vue3, Vite6, TypeScript, Pinia 和 UnoCSS 等最新流行的技术栈。
- **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。 - **清晰的项目架构**:采用 pnpm monorepo 架构,结构清晰,优雅易懂。
- **严格的代码规范**:遵循 [SoybeanJS 规范](https://docs.soybeanjs.cn/zh/standard)集成了eslint, prettier 和 simple-git-hooks保证代码的规范性。 - **严格的代码规范**:遵循 [SoybeanJS 规范](https://docs.soybeanjs.cn/zh/standard)集成了eslint, prettier 和 simple-git-hooks保证代码的规范性。
- **TypeScript** 支持严格的类型检查,提高代码的可维护性。 - **TypeScript** 支持严格的类型检查,提高代码的可维护性。
@ -41,16 +43,22 @@
- [预览地址](https://naive.soybeanjs.cn/) - [预览地址](https://naive.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin) - [Github 仓库](https://github.com/soybeanjs/soybean-admin)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin) - [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin)
- [Gitcode 仓库](https://gitcode.com/soybeanjs/soybean-admin)
- **AntDesignVue 版本:** - **AntDesignVue 版本:**
- [预览地址](https://antd.soybeanjs.cn/) - [预览地址](https://antd.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-antd) - [Github 仓库](https://github.com/soybeanjs/soybean-admin-antd)
- [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-antd) - [Gitee 仓库](https://gitee.com/honghuangdc/soybean-admin-antd)
- [Gitcode 仓库](https://gitcode.com/soybeanjs/soybean-admin-antd)
- **ElementPlus 版本:** - **ElementPlus 版本:**
- [预览地址](https://elp.soybeanjs.cn/) - [预览地址](https://elp.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin-element-plus) - [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/) - [预览地址](https://legacy.soybeanjs.cn/)
- [Github 仓库](https://github.com/soybeanjs/soybean-admin/tree/legacy) - [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)
## 文档 ## 文档
@ -116,7 +124,12 @@
**克隆项目** **克隆项目**
```bash ```bash
# github
git clone https://github.com/soybeanjs/soybean-admin.git 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 +166,8 @@ pnpm build
- [snail-job](https://github.com/aizuda/snail-job): 一款兼具 “高性能、高颜值、高活跃” 的分布式任务重试和分布式任务调度平台。 - [snail-job](https://github.com/aizuda/snail-job): 一款兼具 “高性能、高颜值、高活跃” 的分布式任务重试和分布式任务调度平台。
- [SuperApi](https://github.com/TmmTop/SuperApi): 快速将你的 idea 变成线上稳定运行的产品! 无实体建库建表,对无实体库表进行增删改查,支持 15 种条件查询,以及分页,列表,无限级树形列表 等功能的 API 部署! 拥有接口文档Auth 授权,接口限流,获取客户端真实 IP先进的服务器缓存组件动态 API 等功能,期待您的体验! - [SuperApi](https://github.com/TmmTop/SuperApi): 快速将你的 idea 变成线上稳定运行的产品! 无实体建库建表,对无实体库表进行增删改查,支持 15 种条件查询,以及分页,列表,无限级树形列表 等功能的 API 部署! 拥有接口文档Auth 授权,接口限流,获取客户端真实 IP先进的服务器缓存组件动态 API 等功能,期待您的体验!
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): 基于 FastAPI+Vue3+Naive UI 的现代化轻量管理平台. - [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

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

View File

@ -1,8 +1,8 @@
{ {
"name": "soybean-admin", "name": "soybean-admin",
"type": "module", "type": "module",
"version": "1.3.13", "version": "1.3.14",
"description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。", "description": "A fresh and elegant admin template, based on Vue3、Vite6、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite6、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
"author": { "author": {
"name": "Soybean", "name": "Soybean",
"email": "soybeanjs@outlook.com", "email": "soybeanjs@outlook.com",
@ -19,7 +19,7 @@
"keywords": [ "keywords": [
"Vue3 admin ", "Vue3 admin ",
"vue-admin-template", "vue-admin-template",
"Vite5", "Vite6",
"TypeScript", "TypeScript",
"naive-ui", "naive-ui",
"naive-ui-admin", "naive-ui-admin",
@ -48,93 +48,90 @@
}, },
"dependencies": { "dependencies": {
"@antv/data-set": "0.11.8", "@antv/data-set": "0.11.8",
"@antv/g2": "5.2.12", "@antv/g2": "5.3.3",
"@antv/g6": "5.0.44", "@antv/g6": "5.0.48",
"@better-scroll/core": "2.5.1", "@better-scroll/core": "2.5.1",
"@iconify/vue": "4.3.0", "@iconify/vue": "5.0.0",
"@sa/alova": "workspace:*", "@sa/alova": "workspace:*",
"@sa/axios": "workspace:*", "@sa/axios": "workspace:*",
"@sa/color": "workspace:*", "@sa/color": "workspace:*",
"@sa/hooks": "workspace:*", "@sa/hooks": "workspace:*",
"@sa/materials": "workspace:*", "@sa/materials": "workspace:*",
"@sa/utils": "workspace:*", "@sa/utils": "workspace:*",
"@visactor/vchart": "1.13.7", "@visactor/vchart": "1.13.11",
"@visactor/vchart-theme": "1.12.2", "@visactor/vchart-theme": "1.12.2",
"@visactor/vtable-editors": "1.17.2", "@visactor/vtable-editors": "1.18.5",
"@visactor/vtable-gantt": "1.17.2", "@visactor/vtable-gantt": "1.18.5",
"@visactor/vue-vtable": "1.17.2", "@visactor/vue-vtable": "1.18.5",
"@vueuse/components": "13.0.0", "@vueuse/components": "13.3.0",
"@vueuse/core": "13.0.0", "@vueuse/core": "13.3.0",
"clipboard": "2.0.11", "clipboard": "2.0.11",
"dayjs": "1.11.13", "dayjs": "1.11.13",
"defu": "6.1.4", "defu": "6.1.4",
"dhtmlx-gantt": "9.0.6", "dhtmlx-gantt": "9.0.11",
"dompurify": "3.2.4", "dompurify": "3.2.6",
"echarts": "5.6.0", "echarts": "5.6.0",
"jsbarcode": "3.11.6", "jsbarcode": "3.11.6",
"json5": "2.2.3", "json5": "2.2.3",
"naive-ui": "2.41.0", "naive-ui": "2.41.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "3.0.1", "pinia": "3.0.3",
"pinyin-pro": "3.26.0", "pinyin-pro": "3.26.0",
"print-js": "1.6.0", "print-js": "1.6.0",
"pro-naive-ui": "^2.1.2", "pro-naive-ui": "2.1.6",
"swiper": "11.2.5", "swiper": "11.2.8",
"tailwind-merge": "3.0.2", "tailwind-merge": "3.3.0",
"typeit": "8.8.7", "typeit": "8.8.7",
"vditor": "3.10.9", "vditor": "3.11.1",
"vue": "3.5.13", "vue": "3.5.16",
"vue-draggable-plus": "0.6.0", "vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.2", "vue-i18n": "11.1.5",
"vue-pdf-embed": "2.1.2", "vue-pdf-embed": "2.1.2",
"vue-router": "4.5.0", "vue-router": "4.5.1",
"wangeditor": "4.7.15", "wangeditor": "4.7.15",
"xgplayer": "3.0.21", "xgplayer": "3.0.22",
"xlsx": "0.18.5" "xlsx": "0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@amap/amap-jsapi-types": "0.0.15", "@amap/amap-jsapi-types": "0.0.15",
"@elegant-router/vue": "0.3.8", "@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.318", "@iconify/json": "2.2.347",
"@sa/scripts": "workspace:*", "@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*", "@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.6.0", "@soybeanjs/eslint-config": "1.6.1",
"@types/bmapgl": "0.0.7", "@types/bmapgl": "0.0.7",
"@types/node": "22.13.10", "@types/node": "22.15.30",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.0.0", "@unocss/eslint-config": "66.1.4",
"@unocss/preset-icons": "66.0.0", "@unocss/preset-icons": "66.1.4",
"@unocss/preset-uno": "66.0.0", "@unocss/preset-uno": "66.1.4",
"@unocss/transformer-directives": "66.0.0", "@unocss/transformer-directives": "66.1.4",
"@unocss/transformer-variant-group": "66.0.0", "@unocss/transformer-variant-group": "66.1.4",
"@unocss/vite": "66.0.0", "@unocss/vite": "66.1.4",
"@vitejs/plugin-vue": "5.2.3", "@vitejs/plugin-vue": "5.2.4",
"@vitejs/plugin-vue-jsx": "4.1.2", "@vitejs/plugin-vue-jsx": "4.2.0",
"consola": "3.4.2", "consola": "3.4.2",
"eslint": "9.22.0", "eslint": "9.28.0",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.2.0",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"lint-staged": "15.5.0", "lint-staged": "16.1.0",
"pro-naive-ui-resolver": "^1.0.2", "pro-naive-ui-resolver": "1.0.2",
"sass": "1.86.0", "sass": "1.89.1",
"simple-git-hooks": "2.11.1", "simple-git-hooks": "2.13.0",
"tsx": "4.19.3", "tsx": "4.19.4",
"typescript": "5.8.2", "typescript": "5.8.3",
"unplugin-icons": "22.1.0", "unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.4.1", "unplugin-vue-components": "28.7.0",
"vite": "6.2.2", "vite": "6.3.5",
"vite-plugin-progress": "0.0.7", "vite-plugin-progress": "0.0.7",
"vite-plugin-svg-icons": "2.0.1", "vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "7.7.2", "vite-plugin-vue-devtools": "7.7.6",
"vue-eslint-parser": "10.1.1", "vue-eslint-parser": "10.1.3",
"vue-tsc": "2.2.8" "vue-tsc": "2.2.10"
}, },
"simple-git-hooks": { "simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify", "commit-msg": "pnpm sa git-commit-verify",
"pre-commit": "pnpm typecheck && pnpm lint-staged" "pre-commit": "pnpm typecheck && pnpm lint && git diff --exit-code"
},
"lint-staged": {
"*": "eslint --fix"
}, },
"website": "https://admin.soybeanjs.cn" "website": "https://admin.soybeanjs.cn"
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -2,40 +2,59 @@ import { computed, onScopeDispose, ref } from 'vue';
import { useRafFn } from '@vueuse/core'; 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) { export default function useCountDown(initialSeconds: number) {
const FPS_PER_SECOND = 60; 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(() => remainingSeconds.value > 0);
const isCounting = computed(() => fps.value > 0);
const { pause, resume } = useRafFn( const { pause, resume } = useRafFn(
() => { ({ delta }) => {
if (fps.value > 0) { // delta: milliseconds elapsed since the last frame.
fps.value -= 1;
} else { // 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(); 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(); resume();
} }
/** Stops the countdown and resets the remaining time to 0. */
function stop() { function stop() {
fps.value = 0; remainingSeconds.value = 0;
pause(); pause();
} }
// Ensure the rAF loop is cleaned up when the component is unmounted.
onScopeDispose(() => { onScopeDispose(() => {
pause(); pause();
}); });

View File

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

View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "@sa/scripts", "name": "@sa/scripts",
"version": "1.3.13", "version": "1.3.14",
"bin": { "bin": {
"sa": "./bin.ts" "sa": "./bin.ts"
}, },
@ -14,14 +14,14 @@
}, },
"devDependencies": { "devDependencies": {
"@soybeanjs/changelog": "0.3.24", "@soybeanjs/changelog": "0.3.24",
"bumpp": "10.1.0", "bumpp": "10.1.1",
"c12": "3.0.2", "c12": "3.0.4",
"cac": "6.7.14", "cac": "6.7.14",
"consola": "3.4.2", "consola": "3.4.2",
"enquirer": "2.4.1", "enquirer": "2.4.1",
"execa": "9.5.2", "execa": "9.6.0",
"kolorist": "1.8.0", "kolorist": "1.8.0",
"npm-check-updates": "17.1.15", "npm-check-updates": "18.0.1",
"rimraf": "6.0.1" "rimraf": "6.0.1"
} }
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -130,6 +130,9 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')"> <SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
<NSwitch v-model:value="themeStore.header.multilingual.visible" /> <NSwitch v-model:value="themeStore.header.multilingual.visible" />
</SettingItem> </SettingItem>
<SettingItem key="10" :label="$t('theme.header.globalSearch.visible')">
<NSwitch v-model:value="themeStore.header.globalSearch.visible" />
</SettingItem>
</TransitionGroup> </TransitionGroup>
</template> </template>

View File

@ -112,6 +112,9 @@ const local: App.I18n.Schema = {
}, },
multilingual: { multilingual: {
visible: 'Display multilingual button' visible: 'Display multilingual button'
},
globalSearch: {
visible: 'Display GlobalSearch button'
} }
}, },
tab: { tab: {

View File

@ -112,6 +112,9 @@ const local: App.I18n.Schema = {
}, },
multilingual: { multilingual: {
visible: '显示多语言按钮' visible: '显示多语言按钮'
},
globalSearch: {
visible: '显示全局搜索按钮'
} }
}, },
tab: { tab: {

View File

@ -25,8 +25,8 @@ export function setupAppVersionNotification() {
const buildTime = await getHtmlBuildTime(); const buildTime = await getHtmlBuildTime();
// If build time hasn't changed, no update is needed // If failed to get build time or build time hasn't changed, no update is needed.
if (buildTime === BUILD_TIME) { if (!buildTime || buildTime === BUILD_TIME) {
return; return;
} }
@ -88,16 +88,22 @@ export function setupAppVersionNotification() {
} }
} }
async function getHtmlBuildTime() { async function getHtmlBuildTime(): Promise<string | null> {
const baseUrl = import.meta.env.VITE_BASE_URL || '/'; const baseUrl = import.meta.env.VITE_BASE_URL || '/';
try {
const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`); const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
const html = await res.text(); if (!res.ok) {
console.error('getHtmlBuildTime error:', res.status, res.statusText);
const match = html.match(/<meta name="buildTime" content="(.*)">/); return null;
}
const buildTime = match?.[1] || '';
const html = await res.text();
return buildTime; const match = html.match(/<meta name="buildTime" content="(.*)">/);
return match?.[1] || null;
} catch (error) {
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 */ /** Setup the iconify offline */
export function setupIconifyOffline() { export function setupIconifyOffline() {
@ -6,7 +6,5 @@ export function setupIconifyOffline() {
if (VITE_ICONIFY_URL) { if (VITE_ICONIFY_URL) {
addAPIProvider('', { resources: [VITE_ICONIFY_URL] }); addAPIProvider('', { resources: [VITE_ICONIFY_URL] });
disableCache('all');
} }
} }

View File

@ -2,8 +2,8 @@ import { createAlovaRequest } from '@sa/alova';
import { createAlovaMockAdapter } from '@sa/alova/mock'; import { createAlovaMockAdapter } from '@sa/alova/mock';
import adapterFetch from '@sa/alova/fetch'; import adapterFetch from '@sa/alova/fetch';
import { useAuthStore } from '@/store/modules/auth'; import { useAuthStore } from '@/store/modules/auth';
import { $t } from '@/locales';
import { getServiceBaseURL } from '@/utils/service'; import { getServiceBaseURL } from '@/utils/service';
import { $t } from '@/locales';
import featureUsers20241014 from '../mocks/feature-users-20241014'; import featureUsers20241014 from '../mocks/feature-users-20241014';
import { getAuthorization, handleRefreshToken, showErrorMsg } from './shared'; import { getAuthorization, handleRefreshToken, showErrorMsg } from './shared';
import type { RequestInstanceState } from './type'; import type { RequestInstanceState } from './type';

View File

@ -40,6 +40,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
/** Reset auth store */ /** Reset auth store */
async function resetStore() { async function resetStore() {
recordUserId();
clearAuthStorage(); clearAuthStorage();
authStore.$reset(); authStore.$reset();
@ -52,6 +54,41 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
routeStore.resetStore(); 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 * Login
* *
@ -68,7 +105,15 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const pass = await loginByToken(loginToken); const pass = await loginByToken(loginToken);
if (pass) { 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({ window.$notification?.success({
title: $t('page.login.common.loginSuccess'), 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); const removeTabIndex = tabs.value.findIndex(tab => tab.id === tabId);
if (removeTabIndex === -1) return; if (removeTabIndex === -1) return;
const removedTabRouteKey = tabs.value[removeTabIndex].routeKey;
const isRemoveActiveTab = activeTabId.value === tabId; 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); tabs.value.splice(removeTabIndex, 1);
// if current tab is removed, then switch to next tab
if (isRemoveActiveTab && nextTab) { if (isRemoveActiveTab && nextTab) {
await switchRouteByTab(nextTab); await switchRouteByTab(nextTab);
} }
// reset route cache if cache strategy is close
if (themeStore.resetCacheStrategy === 'close') {
routeStore.resetRouteCache(removedTabRouteKey);
}
} }
/** remove active tab */ /** remove active tab */
@ -131,9 +142,26 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
*/ */
async function clearTabs(excludes: string[] = []) { async function clearTabs(excludes: string[] = []) {
const remainTabIds = [...getFixedTabIds(tabs.value), ...excludes]; 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); 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); const updatedTabs = filterTabsByIds(removedTabsIds, tabs.value);
function update() { function update() {
@ -142,13 +170,21 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
if (!isRemoveActiveTab) { if (!isRemoveActiveTab) {
update(); 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; // After tabs are updated and route potentially switched, reset cache for removed tabs
for (const routeKey of routeKeysToReset) {
await switchRouteByTab(activeTab); routeStore.resetRouteCache(routeKey);
update(); }
} }
const { routerPushByKey } = useRouterPush(); const { routerPushByKey } = useRouterPush();

View File

@ -30,6 +30,9 @@ export const themeSettings: App.Theme.ThemeSetting = {
}, },
multilingual: { multilingual: {
visible: true visible: true
},
globalSearch: {
visible: true
} }
}, },
tab: { tab: {

View File

@ -58,6 +58,10 @@ declare namespace App {
/** Whether to show the multilingual */ /** Whether to show the multilingual */
visible: boolean; visible: boolean;
}; };
globalSearch: {
/** Whether to show the GlobalSearch */
visible: boolean;
};
}; };
/** Tab */ /** Tab */
tab: { tab: {
@ -377,6 +381,9 @@ declare namespace App {
multilingual: { multilingual: {
visible: string; visible: string;
}; };
globalSearch: {
visible: string;
};
}; };
tab: { tab: {
visible: string; visible: string;

View File

@ -14,7 +14,7 @@ declare namespace CommonType {
* @property value: The option value * @property value: The option value
* @property label: The option label * @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'; type YesOrNo = 'Y' | 'N';

View File

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

View File

@ -108,6 +108,8 @@ declare namespace Env {
readonly VITE_AUTOMATICALLY_DETECT_UPDATE?: CommonType.YesOrNo; readonly VITE_AUTOMATICALLY_DETECT_UPDATE?: CommonType.YesOrNo;
/** show proxy url log in terminal */ /** show proxy url log in terminal */
readonly VITE_PROXY_LOG?: CommonType.YesOrNo; 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]) => ({ return Object.entries(record).map(([value, label]) => ({
value, value,
label 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 * @param options
*/ */
export function translateOptions(options: CommonType.Option<string>[]) { export function translateOptions(options: CommonType.Option<string, App.I18n.I18nKey>[]) {
return options.map(option => ({ return options.map(option => ({
...option, ...option,
label: $t(option.label as App.I18n.I18nKey) label: $t(option.label)
})); }));
} }

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { $t } from '@/locales';
import pkg from '~/package.json'; import pkg from '~/package.json';
const appStore = useAppStore(); const appStore = useAppStore();

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { actionDelegationMiddleware, useAutoRequest } from '@sa/alova/client';
import { ref } from 'vue'; import { ref } from 'vue';
import { actionDelegationMiddleware, useAutoRequest } from '@sa/alova/client';
import { alova } from '@/service-alova/request'; import { alova } from '@/service-alova/request';
const getLastTime = alova.Get<{ time: string }>('/mock/getLastTime', { cacheFor: null }); const getLastTime = alova.Get<{ time: string }>('/mock/getLastTime', { cacheFor: null });

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { actionDelegationMiddleware, useCaptcha, useForm } from '@sa/alova/client'; import { actionDelegationMiddleware, useCaptcha, useForm } from '@sa/alova/client';
import { $t } from '@/locales';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
import { sendCaptcha, verifyCaptcha } from '@/service-alova/api'; import { sendCaptcha, verifyCaptcha } from '@/service-alova/api';
defineOptions({ defineOptions({

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { actionDelegationMiddleware, useAutoRequest } from '@sa/alova/client';
import { ref } from 'vue'; import { ref } from 'vue';
import { actionDelegationMiddleware, useAutoRequest } from '@sa/alova/client';
import { alova } from '@/service-alova/request'; import { alova } from '@/service-alova/request';
const getLastTime = alova.Get<{ time: string }>('/mock/getLastTime', { cacheFor: null }); const getLastTime = alova.Get<{ time: string }>('/mock/getLastTime', { cacheFor: null });

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { actionDelegationMiddleware, useAutoRequest } from '@sa/alova/client';
import { ref } from 'vue'; import { ref } from 'vue';
import { actionDelegationMiddleware, useAutoRequest } from '@sa/alova/client';
import { alova } from '@/service-alova/request'; import { alova } from '@/service-alova/request';
const getLastTime = alova.Get<{ time: string }>('/mock/getLastTime', { cacheFor: null }); const getLastTime = alova.Get<{ time: string }>('/mock/getLastTime', { cacheFor: null });

View File

@ -1,6 +1,6 @@
import type { TableColumnCheck } from '@sa/hooks';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import type { DataTableBaseColumn, DataTableColumn } from 'naive-ui'; import type { DataTableBaseColumn, DataTableColumn } from 'naive-ui';
import type { TableColumnCheck } from '@sa/hooks';
import { $t } from '@/locales'; import { $t } from '@/locales';
import type { AlovaGenerics, Method } from '~/packages/alova/src'; import type { AlovaGenerics, Method } from '~/packages/alova/src';

View File

@ -1,6 +1,6 @@
import { useBoolean } from '@sa/hooks';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { useBoolean } from '@sa/hooks';
import { jsonClone } from '@sa/utils'; import { jsonClone } from '@sa/utils';
import { $t } from '@/locales'; import { $t } from '@/locales';

View File

@ -1,11 +1,11 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { reactive } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui'; import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { usePagination } from '@sa/alova/client'; import { usePagination } from '@sa/alova/client';
import { reactive } from 'vue'; import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { useAppStore } from '@/store/modules/app';
import { batchDeleteUser, deleteUser, fetchGetUserList } from '@/service-alova/api'; import { batchDeleteUser, deleteUser, fetchGetUserList } from '@/service-alova/api';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import useCheckedColumns from './hooks/use-checked-columns'; import useCheckedColumns from './hooks/use-checked-columns';
import useTableOperate from './hooks/use-table-operate'; import useTableOperate from './hooks/use-table-operate';
import UserOperateDrawer from './modules/user-operate-drawer.vue'; import UserOperateDrawer from './modules/user-operate-drawer.vue';

View File

@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, watch } from 'vue'; import { computed, watch } from 'vue';
import { useForm, useWatcher } from '@sa/alova/client'; import { useForm, useWatcher } from '@sa/alova/client';
import { enableStatusOptions, userGenderOptions } from '@/constants/business';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import type { UserModel } from '@/service-alova/api'; import type { UserModel } from '@/service-alova/api';
import { addUser, fetchGetAllRoles, updateUser } from '@/service-alova/api'; import { addUser, fetchGetAllRoles, updateUser } from '@/service-alova/api';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { enableStatusOptions, userGenderOptions } from '@/constants/business';
defineOptions({ defineOptions({
name: 'UserOperateDrawer' name: 'UserOperateDrawer'

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { $t } from '@/locales';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { enableStatusOptions, userGenderOptions } from '@/constants/business'; import { enableStatusOptions, userGenderOptions } from '@/constants/business';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { translateOptions } from '@/utils/common'; import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'UserSearch' name: 'UserSearch'

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute } from 'vue-router';
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales'; import { $t } from '@/locales';

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { $t } from '@/locales';
import { fetchCustomBackendError } from '@/service/api'; import { fetchCustomBackendError } from '@/service/api';
import { $t } from '@/locales';
async function logout() { async function logout() {
await fetchCustomBackendError('8888', $t('request.logoutMsg')); await fetchCustomBackendError('8888', $t('request.logoutMsg'));

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useTabStore } from '@/store/modules/tab';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { useTabStore } from '@/store/modules/tab';
const tabStore = useTabStore(); const tabStore = useTabStore();
const { routerPushByKey } = useRouterPush(); const { routerPushByKey } = useRouterPush();

View File

@ -3,12 +3,12 @@ import { ref } from 'vue';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import { NButton, NPopconfirm, NTag } from 'naive-ui'; import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { useBoolean } from '@sa/hooks'; import { useBoolean } from '@sa/hooks';
import { yesOrNoRecord } from '@/constants/common';
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
import { fetchGetAllPages, fetchGetMenuList } from '@/service/api'; import { fetchGetAllPages, fetchGetMenuList } from '@/service/api';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table'; import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { yesOrNoRecord } from '@/constants/common';
import { enableStatusRecord, menuTypeRecord } from '@/constants/business';
import SvgIcon from '@/components/custom/svg-icon.vue'; import SvgIcon from '@/components/custom/svg-icon.vue';
import MenuOperateModal, { type OperateType } from './modules/menu-operate-modal.vue'; import MenuOperateModal, { type OperateType } from './modules/menu-operate-modal.vue';

View File

@ -1,12 +1,12 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui'; 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 { 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 { fetchGetAllRoles } from '@/service/api';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { getLocalIcons } from '@/utils/icon';
import { $t } from '@/locales';
import SvgIcon from '@/components/custom/svg-icon.vue';
import { import {
getLayoutAndPage, getLayoutAndPage,
getPathParamFromRoutePath, getPathParamFromRoutePath,

View File

@ -1,10 +1,10 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui'; import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { enableStatusRecord } from '@/constants/business';
import { fetchGetRoleList } from '@/service/api'; import { fetchGetRoleList } from '@/service/api';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table'; import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { enableStatusRecord } from '@/constants/business';
import RoleOperateDrawer from './modules/role-operate-drawer.vue'; import RoleOperateDrawer from './modules/role-operate-drawer.vue';
import RoleSearch from './modules/role-search.vue'; import RoleSearch from './modules/role-search.vue';

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, shallowRef, watch } from 'vue'; import { computed, shallowRef, watch } from 'vue';
import { $t } from '@/locales';
import { fetchGetAllPages, fetchGetMenuTree } from '@/service/api'; import { fetchGetAllPages, fetchGetMenuTree } from '@/service/api';
import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'MenuAuthModal' name: 'MenuAuthModal'

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { useBoolean } from '@sa/hooks'; import { useBoolean } from '@sa/hooks';
import { enableStatusOptions } from '@/constants/business';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { enableStatusOptions } from '@/constants/business';
import MenuAuthModal from './menu-auth-modal.vue'; import MenuAuthModal from './menu-auth-modal.vue';
import ButtonAuthModal from './button-auth-modal.vue'; import ButtonAuthModal from './button-auth-modal.vue';

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { $t } from '@/locales';
import { enableStatusOptions } from '@/constants/business'; import { enableStatusOptions } from '@/constants/business';
import { translateOptions } from '@/utils/common'; import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'RoleSearch' name: 'RoleSearch'

View File

@ -1,10 +1,10 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui'; import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { fetchGetUserList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { enableStatusRecord, userGenderRecord } from '@/constants/business'; import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { fetchGetUserList } from '@/service/api';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table'; import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import UserOperateDrawer from './modules/user-operate-drawer.vue'; import UserOperateDrawer from './modules/user-operate-drawer.vue';
import UserSearch from './modules/user-search.vue'; import UserSearch from './modules/user-search.vue';

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { fetchGetAllRoles } from '@/service/api';
import { $t } from '@/locales';
import { enableStatusOptions, userGenderOptions } from '@/constants/business'; import { enableStatusOptions, userGenderOptions } from '@/constants/business';
import { fetchGetAllRoles } from '@/service/api';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'UserOperateDrawer' name: 'UserOperateDrawer'

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import { $t } from '@/locales';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { enableStatusOptions, userGenderOptions } from '@/constants/business'; import { enableStatusOptions, userGenderOptions } from '@/constants/business';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { translateOptions } from '@/utils/common'; import { translateOptions } from '@/utils/common';
import { $t } from '@/locales';
defineOptions({ defineOptions({
name: 'UserSearch' name: 'UserSearch'

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted } from 'vue';
import JsBarcode from 'jsbarcode'; import JsBarcode from 'jsbarcode';
import type { Options } from 'jsbarcode'; import type { Options } from 'jsbarcode';
import { onMounted } from 'vue';
const text = 'Soybean'; const text = 'Soybean';

View File

@ -1,10 +1,10 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NButton, NTag } from 'naive-ui'; import { NButton, NTag } from 'naive-ui';
import { utils, writeFile } from 'xlsx'; import { utils, writeFile } from 'xlsx';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { fetchGetUserList } from '@/service/api';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table'; import { useTable } from '@/hooks/common/table';
import { fetchGetUserList } from '@/service/api';
import { enableStatusRecord, userGenderRecord } from '@/constants/business';
import { $t } from '@/locales'; import { $t } from '@/locales';
const appStore = useAppStore(); const appStore = useAppStore();