Merge remote-tracking branch 'soybeanjs/main' into ruoyi
# Conflicts: # CHANGELOG.md # README.md # package.json # pnpm-lock.yaml # src/plugins/loading.ts # src/store/modules/auth/index.ts
This commit is contained in:
commit
849e63b192
2
.env
2
.env
@ -1,3 +1,5 @@
|
||||
# the base url of the application, the default is "/"
|
||||
# if use a sub directory, it must be end with "/", like "/admin/" but not "/admin"
|
||||
VITE_BASE_URL=/
|
||||
|
||||
VITE_APP_TITLE=RuoYi-Vue-Plus
|
||||
|
184
README.en_US.md
Normal file
184
README.en_US.md
Normal file
@ -0,0 +1,184 @@
|
||||
<div align="center">
|
||||
<img src="./public/favicon.svg" width="160" />
|
||||
<h1>SoybeanAdmin</h1>
|
||||
<span><a href="./README.md">中文</a> | English</span>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
[](./LICENSE)
|
||||
[](https://github.com/soybeanjs/soybean-admin)
|
||||
[](https://github.com/soybeanjs/soybean-admin)
|
||||
[](https://gitee.com/honghuangdc/soybean-admin)
|
||||
|
||||
<a href="https://hellogithub.com/repository/1298f27d5fe54959a16cf9686516ddb3" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=1298f27d5fe54959a16cf9686516ddb3&claim_uid=IiDXWmP4TEntjbV" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
> If you think `SoybeanAdmin` is helpful to you, or you like our project, please give us a ⭐️ on GitHub. Your support is the driving force for us to continue to improve and add new features! Thank you for your support!
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **Cutting-edge technology application**: using the latest popular technology stack such as Vue3, Vite5, 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.
|
||||
- **Rich theme configuration**: built-in a variety of theme configurations, perfectly integrated with UnoCSS.
|
||||
- **Built-in internationalization solution**: easily realize multi-language support.
|
||||
- **Automated file routing system**: automatically generate route import, declaration and type. For more details, please refer to [Elegant Router](https://github.com/soybeanjs/elegant-router).
|
||||
- **Flexible permission routing**: support both front-end static routing and back-end dynamic routing.
|
||||
- **Rich page components**: built-in a variety of pages and components, including 403, 404, 500 pages, as well as layout components, tag components, theme configuration components, etc.
|
||||
- **Command line tool**: built-in efficient command line tool, git commit, delete file, release, etc.
|
||||
- **Mobile adaptation**: perfectly support mobile terminal to realize adaptive layout.
|
||||
|
||||
|
||||
## Version
|
||||
|
||||
- **NaiveUI Version:**
|
||||
- [Preview Link](https://naive.soybeanjs.cn/)
|
||||
- [Github Repository](https://github.com/soybeanjs/soybean-admin)
|
||||
- [Gitee Repository](https://gitee.com/honghuangdc/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)
|
||||
|
||||
- **ElementPlus Version:**
|
||||
- [Preview Link](https://elp.soybeanjs.cn/)
|
||||
- [Github Repository](https://github.com/soybeanjs/soybean-admin-element-plus)
|
||||
|
||||
- **Legacy Version:**
|
||||
- [Preview Link](https://legacy.soybeanjs.cn/)
|
||||
- [Github Repository](https://github.com/soybeanjs/soybean-admin/tree/legacy)
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Link](https://docs.soybeanjs.cn)
|
||||
- [Legacy Docs](https://legacy-docs.soybeanjs.cn)
|
||||
|
||||
## Example Images
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
**Environment Preparation**
|
||||
|
||||
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.
|
||||
|
||||
**Clone Project**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/soybeanjs/soybean-admin.git
|
||||
```
|
||||
|
||||
**Install Dependencies**
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
> Since this project uses the pnpm monorepo management method, please do not use npm or yarn to install dependencies.
|
||||
|
||||
**Start Project**
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
**Build Project**
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
**Code Synchronization**
|
||||
|
||||
Refer to the [Code Synchronization](https://docs.soybeanjs.cn/guide/sync) document.
|
||||
|
||||
## Ecosystem
|
||||
|
||||
- [react-soybean-admin](https://github.com/mufeng889/react-soybean-admin): SoybeanAdmin based version of React.
|
||||
- [electron-mock-admin](https://github.com/lixin59/electron-mock-api): A Mock Api management system that helps front-end developers quickly implement interface mocks.
|
||||
- [T-Shell](https://github.com/TheBlindM/T-Shell): A terminal emulator and SSH client with configurable command prompts.
|
||||
- [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin).
|
||||
- [MalusAdmin](https://github.com/pridejoy/MalusAdmin): A backend management framework developed based on Vue3/TypeScript/NaiveUI and NET7 & Sqlsugar. It is implemented in the most original and simplest way, with a fresh and elegant front-end, a clear and elegant backend structure, and powerful functions.
|
||||
- [PanisAdmin](https://github.com/paynezhuang/panis-admin): Adopting SpringBoot 3, SaToken, MySQL and other frameworks to develop and modify [soybean-admin](https://github.com/soybeanjs/soybean-admin) for the second time, adapting dynamic menu/button-level authorization. Retaining the original flavor, fresh and elegant, high-value back-end management system scaffold.
|
||||
- [snail-job](https://github.com/aizuda/snail-job): A distributed task retry and task scheduling platform with "high performance, high value and high activity".
|
||||
- [SuperApi](https://github.com/TmmTop/SuperApi): Quickly turn your idea into an online stable product! Entity-less library and table building, add, delete, change and check entity-less library table, support 15 kinds of condition query, as well as paging, list, unlimited tree list and other functions of the API deployment! With interface documentation, Auth authorisation, interface flow restriction, access to the client's real IP, advanced server caching components, dynamic APIs and other features, we look forward to your experience!
|
||||
- [FastSoyAdmin](https://github.com/sleep1223/fast-soy-admin): A modern Management Platform based on FastAPI+Vue3+Naive UI.
|
||||
|
||||
|
||||
## How to Contribute
|
||||
|
||||
We warmly welcome and appreciate all forms of contributions. If you have any ideas or suggestions, please feel free to share them by submitting [pull requests](https://github.com/soybeanjs/soybean-admin/pulls) or creating GitHub [issue](https://github.com/soybeanjs/soybean-admin/issues/new).
|
||||
|
||||
## Git Commit Guidelines
|
||||
|
||||
This project has built-in `commit` command, you can execute `pnpm commit` to generate commit information that conforms to [Conventional Commits](https://www.conventionalcommits.org/) specification. When submitting PR, please be sure to use `commit` command to create commit information to ensure the standardization of information.
|
||||
|
||||
## Browser Support
|
||||
|
||||
It is recommended to use the latest version of Chrome in development for a better experience.
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## OpenSource Author
|
||||
|
||||
[Soybean](https://github.com/honghuangdc)
|
||||
|
||||
## Contributors
|
||||
|
||||
Thanks the following people for their contributions. If you want to contribute to this project, please refer to [How to Contribute](#how-to-contribute).
|
||||
|
||||
<a href="https://github.com/soybeanjs/soybean-admin/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=soybeanjs/soybean-admin" />
|
||||
</a>
|
||||
|
||||
## Communication
|
||||
|
||||
`SoybeanAdmin` is a completely open source and free project, helping developers to develop medium and large-scale management systems more conveniently. It also provides WeChat and QQ communication groups. If you have any questions, please feel free to ask in the group.
|
||||
|
||||
<div>
|
||||
<p>QQ Group</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-3.jpg" style="width:200px" />
|
||||
</div>
|
||||
<!-- <div>
|
||||
<p>WeChat Group</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/picgo/soybean-admin-wechat-0620.jpg" style="width:200px" />
|
||||
</div> -->
|
||||
<div>
|
||||
<p>Add the following WeChat to invite to the WeChat group</p>
|
||||
<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" />
|
||||
</div>
|
||||
|
||||
## Star Trend
|
||||
|
||||
[](https://star-history.com/#soybeanjs/soybean-admin&Date)
|
||||
|
||||
## License
|
||||
|
||||
This project is based on the [MIT © 2021 Soybean](./LICENSE) protocol, for learning purposes only, please retain the author's copyright information for commercial use, the author does not guarantee and is not responsible for the software.
|
@ -4,7 +4,7 @@ import type { PluginOption } from 'vite';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import IconsResolver from 'unplugin-icons/resolver';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { AntDesignVueResolver, NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
|
||||
|
||||
@ -31,9 +31,6 @@ export function setupUnplugin(viteEnv: Env.ImportMeta) {
|
||||
dts: 'src/typings/components.d.ts',
|
||||
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
|
||||
resolvers: [
|
||||
AntDesignVueResolver({
|
||||
importStyle: false
|
||||
}),
|
||||
NaiveUiResolver(),
|
||||
IconsResolver({ customCollections: [collectionName], componentPrefix: VITE_ICON_PREFIX })
|
||||
]
|
||||
|
72
package.json
72
package.json
@ -1,8 +1,13 @@
|
||||
{
|
||||
"name": "ruoyi-vue-plus",
|
||||
"type": "module",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"description": "RuoYi-Vue-Plus多租户管理系统",
|
||||
"author": {
|
||||
"name": "xlsea",
|
||||
"email": "xlsea@linux.do",
|
||||
"url": "https://gitee.com/xlsea/ruoyi-plus-soybean"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://gitee.com/xlsea/ruoyi-plus-soybean",
|
||||
"keywords": [
|
||||
@ -39,60 +44,61 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-scroll/core": "2.5.1",
|
||||
"@iconify/vue": "4.1.2",
|
||||
"@iconify/vue": "4.3.0",
|
||||
"@sa/axios": "workspace:*",
|
||||
"@sa/color": "workspace:*",
|
||||
"@sa/hooks": "workspace:*",
|
||||
"@sa/materials": "workspace:*",
|
||||
"@sa/utils": "workspace:*",
|
||||
"@vueuse/core": "11.1.0",
|
||||
"@vueuse/core": "12.5.0",
|
||||
"clipboard": "2.0.11",
|
||||
"dayjs": "1.11.13",
|
||||
"echarts": "5.5.1",
|
||||
"defu": "6.1.4",
|
||||
"echarts": "5.6.0",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"json5": "2.2.3",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"naive-ui": "2.40.1",
|
||||
"naive-ui": "2.41.0",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.2.4",
|
||||
"tailwind-merge": "2.5.4",
|
||||
"vue": "3.5.12",
|
||||
"vue-draggable-plus": "0.5.4",
|
||||
"vue-i18n": "10.0.4",
|
||||
"vue-router": "4.4.5"
|
||||
"pinia": "3.0.0",
|
||||
"tailwind-merge": "3.0.1",
|
||||
"vue": "3.5.13",
|
||||
"vue-draggable-plus": "0.6.0",
|
||||
"vue-i18n": "11.1.1",
|
||||
"vue-router": "4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@elegant-router/vue": "0.3.8",
|
||||
"@iconify/json": "2.2.263",
|
||||
"@iconify/json": "2.2.305",
|
||||
"@sa/scripts": "workspace:*",
|
||||
"@sa/uno-preset": "workspace:*",
|
||||
"@soybeanjs/eslint-config": "1.4.2",
|
||||
"@types/node": "22.7.9",
|
||||
"@soybeanjs/eslint-config": "1.4.4",
|
||||
"@types/node": "22.13.1",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@unocss/eslint-config": "0.63.6",
|
||||
"@unocss/preset-icons": "0.63.6",
|
||||
"@unocss/preset-uno": "0.63.6",
|
||||
"@unocss/transformer-directives": "0.63.6",
|
||||
"@unocss/transformer-variant-group": "0.63.6",
|
||||
"@unocss/vite": "0.63.6",
|
||||
"@vitejs/plugin-vue": "5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "4.0.1",
|
||||
"eslint": "9.13.0",
|
||||
"eslint-plugin-vue": "9.29.1",
|
||||
"lint-staged": "15.2.10",
|
||||
"sass": "1.80.4",
|
||||
"@unocss/eslint-config": "65.4.3",
|
||||
"@unocss/preset-icons": "65.4.3",
|
||||
"@unocss/preset-uno": "65.4.3",
|
||||
"@unocss/transformer-directives": "65.4.3",
|
||||
"@unocss/transformer-variant-group": "65.4.3",
|
||||
"@unocss/vite": "65.4.3",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "4.1.1",
|
||||
"eslint": "9.20.0",
|
||||
"eslint-plugin-vue": "9.32.0",
|
||||
"lint-staged": "15.4.3",
|
||||
"sass": "1.84.0",
|
||||
"simple-git-hooks": "2.11.1",
|
||||
"tsx": "4.19.1",
|
||||
"typescript": "5.6.3",
|
||||
"unplugin-icons": "0.19.3",
|
||||
"unplugin-vue-components": "0.27.4",
|
||||
"vite": "5.4.10",
|
||||
"tsx": "4.19.2",
|
||||
"typescript": "5.7.3",
|
||||
"unplugin-icons": "22.0.0",
|
||||
"unplugin-vue-components": "28.0.0",
|
||||
"vite": "6.1.0",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-devtools": "7.5.4",
|
||||
"vite-plugin-vue-devtools": "7.7.1",
|
||||
"vue-eslint-parser": "9.4.3",
|
||||
"vue-tsc": "2.1.6"
|
||||
"vue-tsc": "2.2.0"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"commit-msg": "pnpm sa git-commit-verify",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/alova",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./fetch": "./src/fetch.ts",
|
||||
@ -13,8 +13,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/mock": "2.0.8",
|
||||
"@alova/mock": "2.0.11",
|
||||
"@sa/utils": "workspace:*",
|
||||
"alova": "3.1.1"
|
||||
"alova": "3.2.8"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/axios",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
@ -11,11 +11,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"axios": "1.7.7",
|
||||
"axios": "1.7.9",
|
||||
"axios-retry": "4.5.0",
|
||||
"qs": "6.13.0"
|
||||
"qs": "6.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qs": "6.9.16"
|
||||
"@types/qs": "6.9.18"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/color",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/hooks",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/materials",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"simplebar-vue": "2.3.5"
|
||||
"simplebar-vue": "2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typed-css-modules": "0.9.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/scripts",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"bin": {
|
||||
"sa": "./bin.ts"
|
||||
},
|
||||
@ -14,14 +14,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@soybeanjs/changelog": "0.3.24",
|
||||
"bumpp": "9.7.1",
|
||||
"c12": "2.0.1",
|
||||
"bumpp": "10.0.3",
|
||||
"c12": "2.0.2",
|
||||
"cac": "6.7.14",
|
||||
"consola": "3.2.3",
|
||||
"consola": "3.4.0",
|
||||
"enquirer": "2.4.1",
|
||||
"execa": "9.4.1",
|
||||
"execa": "9.5.2",
|
||||
"kolorist": "1.8.0",
|
||||
"npm-check-updates": "17.1.4",
|
||||
"npm-check-updates": "17.1.14",
|
||||
"rimraf": "6.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/uno-preset",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sa/utils",
|
||||
"version": "1.3.8",
|
||||
"version": "1.3.11",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
@ -14,7 +14,7 @@
|
||||
"crypto-js": "4.2.0",
|
||||
"klona": "2.0.6",
|
||||
"localforage": "1.10.0",
|
||||
"nanoid": "5.0.7"
|
||||
"nanoid": "5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "4.2.2"
|
||||
|
4445
pnpm-lock.yaml
4445
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ const naiveDateLocale = computed(() => {
|
||||
|
||||
const watermarkProps = computed<WatermarkProps>(() => {
|
||||
return {
|
||||
content: themeStore.watermark?.text || 'SoybeanAdmin',
|
||||
content: themeStore.watermark.text,
|
||||
cross: true,
|
||||
fullscreen: true,
|
||||
fontSize: 16,
|
||||
@ -50,7 +50,7 @@ const watermarkProps = computed<WatermarkProps>(() => {
|
||||
>
|
||||
<AppProvider>
|
||||
<RouterView class="bg-layout" />
|
||||
<NWatermark v-if="themeStore.watermark?.visible" v-bind="watermarkProps" />
|
||||
<NWatermark v-if="themeStore.watermark.visible" v-bind="watermarkProps" />
|
||||
</AppProvider>
|
||||
</NConfigProvider>
|
||||
</template>
|
||||
|
@ -61,3 +61,5 @@ export const resetCacheStrategyRecord: Record<UnionKey.ResetCacheStrategy, App.I
|
||||
};
|
||||
|
||||
export const resetCacheStrategyOptions = transformRecordToOption(resetCacheStrategyRecord);
|
||||
|
||||
export const DARK_CLASS = 'dark';
|
||||
|
@ -102,9 +102,9 @@ export function useRouterPush(inSetup = true) {
|
||||
const redirect = route.value.query?.redirect as string;
|
||||
|
||||
if (needRedirect && redirect) {
|
||||
routerPush(redirect);
|
||||
await routerPush(redirect);
|
||||
} else {
|
||||
toHome();
|
||||
await toHome();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,6 +122,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
showSizePicker: true,
|
||||
itemCount: 0,
|
||||
pageSizes: [10, 15, 20, 25, 30],
|
||||
onUpdatePage: async (page: number) => {
|
||||
pagination.page = page;
|
||||
|
@ -40,7 +40,12 @@ const { isFullscreen, toggle } = useFullscreen();
|
||||
<div class="h-full flex-y-center justify-end">
|
||||
<GlobalSearch />
|
||||
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
|
||||
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
|
||||
<LangSwitch
|
||||
v-if="themeStore.header.multilingual.visible"
|
||||
:lang="appStore.locale"
|
||||
:lang-options="appStore.localeOptions"
|
||||
@change-lang="appStore.changeLocale"
|
||||
/>
|
||||
<ThemeSchemaSwitch
|
||||
:theme-schema="themeStore.themeScheme"
|
||||
:is-dark="themeStore.darkMode"
|
||||
|
@ -186,7 +186,7 @@ init();
|
||||
:active="tab.id === tabStore.activeTabId"
|
||||
:active-color="themeStore.themeColor"
|
||||
:closable="!tabStore.isTabRetain(tab.id)"
|
||||
@click="tabStore.switchRouteByTab(tab)"
|
||||
@pointerdown="tabStore.switchRouteByTab(tab)"
|
||||
@close="handleCloseTab(tab)"
|
||||
@contextmenu="handleContextMenu($event, tab.id)"
|
||||
>
|
||||
|
@ -114,10 +114,10 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
|
||||
>
|
||||
<NSwitch v-model:value="themeStore.footer.right" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.watermark" key="8" :label="$t('theme.watermark.visible')">
|
||||
<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.text')">
|
||||
<NInput
|
||||
v-model:value="themeStore.watermark.text"
|
||||
autosize
|
||||
@ -127,6 +127,9 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
|
||||
placeholder="SoybeanAdmin"
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem key="9" :label="$t('theme.header.multilingual.visible')">
|
||||
<NSwitch v-model:value="themeStore.header.multilingual.visible" />
|
||||
</SettingItem>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
|
||||
|
@ -111,6 +111,9 @@ const local: App.I18n.Schema = {
|
||||
breadcrumb: {
|
||||
visible: 'Breadcrumb Visible',
|
||||
showIcon: 'Breadcrumb Icon Visible'
|
||||
},
|
||||
multilingual: {
|
||||
visible: 'Display multilingual button'
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
|
@ -111,6 +111,9 @@ const local: App.I18n.Schema = {
|
||||
breadcrumb: {
|
||||
visible: '显示面包屑',
|
||||
showIcon: '显示面包屑图标'
|
||||
},
|
||||
multilingual: {
|
||||
visible: '显示多语言按钮'
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
|
@ -11,25 +11,28 @@ export function setupAppErrorHandle(app: App) {
|
||||
}
|
||||
|
||||
export function setupAppVersionNotification() {
|
||||
const canAutoUpdateApp = import.meta.env.VITE_AUTOMATICALLY_DETECT_UPDATE === 'Y';
|
||||
// Update check interval in milliseconds
|
||||
const UPDATE_CHECK_INTERVAL = 3 * 60 * 1000;
|
||||
|
||||
const canAutoUpdateApp = import.meta.env.VITE_AUTOMATICALLY_DETECT_UPDATE === 'Y' && import.meta.env.PROD;
|
||||
if (!canAutoUpdateApp) return;
|
||||
|
||||
let isShow = false;
|
||||
let updateInterval: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
document.addEventListener('visibilitychange', async () => {
|
||||
const preConditions = [!isShow, document.visibilityState === 'visible', !import.meta.env.DEV];
|
||||
|
||||
if (!preConditions.every(Boolean)) return;
|
||||
const checkForUpdates = async () => {
|
||||
if (isShow) return;
|
||||
|
||||
const buildTime = await getHtmlBuildTime();
|
||||
|
||||
// If build time hasn't changed, no update is needed
|
||||
if (buildTime === BUILD_TIME) {
|
||||
return;
|
||||
}
|
||||
|
||||
isShow = true;
|
||||
|
||||
// Show update notification
|
||||
const n = window.$notification?.create({
|
||||
title: $t('system.updateTitle'),
|
||||
content: $t('system.updateContent'),
|
||||
@ -40,6 +43,7 @@ export function setupAppVersionNotification() {
|
||||
{
|
||||
onClick() {
|
||||
n?.destroy();
|
||||
isShow = false;
|
||||
}
|
||||
},
|
||||
() => $t('system.updateCancel')
|
||||
@ -60,11 +64,34 @@ export function setupAppVersionNotification() {
|
||||
isShow = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const startUpdateInterval = () => {
|
||||
if (updateInterval) {
|
||||
clearInterval(updateInterval);
|
||||
}
|
||||
updateInterval = setInterval(checkForUpdates, UPDATE_CHECK_INTERVAL);
|
||||
};
|
||||
|
||||
// If updates should be checked, set up the visibility change listener and start the update interval
|
||||
if (!isShow && document.visibilityState === 'visible') {
|
||||
// Check for updates when the document is visible
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
checkForUpdates();
|
||||
startUpdateInterval();
|
||||
}
|
||||
});
|
||||
|
||||
// Start the update interval
|
||||
startUpdateInterval();
|
||||
}
|
||||
}
|
||||
|
||||
async function getHtmlBuildTime() {
|
||||
const res = await fetch(`/index.html?time=${Date.now()}`);
|
||||
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
|
||||
|
||||
const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
|
||||
|
||||
const html = await res.text();
|
||||
|
||||
|
@ -36,54 +36,34 @@ export function createRouteGuard(router: Router) {
|
||||
const routeRoles = to.meta.roles || [];
|
||||
|
||||
const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
|
||||
|
||||
const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;
|
||||
|
||||
const routeSwitches: CommonType.StrategicPattern[] = [
|
||||
// if it is login route when logged in, then switch to the root page
|
||||
{
|
||||
condition: isLogin && to.name === loginRoute,
|
||||
callback: () => {
|
||||
next({ name: rootRoute });
|
||||
}
|
||||
},
|
||||
// if it is constant route, then it is allowed to access directly
|
||||
{
|
||||
condition: !needLogin,
|
||||
callback: () => {
|
||||
handleRouteSwitch(to, from, next);
|
||||
}
|
||||
},
|
||||
// if the route need login but the user is not logged in, then switch to the login page
|
||||
{
|
||||
condition: !isLogin && needLogin,
|
||||
callback: () => {
|
||||
next({ name: loginRoute, query: { redirect: to.fullPath } });
|
||||
}
|
||||
},
|
||||
// if the user is logged in and has authorization, then it is allowed to access
|
||||
{
|
||||
condition: isLogin && needLogin && hasAuth,
|
||||
callback: () => {
|
||||
handleRouteSwitch(to, from, next);
|
||||
}
|
||||
},
|
||||
// if the user is logged in but does not have authorization, then switch to the 403 page
|
||||
{
|
||||
condition: isLogin && needLogin && !hasAuth,
|
||||
callback: () => {
|
||||
next({ name: noAuthorizationRoute });
|
||||
}
|
||||
}
|
||||
];
|
||||
// if it is login route when logged in, then switch to the root page
|
||||
if (to.name === loginRoute && isLogin) {
|
||||
next({ name: rootRoute });
|
||||
return;
|
||||
}
|
||||
|
||||
routeSwitches.some(({ condition, callback }) => {
|
||||
if (condition) {
|
||||
callback();
|
||||
}
|
||||
// if the route does not need login, then it is allowed to access directly
|
||||
if (!needLogin) {
|
||||
handleRouteSwitch(to, from, next);
|
||||
return;
|
||||
}
|
||||
|
||||
return condition;
|
||||
});
|
||||
// the route need login but the user is not logged in, then switch to the login page
|
||||
if (!isLogin) {
|
||||
next({ name: loginRoute, query: { redirect: to.fullPath } });
|
||||
return;
|
||||
}
|
||||
|
||||
// if the user is logged in but does not have authorization, then switch to the 403 page
|
||||
if (!hasAuth) {
|
||||
next({ name: noAuthorizationRoute });
|
||||
return;
|
||||
}
|
||||
|
||||
// switch route normally
|
||||
handleRouteSwitch(to, from, next);
|
||||
});
|
||||
}
|
||||
|
||||
@ -93,7 +73,6 @@ export function createRouteGuard(router: Router) {
|
||||
* @param to to route
|
||||
*/
|
||||
async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
|
||||
const authStore = useAuthStore();
|
||||
const routeStore = useRouteStore();
|
||||
|
||||
const notFoundRoute: RouteKey = 'not-found';
|
||||
@ -105,8 +84,48 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
|
||||
|
||||
// the route is captured by the "not-found" route because the constant route is not initialized
|
||||
// after the constant route is initialized, redirect to the original route
|
||||
const path = to.fullPath;
|
||||
const location: RouteLocationRaw = {
|
||||
path,
|
||||
replace: true,
|
||||
query: to.query,
|
||||
hash: to.hash
|
||||
};
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
const isLogin = Boolean(localStg.get('token'));
|
||||
|
||||
if (!isLogin) {
|
||||
// if the user is not logged in and the route is a constant route but not the "not-found" route, then it is allowed to access.
|
||||
if (to.meta.constant && !isNotFoundRoute) {
|
||||
routeStore.onRouteSwitchWhenNotLoggedIn();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// if the user is not logged in, then switch to the login page
|
||||
const loginRoute: RouteKey = 'login';
|
||||
const query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
|
||||
|
||||
const location: RouteLocationRaw = {
|
||||
name: loginRoute,
|
||||
query
|
||||
};
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
if (!routeStore.isInitAuthRoute) {
|
||||
// initialize the auth route
|
||||
await routeStore.initAuthRoute();
|
||||
|
||||
// the route is captured by the "not-found" route because the auth route is not initialized
|
||||
// after the auth route is initialized, redirect to the original route
|
||||
if (isNotFoundRoute) {
|
||||
const path = to.fullPath;
|
||||
const rootRoute: RouteKey = 'root';
|
||||
const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;
|
||||
|
||||
const location: RouteLocationRaw = {
|
||||
path,
|
||||
@ -119,63 +138,21 @@ async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw
|
||||
}
|
||||
}
|
||||
|
||||
// if the route is the constant route but is not the "not-found" route, then it is allowed to access.
|
||||
if (to.meta.constant && !isNotFoundRoute) {
|
||||
return null;
|
||||
}
|
||||
routeStore.onRouteSwitchWhenLoggedIn();
|
||||
|
||||
// the auth route is initialized
|
||||
// it is not the "not-found" route, then it is allowed to access
|
||||
if (routeStore.isInitAuthRoute && !isNotFoundRoute) {
|
||||
if (!isNotFoundRoute) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// it is captured by the "not-found" route, then check whether the route exists
|
||||
if (routeStore.isInitAuthRoute && isNotFoundRoute) {
|
||||
const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
|
||||
const noPermissionRoute: RouteKey = '403';
|
||||
|
||||
if (exist) {
|
||||
const location: RouteLocationRaw = {
|
||||
name: noPermissionRoute
|
||||
};
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// if the auth route is not initialized, then initialize the auth route
|
||||
const isLogin = Boolean(localStg.get('token'));
|
||||
// initialize the auth route requires the user to be logged in, if not, redirect to the login page
|
||||
if (!isLogin) {
|
||||
const loginRoute: RouteKey = 'login';
|
||||
const query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
|
||||
const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
|
||||
const noPermissionRoute: RouteKey = '403';
|
||||
|
||||
if (exist) {
|
||||
const location: RouteLocationRaw = {
|
||||
name: loginRoute,
|
||||
query
|
||||
};
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
await authStore.initUserInfo();
|
||||
|
||||
// initialize the auth route
|
||||
await routeStore.initAuthRoute();
|
||||
|
||||
// the route is captured by the "not-found" route because the auth route is not initialized
|
||||
// after the auth route is initialized, redirect to the original route
|
||||
if (isNotFoundRoute) {
|
||||
const rootRoute: RouteKey = 'root';
|
||||
const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;
|
||||
|
||||
const location: RouteLocationRaw = {
|
||||
path,
|
||||
replace: true,
|
||||
query: to.query,
|
||||
hash: to.hash
|
||||
name: noPermissionRoute
|
||||
};
|
||||
|
||||
return location;
|
||||
|
@ -79,17 +79,13 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
const pass = await loginByToken(loginToken);
|
||||
|
||||
if (pass) {
|
||||
await routeStore.initAuthRoute();
|
||||
|
||||
await redirectFromLogin(redirect);
|
||||
|
||||
if (routeStore.isInitAuthRoute) {
|
||||
// window.$notification?.success({
|
||||
// title: $t('page.login.common.loginSuccess'),
|
||||
// content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
|
||||
// duration: 4500
|
||||
// });
|
||||
}
|
||||
window.$notification?.success({
|
||||
title: $t('page.login.common.loginSuccess'),
|
||||
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
|
||||
duration: 4500
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resetStore();
|
||||
|
@ -232,10 +232,17 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
handleConstantAndAuthRoutes();
|
||||
|
||||
setIsInitConstantRoute(true);
|
||||
|
||||
tabStore.initHomeTab();
|
||||
}
|
||||
|
||||
/** Init auth route */
|
||||
async function initAuthRoute() {
|
||||
// check if user info is initialized
|
||||
if (!authStore.userInfo.userId) {
|
||||
await authStore.initUserInfo();
|
||||
}
|
||||
|
||||
if (authRouteMode.value === 'static') {
|
||||
initStaticAuthRoute();
|
||||
} else {
|
||||
@ -364,6 +371,14 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
return getSelectedMenuKeyPathByKey(selectedKey, menus.value);
|
||||
}
|
||||
|
||||
async function onRouteSwitchWhenLoggedIn() {
|
||||
await authStore.initUserInfo();
|
||||
}
|
||||
|
||||
async function onRouteSwitchWhenNotLoggedIn() {
|
||||
// some global init logic if it does not need to be logged in
|
||||
}
|
||||
|
||||
return {
|
||||
resetStore,
|
||||
routeHome,
|
||||
@ -380,6 +395,8 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
isInitAuthRoute,
|
||||
setIsInitAuthRoute,
|
||||
getIsAuthRouteExist,
|
||||
getSelectedMenuKeyPath
|
||||
getSelectedMenuKeyPath,
|
||||
onRouteSwitchWhenLoggedIn,
|
||||
onRouteSwitchWhenNotLoggedIn
|
||||
};
|
||||
});
|
||||
|
@ -174,6 +174,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
darkMode,
|
||||
val => {
|
||||
toggleCssDarkMode(val);
|
||||
localStg.set('darkMode', val);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import type { GlobalThemeOverrides } from 'naive-ui';
|
||||
import { defu } from 'defu';
|
||||
import { addColorAlpha, getColorPalette, getPaletteColorByNumber, getRgb } from '@sa/color';
|
||||
import { overrideThemeSettings, themeSettings } from '@/theme/settings';
|
||||
import { themeVars } from '@/theme/vars';
|
||||
import { toggleHtmlClass } from '@/utils/common';
|
||||
import { localStg } from '@/utils/storage';
|
||||
|
||||
const DARK_CLASS = 'dark';
|
||||
import { DARK_CLASS } from '@/constants/app';
|
||||
|
||||
/** Init theme settings */
|
||||
export function initThemeSettings() {
|
||||
@ -17,12 +17,15 @@ export function initThemeSettings() {
|
||||
// if it is production mode, the theme settings will be cached in localStorage
|
||||
// if want to update theme settings when publish new version, please update `overrideThemeSettings` in `src/theme/settings.ts`
|
||||
|
||||
const settings = localStg.get('themeSettings') || themeSettings;
|
||||
const localSettings = localStg.get('themeSettings');
|
||||
|
||||
let settings = defu(localSettings, themeSettings);
|
||||
|
||||
const isOverride = localStg.get('overrideThemeFlag') === BUILD_TIME;
|
||||
|
||||
if (!isOverride) {
|
||||
Object.assign(settings, overrideThemeSettings);
|
||||
settings = defu(overrideThemeSettings, settings);
|
||||
|
||||
localStg.set('overrideThemeFlag', BUILD_TIME);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,9 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
breadcrumb: {
|
||||
visible: true,
|
||||
showIcon: true
|
||||
},
|
||||
multilingual: {
|
||||
visible: true
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
@ -83,10 +86,4 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
*
|
||||
* If publish new version, use `overrideThemeSettings` to override certain theme settings
|
||||
*/
|
||||
export const overrideThemeSettings: Partial<App.Theme.ThemeSetting> = {
|
||||
resetCacheStrategy: 'close',
|
||||
watermark: {
|
||||
visible: false,
|
||||
text: 'SoybeanAdmin'
|
||||
}
|
||||
};
|
||||
export const overrideThemeSettings: Partial<App.Theme.ThemeSetting> = {};
|
||||
|
14
src/typings/app.d.ts
vendored
14
src/typings/app.d.ts
vendored
@ -21,7 +21,7 @@ declare namespace App {
|
||||
/** Whether info color is followed by the primary color */
|
||||
isInfoFollowPrimary: boolean;
|
||||
/** Reset cache strategy */
|
||||
resetCacheStrategy?: UnionKey.ResetCacheStrategy;
|
||||
resetCacheStrategy: UnionKey.ResetCacheStrategy;
|
||||
/** Layout */
|
||||
layout: {
|
||||
/** Layout mode */
|
||||
@ -33,7 +33,7 @@ declare namespace App {
|
||||
*
|
||||
* if true, the vertical child level menus in left and horizontal first level menus in top
|
||||
*/
|
||||
reverseHorizontalMix?: boolean;
|
||||
reverseHorizontalMix: boolean;
|
||||
};
|
||||
/** Page */
|
||||
page: {
|
||||
@ -53,6 +53,11 @@ declare namespace App {
|
||||
/** Whether to show the breadcrumb icon */
|
||||
showIcon: boolean;
|
||||
};
|
||||
/** Multilingual */
|
||||
multilingual: {
|
||||
/** Whether to show the multilingual */
|
||||
visible: boolean;
|
||||
};
|
||||
};
|
||||
/** Tab */
|
||||
tab: {
|
||||
@ -98,7 +103,7 @@ declare namespace App {
|
||||
right: boolean;
|
||||
};
|
||||
/** Watermark */
|
||||
watermark?: {
|
||||
watermark: {
|
||||
/** Whether to show the watermark */
|
||||
visible: boolean;
|
||||
/** Watermark text */
|
||||
@ -365,6 +370,9 @@ declare namespace App {
|
||||
visible: string;
|
||||
showIcon: string;
|
||||
};
|
||||
multilingual: {
|
||||
visible: string;
|
||||
};
|
||||
};
|
||||
tab: {
|
||||
visible: string;
|
||||
|
2
src/typings/storage.d.ts
vendored
2
src/typings/storage.d.ts
vendored
@ -25,6 +25,8 @@ declare namespace StorageType {
|
||||
refreshToken: string;
|
||||
/** The theme color */
|
||||
themeColor: string;
|
||||
/** The dark mode */
|
||||
darkMode: boolean;
|
||||
/** The theme settings */
|
||||
themeSettings: App.Theme.ThemeSetting;
|
||||
/**
|
||||
|
@ -66,6 +66,7 @@ const bgColor = computed(() => {
|
||||
@switch="themeStore.toggleThemeScheme"
|
||||
/>
|
||||
<LangSwitch
|
||||
v-if="themeStore.header.multilingual.visible"
|
||||
:lang="appStore.locale"
|
||||
:lang-options="appStore.localeOptions"
|
||||
:show-tooltip="false"
|
||||
|
@ -35,10 +35,7 @@ export default defineConfig(configEnv => {
|
||||
host: '0.0.0.0',
|
||||
port: 9527,
|
||||
open: true,
|
||||
proxy: createViteProxy(viteEnv, enableProxy),
|
||||
fs: {
|
||||
cachedChecks: false
|
||||
}
|
||||
proxy: createViteProxy(viteEnv, enableProxy)
|
||||
},
|
||||
preview: {
|
||||
port: 9725
|
||||
|
Loading…
Reference in New Issue
Block a user