Compare commits
	
		
			41 Commits
		
	
	
		
			v1.3.1
			...
			tauri-v1.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b00ae45108 | ||
|  | 75997c1655 | ||
|  | c4e16102cb | ||
|  | be8556cd2b | ||
|  | 27b5222cfb | ||
|  | 79b2a28b5a | ||
|  | 531bfaf1b9 | ||
|  | c3f1f69833 | ||
|  | a44ea624f1 | ||
|  | 7cb43fc3c2 | ||
|  | 4c9f4e09b1 | ||
|  | 9c012c7d13 | ||
|  | 878d9c395c | ||
|  | 3830ec7a69 | ||
|  | ef6cf93d96 | ||
|  | 7fa55905fc | ||
|  | 2fa400b113 | ||
|  | 73e917ad59 | ||
|  | e9a2ee4a23 | ||
|  | a1c14a1570 | ||
|  | b16a96323a | ||
|  | 26c93dff9a | ||
|  | 5094f0eea8 | ||
|  | a8f923eb38 | ||
|  | e6aa25e9f8 | ||
|  | 968370b1aa | ||
|  | cb1d44525d | ||
|  | 939c512f02 | ||
|  | 3f23993a83 | ||
|  | ebc838c3e0 | ||
|  | 5646a50ddf | ||
|  | 217a3bb259 | ||
|  | d37591dd7a | ||
|  | cf67d55cbf | ||
|  | 98b75c22c3 | ||
|  | 993e9caf79 | ||
|  | f26d0a61eb | ||
|  | eed617f9eb | ||
|  | ea8aa6c4e6 | ||
|  | 4dde4c22b1 | ||
|  | 59f07d8ac4 | 
							
								
								
									
										5
									
								
								.env
									
									
									
									
									
								
							
							
						
						| @@ -36,7 +36,7 @@ VITE_SERVICE_LOGOUT_CODES=8888,8889 | |||||||
| VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778 | VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778 | ||||||
|  |  | ||||||
| # token expired codes of backend service, when the code is received, it will refresh the token and resend the request | # token expired codes of backend service, when the code is received, it will refresh the token and resend the request | ||||||
| VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998 | VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998,3333 | ||||||
|  |  | ||||||
| # when the route mode is static, the defined super role | # when the route mode is static, the defined super role | ||||||
| VITE_STATIC_SUPER_ROLE=R_SUPER | VITE_STATIC_SUPER_ROLE=R_SUPER | ||||||
| @@ -46,3 +46,6 @@ VITE_SOURCE_MAP=N | |||||||
|  |  | ||||||
| # Used to differentiate storage across different domains | # Used to differentiate storage across different domains | ||||||
| VITE_STORAGE_PREFIX=SOY_ | VITE_STORAGE_PREFIX=SOY_ | ||||||
|  |  | ||||||
|  | # used to control whether the program automatically detects updates | ||||||
|  | VITE_AUTOMATICALLY_DETECT_UPDATE=Y | ||||||
|   | |||||||
							
								
								
									
										121
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,127 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## [v1.3.5](https://github.com/soybeanjs/soybean-admin/compare/v1.3.4...v1.3.5) (2024-09-07) | ||||||
|  |  | ||||||
|  | ###    🚀 Features | ||||||
|  |  | ||||||
|  | - **packages**: | ||||||
|  |   - @sa/axios: add response to flatRequest when success  -  by @soybeanjs [<samp>(c4e16)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c4e1610) | ||||||
|  | - **projects**: | ||||||
|  |   - README.zh_CN.md 添加合作推广  -  by @PZ-18664918826 in https://github.com/soybeanjs/soybean-admin/issues/601 [<samp>(2fa40)</samp>](https://github.com/soybeanjs/soybean-admin/commit/2fa400b) | ||||||
|  |   - Add more commit types according to Apache specifications  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/610 [<samp>(878d9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/878d9c3) | ||||||
|  |   - does the configuration support automatic updates. close#612  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/617 and https://github.com/soybeanjs/soybean-admin/issues/612 [<samp>(4c9f4)</samp>](https://github.com/soybeanjs/soybean-admin/commit/4c9f4e0) | ||||||
|  |   - add app error handler. close #587  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/587 [<samp>(be855)</samp>](https://github.com/soybeanjs/soybean-admin/commit/be8556c) | ||||||
|  |  | ||||||
|  | ###    🐞 Bug Fixes | ||||||
|  |  | ||||||
|  | - **deps**: | ||||||
|  |   - move json5 from devDependencies to dependencies to support production usage  -  by @mufeng889 in https://github.com/soybeanjs/soybean-admin/issues/618 [<samp>(7cb43)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7cb43fc) | ||||||
|  | - **projects**: | ||||||
|  |   - avoid retrieving cached HTML  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/605 [<samp>(ef6cf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/ef6cf93) | ||||||
|  |   - fix login redirect  -  by @soybeanjs [<samp>(3830e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/3830ec7) | ||||||
|  |   - fix vertical-mix-menu when sider collapse. fixed #608  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/608 [<samp>(c3f1f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/c3f1f69) | ||||||
|  |   - fix breadcrumb when activeMenu is parent menu. fixed #589  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/589 [<samp>(79b2a)</samp>](https://github.com/soybeanjs/soybean-admin/commit/79b2a28) | ||||||
|  |   - fix refresh token when meet multi requests. fixed #581  -  by @soybeanjs in https://github.com/soybeanjs/soybean-admin/issues/581 [<samp>(27b52)</samp>](https://github.com/soybeanjs/soybean-admin/commit/27b5222) | ||||||
|  | - **types**: | ||||||
|  |   - fix the type of TableApiFn  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/599 [<samp>(26c93)</samp>](https://github.com/soybeanjs/soybean-admin/commit/26c93df) | ||||||
|  |  | ||||||
|  | ###    🛠 Optimizations | ||||||
|  |  | ||||||
|  | - **projects**: optimize menu selectedKey  -  by @soybeanjs [<samp>(531bf)</samp>](https://github.com/soybeanjs/soybean-admin/commit/531bfaf) | ||||||
|  |  | ||||||
|  | ###    📖 Documentation | ||||||
|  |  | ||||||
|  | - **projects**: | ||||||
|  |   - update README  -  by @mufeng889 in https://github.com/soybeanjs/soybean-admin/issues/594 [<samp>(a8f92)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a8f923e) | ||||||
|  |   - update README  -  by @soybeanjs [<samp>(e9a2e)</samp>](https://github.com/soybeanjs/soybean-admin/commit/e9a2ee4) | ||||||
|  |   - update README  -  by @soybeanjs [<samp>(73e91)</samp>](https://github.com/soybeanjs/soybean-admin/commit/73e917a) | ||||||
|  |   - update the location of important information in the document  -  by **Azir** [<samp>(9c012)</samp>](https://github.com/soybeanjs/soybean-admin/commit/9c012c7) | ||||||
|  |  | ||||||
|  | ###    🏡 Chore | ||||||
|  |  | ||||||
|  | - **deps**: | ||||||
|  |   - update deps  -  by @soybeanjs [<samp>(a1c14)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a1c14a1) | ||||||
|  |   - update deps  -  by @soybeanjs [<samp>(7fa55)</samp>](https://github.com/soybeanjs/soybean-admin/commit/7fa5590) | ||||||
|  |   - update deps  -  by @soybeanjs [<samp>(a44ea)</samp>](https://github.com/soybeanjs/soybean-admin/commit/a44ea62) | ||||||
|  | - **projects**: | ||||||
|  |   - use json5 resolve env `VITE_OTHER_SERVICE_BASE_URL` & fix proxy enable  -  by @soybeanjs [<samp>(b16a9)</samp>](https://github.com/soybeanjs/soybean-admin/commit/b16a963) | ||||||
|  |  | ||||||
|  | ###    🎨 Styles | ||||||
|  |  | ||||||
|  | - **projects**: rename script czh to commit:zh  -  by @Azir-11 in https://github.com/soybeanjs/soybean-admin/issues/597 [<samp>(5094f)</samp>](https://github.com/soybeanjs/soybean-admin/commit/5094f0e) | ||||||
|  |  | ||||||
|  | ###    ❤️ Contributors | ||||||
|  |  | ||||||
|  | [](https://github.com/soybeanjs)  [](https://github.com/mufeng889)  [](https://github.com/Azir-11)  [](https://github.com/PZ-18664918826)   | ||||||
|  | [Azir](mailto:2075125282@qq.com),  | ||||||
|  |  | ||||||
|  | ## [v1.3.4](https://github.com/honghuangdc/soybean-admin/compare/v1.3.3...v1.3.4) (2024-08-01) | ||||||
|  |  | ||||||
|  | ###    🚨 Breaking Changes | ||||||
|  |  | ||||||
|  | - **projects**: don't reset active menu of vertical-mix layout when it is mixSiderFixed  -  by @honghuangdc [<samp>(939c5)</samp>](https://github.com/honghuangdc/soybean-admin/commit/939c512) | ||||||
|  |  | ||||||
|  | ###    🛠 Optimizations | ||||||
|  |  | ||||||
|  | - **projects**: optimize code  -  by @honghuangdc [<samp>(cb1d4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cb1d445) | ||||||
|  |  | ||||||
|  | ###    🏡 Chore | ||||||
|  |  | ||||||
|  | - **projects**: update deps & fix vue-router type  -  by @honghuangdc [<samp>(96837)</samp>](https://github.com/honghuangdc/soybean-admin/commit/968370b) | ||||||
|  |  | ||||||
|  | ###    ❤️ Contributors | ||||||
|  |  | ||||||
|  | [](https://github.com/honghuangdc)   | ||||||
|  |  | ||||||
|  | ## [v1.3.3](https://github.com/honghuangdc/soybean-admin/compare/v1.3.2...v1.3.3) (2024-07-30) | ||||||
|  |  | ||||||
|  | ###    🐞 Bug Fixes | ||||||
|  |  | ||||||
|  | - **projects**: fix watermark settings  -  by @honghuangdc [<samp>(5646a)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5646a50) | ||||||
|  |  | ||||||
|  | ###    📖 Documentation | ||||||
|  |  | ||||||
|  | - **projects**: update CHANGELOG  -  by @honghuangdc [<samp>(ebc83)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ebc838c) | ||||||
|  |  | ||||||
|  | ###    ❤️ Contributors | ||||||
|  |  | ||||||
|  | [](https://github.com/honghuangdc)   | ||||||
|  |  | ||||||
|  | ## [v1.3.2](https://github.com/honghuangdc/soybean-admin/compare/v1.3.1...v1.3.2) (2024-07-30) | ||||||
|  |  | ||||||
|  | ###    🚀 Features | ||||||
|  |  | ||||||
|  | - **projects**: | ||||||
|  |   - add color fading mode.close #567  -  by @Azir-11 in https://github.com/honghuangdc/soybean-admin/issues/569 and https://github.com/honghuangdc/soybean-admin/issues/567 [<samp>(4dde4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4dde4c2) | ||||||
|  |   - add full screen watermark. close#571  -  by @paynezhuang in https://github.com/honghuangdc/soybean-admin/issues/573 and https://github.com/honghuangdc/soybean-admin/issues/571 [<samp>(ea8aa)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ea8aa6c) | ||||||
|  |  | ||||||
|  | ###    🐞 Bug Fixes | ||||||
|  |  | ||||||
|  | - **projects**: fix vertical-mix menu selected  -  by @honghuangdc [<samp>(59f07)</samp>](https://github.com/honghuangdc/soybean-admin/commit/59f07d8) | ||||||
|  |  | ||||||
|  | ###    🛠 Optimizations | ||||||
|  |  | ||||||
|  | - **projects**: | ||||||
|  |   - add type WatermarkProps  -  by @honghuangdc [<samp>(f26d0)</samp>](https://github.com/honghuangdc/soybean-admin/commit/f26d0a6) | ||||||
|  |   - remove home NAlert closable  -  by @honghuangdc [<samp>(98b75)</samp>](https://github.com/honghuangdc/soybean-admin/commit/98b75c2) | ||||||
|  |   - optimize response code comparison  -  by @honghuangdc [<samp>(cf67d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cf67d55) | ||||||
|  | - **types**: | ||||||
|  |   - remove useless types.  -  by **Azir** [<samp>(eed61)</samp>](https://github.com/honghuangdc/soybean-admin/commit/eed617f) | ||||||
|  |  | ||||||
|  | ###    📖 Documentation | ||||||
|  |  | ||||||
|  | - **projects**: update CHANGELOG  -  by @honghuangdc [<samp>(d3759)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d37591d) | ||||||
|  |  | ||||||
|  | ###    🏡 Chore | ||||||
|  |  | ||||||
|  | - **deps**: update deps  -  by @honghuangdc [<samp>(993e9)</samp>](https://github.com/honghuangdc/soybean-admin/commit/993e9ca) | ||||||
|  |  | ||||||
|  | ###    ❤️ Contributors | ||||||
|  |  | ||||||
|  | [](https://github.com/honghuangdc)  [](https://github.com/paynezhuang)  [](https://github.com/Azir-11)   | ||||||
|  | [Azir](mailto:2075125282@qq.com),  | ||||||
|  |  | ||||||
| ## [v1.3.1](https://github.com/honghuangdc/soybean-admin/compare/v1.3.0...v1.3.1) (2024-07-22) | ## [v1.3.1](https://github.com/honghuangdc/soybean-admin/compare/v1.3.0...v1.3.1) (2024-07-22) | ||||||
|  |  | ||||||
| ###    🐞 Bug Fixes | ###    🐞 Bug Fixes | ||||||
|   | |||||||
| @@ -1,5 +1,61 @@ | |||||||
| # 更新日志 | # 更新日志 | ||||||
|  |  | ||||||
|  | ## [v1.3.2](https://github.com/honghuangdc/soybean-admin/compare/v1.3.1...v1.3.2) (2024-07-30) | ||||||
|  |  | ||||||
|  | ###    🚀 新功能 | ||||||
|  |  | ||||||
|  | - **项目**: | ||||||
|  |   - 添加颜色渐变模式。关闭 #567  -  由 @Azir-11 在 https://github.com/honghuangdc/soybean-admin/issues/569 和 https://github.com/honghuangdc/soybean-admin/issues/567 [<samp>(4dde4)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4dde4c2) | ||||||
|  |   - 添加全屏水印。关闭 #571  -  由 @paynezhuang 在 https://github.com/honghuangdc/soybean-admin/issues/573 和 https://github.com/honghuangdc/soybean-admin/issues/571 [<samp>(ea8aa)</samp>](https://github.com/honghuangdc/soybean-admin/commit/ea8aa6c) | ||||||
|  |  | ||||||
|  | ###    🐞 Bug 修复 | ||||||
|  |  | ||||||
|  | - **项目**: 修复 vertical-mix 菜单选中问题  -  由 @honghuangdc [<samp>(59f07)</samp>](https://github.com/honghuangdc/soybean-admin/commit/59f07d8) | ||||||
|  |  | ||||||
|  | ###    🛠 优化 | ||||||
|  |  | ||||||
|  | - **项目**: | ||||||
|  |   - 添加类型 WatermarkProps  -  由 @honghuangdc [<samp>(f26d0)</samp>](https://github.com/honghuangdc/soybean-admin/commit/f26d0a6) | ||||||
|  |   - 移除 home NAlert 可关闭  -  由 @honghuangdc [<samp>(98b75)</samp>](https://github.com/honghuangdc/soybean-admin/commit/98b75c2) | ||||||
|  |   - 优化响应代码比较  -  由 @honghuangdc [<samp>(cf67d)</samp>](https://github.com/honghuangdc/soybean-admin/commit/cf67d55) | ||||||
|  | - **类型**: | ||||||
|  |   - 移除无用类型。  -  由 **Azir** [<samp>(eed61)</samp>](https://github.com/honghuangdc/soybean-admin/commit/eed617f) | ||||||
|  |  | ||||||
|  | ###    📖 文档 | ||||||
|  |  | ||||||
|  | - **项目**: 更新更新日志  -  由 @honghuangdc [<samp>(d3759)</samp>](https://github.com/honghuangdc/soybean-admin/commit/d37591d) | ||||||
|  |  | ||||||
|  | ###    🏡 杂项 | ||||||
|  |  | ||||||
|  | - **依赖**: 更新依赖  -  由 @honghuangdc [<samp>(993e9)</samp>](https://github.com/honghuangdc/soybean-admin/commit/993e9ca) | ||||||
|  |  | ||||||
|  | ###    ❤️ 贡献者 | ||||||
|  |  | ||||||
|  | [](https://github.com/honghuangdc)  [](https://github.com/paynezhuang)  [](https://github.com/Azir-11)   | ||||||
|  | [Azir](mailto:2075125282@qq.com),  | ||||||
|  |  | ||||||
|  | ## [v1.3.1](https://github.com/honghuangdc/soybean-admin/compare/v1.3.0...v1.3.1) (2024-07-22) | ||||||
|  |  | ||||||
|  | ###    🐞 Bug 修复 | ||||||
|  |  | ||||||
|  | - **项目**: | ||||||
|  |   - 修复顶部菜单混合和反向模式下侧边栏宽度异常的问题  -  由 @Azir-11 在 https://github.com/honghuangdc/soybean-admin/issues/562 修复 [<samp>(c4695)</samp>](https://github.com/honghuangdc/soybean-admin/commit/c469512) | ||||||
|  |   - 修复水平混合菜单反转问题。修复 #563  -  由 @honghuangdc 在 https://github.com/honghuangdc/soybean-admin/issues/563 修复 [<samp>(4e55b)</samp>](https://github.com/honghuangdc/soybean-admin/commit/4e55b0e) | ||||||
|  |   - 修复当布局来自移动端时垂直菜单不渲染的问题  -  由 @honghuangdc 修复 [<samp>(84027)</samp>](https://github.com/honghuangdc/soybean-admin/commit/8402734) | ||||||
|  |  | ||||||
|  | ###    📖 文档 | ||||||
|  |  | ||||||
|  | - **项目**: 更新更新日志  -  由 @honghuangdc 更新 [<samp>(613c8)</samp>](https://github.com/honghuangdc/soybean-admin/commit/613c836) | ||||||
|  |  | ||||||
|  | ###    🎨 样式 | ||||||
|  |  | ||||||
|  | - **其他**: 修改灰度模式的中文名称  -  由 **Azir** 修改 [<samp>(53770)</samp>](https://github.com/honghuangdc/soybean-admin/commit/5377002) | ||||||
|  |  | ||||||
|  | ###    ❤️ 贡献者 | ||||||
|  |  | ||||||
|  | [](https://github.com/honghuangdc)  [](https://github.com/Azir-11)   | ||||||
|  | [Azir](mailto:2075125282@qq.com),  | ||||||
|  |  | ||||||
|  |  | ||||||
| ## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22) | ## [v1.3.0](https://github.com/soybeanjs/soybean-admin/compare/v1.2.8...v1.3.0) (2024-07-22) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -111,6 +111,7 @@ pnpm build | |||||||
|  |  | ||||||
| ## Ecosystem | ## 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. | - [electron-mock-admin](https://github.com/lixin59/electron-mock-api): A Mock Api management system that helps front-end developers quickly implement interface mocks. | ||||||
| - [T-Shell](https://github.com/TheBlindM/T-Shell): A terminal emulator and SSH client with configurable command prompts. | - [T-Shell](https://github.com/TheBlindM/T-Shell): A terminal emulator and SSH client with configurable command prompts. | ||||||
| - [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin). | - [pea](https://github.com/haitang1894/pea) : Adopting SpringBoot3.2 + JDK21, MyBatis-Plus, SpringSecurity security framework, etc., suitable for the simple permission system developed by [soybean-admin](https://gitee.com/honghuangdc/soybean-admin). | ||||||
| @@ -155,7 +156,7 @@ Thanks the following people for their contributions. If you want to contribute t | |||||||
|  |  | ||||||
|   <div> |   <div> | ||||||
|   	<p>QQ Group</p> |   	<p>QQ Group</p> | ||||||
|     <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" /> |     <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-3.jpg" style="width:200px" /> | ||||||
|   </div> |   </div> | ||||||
| 	<!-- <div> | 	<!-- <div> | ||||||
| 		<p>WeChat Group</p> | 		<p>WeChat Group</p> | ||||||
| @@ -165,10 +166,6 @@ Thanks the following people for their contributions. If you want to contribute t | |||||||
| 		<p>Add the following WeChat to invite to the WeChat group</p> | 		<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" /> | 		<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" /> | ||||||
| 	</div> | 	</div> | ||||||
|   <div> |  | ||||||
|     <p>Add Soybean's WeChat for business consultation, cooperation, project architecture, one-on-one guidance, etc.</p> |  | ||||||
|     <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybean.jpg" style="width:200px" /> |  | ||||||
|   </div> |  | ||||||
|  |  | ||||||
| ## Star Trend | ## Star Trend | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,6 +57,36 @@ | |||||||
| - [地址](https://docs.soybeanjs.cn) | - [地址](https://docs.soybeanjs.cn) | ||||||
| - [旧版文档](https://legacy-docs.soybeanjs.cn) | - [旧版文档](https://legacy-docs.soybeanjs.cn) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## 合作事项 | ||||||
|  |  | ||||||
|  | 我们非常感谢大家对 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的支持!为了进一步回馈社区,并助力企业和开发者实现个性化需求,我们现提供多种合作服务,期待与您携手共赢。 | ||||||
|  |  | ||||||
|  | ##### 1、定制化管理后台开发 | ||||||
|  |  | ||||||
|  | 针对企业和开发者的特定业务需求,我们提供基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的定制化管理后台开发服务。我们的团队具备丰富的行业经验,能够迅速理解并实现您的需求,打造高效、灵活且安全的定制化解决方案。 | ||||||
|  |  | ||||||
|  | - **定制开发**:我们将根据您的具体需求,提供从需求分析、UI设计到功能实现的全方位服务,确保项目高效交付。 | ||||||
|  | - **功能扩展**:在 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 基础上,扩展您所需的特定功能模块,提升管理后台的功能和用户体验。 | ||||||
|  |  | ||||||
|  | ##### 2、企业外包服务 | ||||||
|  |  | ||||||
|  | 我们承接各类企业级外包项目,特别是在管理后台系统的开发、集成与运维方面。我们以精益求精的态度,确保项目的质量和进度,为您的业务提供强有力的技术支持。 | ||||||
|  |  | ||||||
|  | - **项目开发**:无论是全新的项目,还是现有系统的优化与集成,我们都将为您量身打造高效可靠的解决方案。 | ||||||
|  | - **系统集成与维护**:我们也提供基于 [`SoybeanAdmin`](https://github.com/soybeanjs/soybean-admin) 的系统集成与长期维护服务,确保您的系统稳定、安全地运行。 | ||||||
|  |  | ||||||
|  | ##### 3、联系方式 | ||||||
|  |  | ||||||
|  | 如有合作意向或项目咨询,请通过以下方式与我们联系: | ||||||
|  |  | ||||||
|  | - **Email**: [soybeanjs@outlook.com](mailto:soybeanjs@outlook.com) | ||||||
|  | - **GitHub Issues**: 欢迎通过 [GitHub Issues](https://github.com/soybeanjs/soybean-admin/issues/new) 联系我们,进行初步的合作洽谈。 | ||||||
|  | - **商务合作微信**:  honghuangdc | ||||||
|  |  | ||||||
|  | 期待与您开展深入合作,共同推动 SoybeanAdmin 项目及其在更多领域的成功应用! | ||||||
|  |  | ||||||
|  |  | ||||||
| ## 示例图片 | ## 示例图片 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -109,6 +139,7 @@ pnpm build | |||||||
|  |  | ||||||
| ## 周边生态 | ## 周边生态 | ||||||
|  |  | ||||||
|  | - [react-soybean-admin](https://github.com/mufeng889/react-soybean-admin): 基于SoybeanAdmin的React版本. | ||||||
| - [electron-mock-admin](https://github.com/lixin59/electron-mock-api): 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的 mock。 | - [electron-mock-admin](https://github.com/lixin59/electron-mock-api): 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的 mock。 | ||||||
| - [T-Shell](https://github.com/TheBlindM/T-Shell): 是一个可配置命令提示的终端模拟器和 SSH 客户端。 | - [T-Shell](https://github.com/TheBlindM/T-Shell): 是一个可配置命令提示的终端模拟器和 SSH 客户端。 | ||||||
| - [pea](https://github.com/haitang1894/pea) : 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等,适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 开发的简单权限系统。 | - [pea](https://github.com/haitang1894/pea) : 采用SpringBoot3.2 + JDK21、MyBatis-Plus、SpringSecurity安全框架等,适配 [soybean-admin](https://gitee.com/honghuangdc/soybean-admin) 开发的简单权限系统。 | ||||||
| @@ -155,7 +186,7 @@ pnpm build | |||||||
|  |  | ||||||
|   <div> |   <div> | ||||||
|   	<p>QQ交流群</p> |   	<p>QQ交流群</p> | ||||||
|     <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-2.jpg" style="width:200px" /> |     <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin-3.jpg" style="width:200px" /> | ||||||
|   </div> |   </div> | ||||||
| 	<!-- <div> | 	<!-- <div> | ||||||
| 		<p>微信群</p> | 		<p>微信群</p> | ||||||
| @@ -165,10 +196,6 @@ pnpm build | |||||||
| 		<p>添加下面微信邀请进微信群</p> | 		<p>添加下面微信邀请进微信群</p> | ||||||
| 		<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" /> | 		<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybeanjs.jpg" style="width:200px" /> | ||||||
| 	</div> | 	</div> | ||||||
|   <div> |  | ||||||
|     <p>添加 Soybean 的微信,业务咨询、合作、项目架构、一对一指导等</p> |  | ||||||
|     <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/wechat-soybean.jpg" style="width:200px" /> |  | ||||||
|   </div> |  | ||||||
|  |  | ||||||
| ## Star 趋势 | ## Star 趋势 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,10 +5,10 @@ import { createServiceConfig } from '../../src/utils/service'; | |||||||
|  * Set http proxy |  * Set http proxy | ||||||
|  * |  * | ||||||
|  * @param env - The current env |  * @param env - The current env | ||||||
|  * @param isDev - Is development environment |  * @param enable - If enable http proxy | ||||||
|  */ |  */ | ||||||
| export function createViteProxy(env: Env.ImportMeta, isDev: boolean) { | export function createViteProxy(env: Env.ImportMeta, enable: boolean) { | ||||||
|   const isEnableHttpProxy = isDev && env.VITE_HTTP_PROXY === 'Y'; |   const isEnableHttpProxy = enable && env.VITE_HTTP_PROXY === 'Y'; | ||||||
|  |  | ||||||
|   if (!isEnableHttpProxy) return undefined; |   if (!isEnableHttpProxy) return undefined; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { defineConfig } from '@soybeanjs/eslint-config'; | import { defineConfig } from '@soybeanjs/eslint-config'; | ||||||
|  |  | ||||||
| export default defineConfig( | export default defineConfig( | ||||||
|   { vue: true, unocss: true }, |   { vue: true, unocss: true, ignores: ['src-tauri/target'] }, | ||||||
|   { |   { | ||||||
|     rules: { |     rules: { | ||||||
|       'vue/multi-word-component-names': [ |       'vue/multi-word-component-names': [ | ||||||
|   | |||||||
							
								
								
									
										71
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,7 +1,7 @@ | |||||||
| { | { | ||||||
|   "name": "soybean-admin", |   "name": "soybean-admin", | ||||||
|   "type": "module", |   "type": "module", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "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、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。", | ||||||
|   "author": { |   "author": { | ||||||
|     "name": "Soybean", |     "name": "Soybean", | ||||||
| @@ -32,17 +32,20 @@ | |||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "build": "vite build --mode prod", |     "build": "vite build --mode prod", | ||||||
|  |     "build:tauri": "pnpm tauri build", | ||||||
|     "build:test": "vite build --mode test", |     "build:test": "vite build --mode test", | ||||||
|     "cleanup": "sa cleanup", |     "cleanup": "sa cleanup", | ||||||
|     "commit": "sa git-commit", |     "commit": "sa git-commit", | ||||||
|     "czh": "sa git-commit -l=zh-cn", |     "commit:zh": "sa git-commit -l=zh-cn", | ||||||
|     "dev": "vite --mode test", |     "dev": "vite --mode test", | ||||||
|     "dev:prod": "vite --mode prod", |     "dev:prod": "vite --mode prod", | ||||||
|  |     "dev:tauri": "pnpm tauri dev", | ||||||
|     "gen-route": "sa gen-route", |     "gen-route": "sa gen-route", | ||||||
|     "lint": "eslint . --fix", |     "lint": "eslint . --fix", | ||||||
|     "prepare": "simple-git-hooks", |     "prepare": "simple-git-hooks", | ||||||
|     "preview": "vite preview", |     "preview": "vite preview", | ||||||
|     "release": "sa release", |     "release": "sa release", | ||||||
|  |     "tauri-icon": "pnpm tauri icon ./public/logo.png", | ||||||
|     "typecheck": "vue-tsc --noEmit --skipLibCheck", |     "typecheck": "vue-tsc --noEmit --skipLibCheck", | ||||||
|     "update-pkg": "sa update-pkg" |     "update-pkg": "sa update-pkg" | ||||||
|   }, |   }, | ||||||
| @@ -54,50 +57,52 @@ | |||||||
|     "@sa/hooks": "workspace:*", |     "@sa/hooks": "workspace:*", | ||||||
|     "@sa/materials": "workspace:*", |     "@sa/materials": "workspace:*", | ||||||
|     "@sa/utils": "workspace:*", |     "@sa/utils": "workspace:*", | ||||||
|     "@vueuse/core": "10.11.0", |     "@vueuse/core": "11.0.3", | ||||||
|     "clipboard": "2.0.11", |     "clipboard": "2.0.11", | ||||||
|     "dayjs": "1.11.12", |     "dayjs": "1.11.13", | ||||||
|     "echarts": "5.5.1", |     "echarts": "5.5.1", | ||||||
|  |     "json5": "2.2.3", | ||||||
|     "naive-ui": "2.39.0", |     "naive-ui": "2.39.0", | ||||||
|     "nprogress": "0.2.0", |     "nprogress": "0.2.0", | ||||||
|     "pinia": "2.1.7", |     "pinia": "2.2.2", | ||||||
|     "tailwind-merge": "2.4.0", |     "tailwind-merge": "2.5.2", | ||||||
|     "vue": "3.4.33", |     "vue": "3.5.3", | ||||||
|     "vue-draggable-plus": "0.5.2", |     "vue-draggable-plus": "0.5.3", | ||||||
|     "vue-i18n": "9.13.1", |     "vue-i18n": "9.14.0", | ||||||
|     "vue-router": "4.4.0" |     "vue-router": "4.4.3" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@elegant-router/vue": "0.3.7", |     "@elegant-router/vue": "0.3.8", | ||||||
|     "@iconify/json": "2.2.230", |     "@iconify/json": "2.2.245", | ||||||
|     "@sa/scripts": "workspace:*", |     "@sa/scripts": "workspace:*", | ||||||
|     "@sa/uno-preset": "workspace:*", |     "@sa/uno-preset": "workspace:*", | ||||||
|     "@soybeanjs/eslint-config": "1.3.7", |     "@soybeanjs/eslint-config": "1.4.0", | ||||||
|     "@types/node": "20.14.11", |     "@tauri-apps/cli": "1.6.1", | ||||||
|  |     "@types/node": "22.5.4", | ||||||
|     "@types/nprogress": "0.2.3", |     "@types/nprogress": "0.2.3", | ||||||
|     "@unocss/eslint-config": "0.61.5", |     "@unocss/eslint-config": "0.62.3", | ||||||
|     "@unocss/preset-icons": "0.61.5", |     "@unocss/preset-icons": "0.62.3", | ||||||
|     "@unocss/preset-uno": "0.61.5", |     "@unocss/preset-uno": "0.62.3", | ||||||
|     "@unocss/transformer-directives": "0.61.5", |     "@unocss/transformer-directives": "0.62.3", | ||||||
|     "@unocss/transformer-variant-group": "0.61.5", |     "@unocss/transformer-variant-group": "0.62.3", | ||||||
|     "@unocss/vite": "0.61.5", |     "@unocss/vite": "0.62.3", | ||||||
|     "@vitejs/plugin-vue": "5.0.5", |     "@vitejs/plugin-vue": "5.1.3", | ||||||
|     "@vitejs/plugin-vue-jsx": "4.0.0", |     "@vitejs/plugin-vue-jsx": "4.0.1", | ||||||
|     "eslint": "9.7.0", |     "eslint": "9.10.0", | ||||||
|     "eslint-plugin-vue": "9.27.0", |     "eslint-plugin-vue": "9.28.0", | ||||||
|     "lint-staged": "15.2.7", |     "lint-staged": "15.2.10", | ||||||
|     "sass": "1.77.8", |     "sass": "1.78.0", | ||||||
|     "simple-git-hooks": "2.11.1", |     "simple-git-hooks": "2.11.1", | ||||||
|     "tsx": "4.16.2", |     "tsx": "4.19.0", | ||||||
|     "typescript": "5.5.3", |     "typescript": "5.5.4", | ||||||
|     "unplugin-icons": "0.19.0", |     "unplugin-icons": "0.19.3", | ||||||
|     "unplugin-vue-components": "0.27.3", |     "unplugin-vue-components": "0.27.4", | ||||||
|     "vite": "5.3.4", |     "vite": "5.4.3", | ||||||
|     "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.3.6", |     "vite-plugin-vue-devtools": "7.4.4", | ||||||
|     "vue-eslint-parser": "9.4.3", |     "vue-eslint-parser": "9.4.3", | ||||||
|     "vue-tsc": "2.0.26" |     "vue-tsc": "2.1.6" | ||||||
|   }, |   }, | ||||||
|   "simple-git-hooks": { |   "simple-git-hooks": { | ||||||
|     "commit-msg": "pnpm sa git-commit-verify", |     "commit-msg": "pnpm sa git-commit-verify", | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/axios", |   "name": "@sa/axios", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
| @@ -11,9 +11,9 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@sa/utils": "workspace:*", |     "@sa/utils": "workspace:*", | ||||||
|     "axios": "1.7.2", |     "axios": "1.7.7", | ||||||
|     "axios-retry": "4.4.1", |     "axios-retry": "4.5.0", | ||||||
|     "qs": "6.12.3" |     "qs": "6.13.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/qs": "6.9.15" |     "@types/qs": "6.9.15" | ||||||
|   | |||||||
| @@ -162,12 +162,12 @@ export function createFlatRequest<ResponseData = any, State = Record<string, unk | |||||||
|       if (responseType === 'json') { |       if (responseType === 'json') { | ||||||
|         const data = opts.transformBackendResponse(response); |         const data = opts.transformBackendResponse(response); | ||||||
|  |  | ||||||
|         return { data, error: null }; |         return { data, error: null, response }; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       return { data: response.data as MappedType<R, T>, error: null }; |       return { data: response.data as MappedType<R, T>, error: null }; | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       return { data: null, error }; |       return { data: null, error, response: (error as AxiosError<ResponseData>).response }; | ||||||
|     } |     } | ||||||
|   } as FlatRequestInstance<State, ResponseData>; |   } as FlatRequestInstance<State, ResponseData>; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -92,18 +92,20 @@ export interface RequestInstance<S = Record<string, unknown>> extends RequestIns | |||||||
|   <T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>; |   <T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>; | ||||||
| } | } | ||||||
|  |  | ||||||
| export type FlatResponseSuccessData<T = any> = { | export type FlatResponseSuccessData<T = any, ResponseData = any> = { | ||||||
|   data: T; |   data: T; | ||||||
|   error: null; |   error: null; | ||||||
|  |   response: AxiosResponse<ResponseData>; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export type FlatResponseFailData<ResponseData = any> = { | export type FlatResponseFailData<ResponseData = any> = { | ||||||
|   data: null; |   data: null; | ||||||
|   error: AxiosError<ResponseData>; |   error: AxiosError<ResponseData>; | ||||||
|  |   response: AxiosResponse<ResponseData>; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export type FlatResponseData<T = any, ResponseData = any> = | export type FlatResponseData<T = any, ResponseData = any> = | ||||||
|   | FlatResponseSuccessData<T> |   | FlatResponseSuccessData<T, ResponseData> | ||||||
|   | FlatResponseFailData<ResponseData>; |   | FlatResponseFailData<ResponseData>; | ||||||
|  |  | ||||||
| export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> { | export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/color", |   "name": "@sa/color", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/hooks", |   "name": "@sa/hooks", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/materials", |   "name": "@sa/materials", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/fetch", |   "name": "@sa/fetch", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/scripts", |   "name": "@sa/scripts", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "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": "9.4.1", |     "bumpp": "9.5.2", | ||||||
|     "c12": "1.11.1", |     "c12": "1.11.2", | ||||||
|     "cac": "6.7.14", |     "cac": "6.7.14", | ||||||
|     "consola": "3.2.3", |     "consola": "3.2.3", | ||||||
|     "enquirer": "2.4.1", |     "enquirer": "2.4.1", | ||||||
|     "execa": "9.3.0", |     "execa": "9.3.1", | ||||||
|     "kolorist": "1.8.0", |     "kolorist": "1.8.0", | ||||||
|     "npm-check-updates": "16.14.20", |     "npm-check-updates": "17.1.1", | ||||||
|     "rimraf": "6.0.1" |     "rimraf": "6.0.1" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,13 +11,15 @@ export const locales = { | |||||||
|     }, |     }, | ||||||
|     gitCommitTypes: [ |     gitCommitTypes: [ | ||||||
|       ['feat', '新功能'], |       ['feat', '新功能'], | ||||||
|  |       ['feat-wip', '开发中的功能,比如某功能的部分代码'], | ||||||
|       ['fix', '修复Bug'], |       ['fix', '修复Bug'], | ||||||
|       ['docs', '只更新文档'], |       ['docs', '只涉及文档更新'], | ||||||
|  |       ['typo', '代码或文档勘误,比如错误拼写'], | ||||||
|       ['style', '修改代码风格,不影响代码含义的变更'], |       ['style', '修改代码风格,不影响代码含义的变更'], | ||||||
|       ['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'], |       ['refactor', '代码重构,既不修复 bug 也不添加功能的代码变更'], | ||||||
|       ['perf', '可提高性能的代码更改'], |       ['perf', '可提高性能的代码更改'], | ||||||
|       ['optimize', '优化代码质量的代码更改'], |       ['optimize', '优化代码质量的代码更改'], | ||||||
|       ['test', '添加缺失的测试或更正现有测'], |       ['test', '添加缺失的测试或更正现有测试'], | ||||||
|       ['build', '影响构建系统或外部依赖项的更改'], |       ['build', '影响构建系统或外部依赖项的更改'], | ||||||
|       ['ci', '对 CI 配置文件和脚本的更改'], |       ['ci', '对 CI 配置文件和脚本的更改'], | ||||||
|       ['chore', '没有修改src或测试文件的其他变更'], |       ['chore', '没有修改src或测试文件的其他变更'], | ||||||
| @@ -47,8 +49,10 @@ export const locales = { | |||||||
|     }, |     }, | ||||||
|     gitCommitTypes: [ |     gitCommitTypes: [ | ||||||
|       ['feat', 'A new feature'], |       ['feat', 'A new feature'], | ||||||
|  |       ['feat-wip', 'Features in development, such as partial code for a certain feature'], | ||||||
|       ['fix', 'A bug fix'], |       ['fix', 'A bug fix'], | ||||||
|       ['docs', 'Documentation only changes'], |       ['docs', 'Documentation only changes'], | ||||||
|  |       ['typo', 'Code or document corrections, such as spelling errors'], | ||||||
|       ['style', 'Changes that do not affect the meaning of the code'], |       ['style', 'Changes that do not affect the meaning of the code'], | ||||||
|       ['refactor', 'A code change that neither fixes a bug nor adds a feature'], |       ['refactor', 'A code change that neither fixes a bug nor adds a feature'], | ||||||
|       ['perf', 'A code change that improves performance'], |       ['perf', 'A code change that improves performance'], | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/uno-preset", |   "name": "@sa/uno-preset", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "@sa/utils", |   "name": "@sa/utils", | ||||||
|   "version": "1.3.1", |   "version": "1.3.5", | ||||||
|   "exports": { |   "exports": { | ||||||
|     ".": "./src/index.ts" |     ".": "./src/index.ts" | ||||||
|   }, |   }, | ||||||
|   | |||||||
							
								
								
									
										4438
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								public/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										3
									
								
								src-tauri/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | # Generated by Cargo | ||||||
|  | # will have compiled files and executables | ||||||
|  | /target/ | ||||||
							
								
								
									
										3664
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										26
									
								
								src-tauri/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | |||||||
|  | [package] | ||||||
|  | name = "app" | ||||||
|  | version = "0.1.0" | ||||||
|  | description = "A Tauri App" | ||||||
|  | authors = ["you"] | ||||||
|  | license = "" | ||||||
|  | repository = "" | ||||||
|  | default-run = "app" | ||||||
|  | edition = "2021" | ||||||
|  | rust-version = "1.60" | ||||||
|  |  | ||||||
|  | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  |  | ||||||
|  | [build-dependencies] | ||||||
|  | tauri-build = { version = "1.5.1", features = [] } | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | serde_json = "1.0" | ||||||
|  | serde = { version = "1.0", features = ["derive"] } | ||||||
|  | tauri = { version = "1.6.1", features = [] } | ||||||
|  |  | ||||||
|  | [features] | ||||||
|  | # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. | ||||||
|  | # If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. | ||||||
|  | # DO NOT REMOVE!! | ||||||
|  | custom-protocol = [ "tauri/custom-protocol" ] | ||||||
							
								
								
									
										3
									
								
								src-tauri/build.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | fn main() { | ||||||
|  |   tauri_build::build() | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/128x128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/128x128@2x.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square107x107Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square142x142Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 9.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square150x150Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 10 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square284x284Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 21 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square30x30Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square310x310Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 23 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square44x44Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square71x71Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/Square89x89Logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/StoreLogo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/icon.icns
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/icon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								src-tauri/icons/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 39 KiB | 
							
								
								
									
										8
									
								
								src-tauri/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | |||||||
|  | // Prevents additional console window on Windows in release, DO NOT REMOVE!! | ||||||
|  | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  |   tauri::Builder::default() | ||||||
|  |     .run(tauri::generate_context!()) | ||||||
|  |     .expect("error while running tauri application"); | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								src-tauri/tauri.conf.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,60 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "../node_modules/@tauri-apps/cli/schema.json", | ||||||
|  |   "build": { | ||||||
|  |     "beforeBuildCommand": "npm run build", | ||||||
|  |     "beforeDevCommand": "npm run dev", | ||||||
|  |     "devPath": "http://localhost:9527", | ||||||
|  |     "distDir": "../dist" | ||||||
|  |   }, | ||||||
|  |   "package": { | ||||||
|  |     "productName": "soybean-admin", | ||||||
|  |     "version": "1.0.0" | ||||||
|  |   }, | ||||||
|  |   "tauri": { | ||||||
|  |     "allowlist": { | ||||||
|  |       "all": false | ||||||
|  |     }, | ||||||
|  |     "bundle": { | ||||||
|  |       "active": true, | ||||||
|  |       "category": "DeveloperTool", | ||||||
|  |       "copyright": "", | ||||||
|  |       "deb": { | ||||||
|  |         "depends": [] | ||||||
|  |       }, | ||||||
|  |       "externalBin": [], | ||||||
|  |       "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], | ||||||
|  |       "identifier": "cn.soybeanjs.admin", | ||||||
|  |       "longDescription": "", | ||||||
|  |       "macOS": { | ||||||
|  |         "entitlements": null, | ||||||
|  |         "exceptionDomain": "", | ||||||
|  |         "frameworks": [], | ||||||
|  |         "providerShortName": null, | ||||||
|  |         "signingIdentity": null | ||||||
|  |       }, | ||||||
|  |       "resources": [], | ||||||
|  |       "shortDescription": "", | ||||||
|  |       "targets": "all", | ||||||
|  |       "windows": { | ||||||
|  |         "certificateThumbprint": null, | ||||||
|  |         "digestAlgorithm": "sha256", | ||||||
|  |         "timestampUrl": "" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "security": { | ||||||
|  |       "csp": null | ||||||
|  |     }, | ||||||
|  |     "updater": { | ||||||
|  |       "active": false | ||||||
|  |     }, | ||||||
|  |     "windows": [ | ||||||
|  |       { | ||||||
|  |         "fullscreen": false, | ||||||
|  |         "height": 768, | ||||||
|  |         "resizable": true, | ||||||
|  |         "title": "SoybeanAdmin", | ||||||
|  |         "width": 1366 | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,7 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { computed } from 'vue'; | import { computed } from 'vue'; | ||||||
| import { NConfigProvider, darkTheme } from 'naive-ui'; | import { NConfigProvider, darkTheme } from 'naive-ui'; | ||||||
|  | import type { WatermarkProps } from 'naive-ui'; | ||||||
| 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 { naiveDateLocales, naiveLocales } from './locales/naive'; | import { naiveDateLocales, naiveLocales } from './locales/naive'; | ||||||
| @@ -21,6 +22,22 @@ const naiveLocale = computed(() => { | |||||||
| const naiveDateLocale = computed(() => { | const naiveDateLocale = computed(() => { | ||||||
|   return naiveDateLocales[appStore.locale]; |   return naiveDateLocales[appStore.locale]; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | const watermarkProps = computed<WatermarkProps>(() => { | ||||||
|  |   return { | ||||||
|  |     content: themeStore.watermark?.text || 'SoybeanAdmin', | ||||||
|  |     cross: true, | ||||||
|  |     fullscreen: true, | ||||||
|  |     fontSize: 16, | ||||||
|  |     lineHeight: 16, | ||||||
|  |     width: 384, | ||||||
|  |     height: 384, | ||||||
|  |     xOffset: 12, | ||||||
|  |     yOffset: 60, | ||||||
|  |     rotate: -15, | ||||||
|  |     zIndex: 9999 | ||||||
|  |   }; | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| @@ -33,6 +50,7 @@ const naiveDateLocale = computed(() => { | |||||||
|   > |   > | ||||||
|     <AppProvider> |     <AppProvider> | ||||||
|       <RouterView class="bg-layout" /> |       <RouterView class="bg-layout" /> | ||||||
|  |       <NWatermark v-if="themeStore.watermark?.visible" v-bind="watermarkProps" /> | ||||||
|     </AppProvider> |     </AppProvider> | ||||||
|   </NConfigProvider> |   </NConfigProvider> | ||||||
| </template> | </template> | ||||||
|   | |||||||
| @@ -93,11 +93,15 @@ export function useRouterPush(inSetup = true) { | |||||||
|     return routerPushByKey('login', { query, params: { module } }); |     return routerPushByKey('login', { query, params: { module } }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** Redirect from login */ |   /** | ||||||
|   async function redirectFromLogin() { |    * Redirect from login | ||||||
|  |    * | ||||||
|  |    * @param [needRedirect=true] Whether to redirect after login. Default is `true` | ||||||
|  |    */ | ||||||
|  |   async function redirectFromLogin(needRedirect = true) { | ||||||
|     const redirect = route.value.query?.redirect as string; |     const redirect = route.value.query?.redirect as string; | ||||||
|  |  | ||||||
|     if (redirect) { |     if (needRedirect && redirect) { | ||||||
|       routerPush(redirect); |       routerPush(redirect); | ||||||
|     } else { |     } else { | ||||||
|       toHome(); |       toHome(); | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = | |||||||
| function useMixMenu() { | function useMixMenu() { | ||||||
|   const route = useRoute(); |   const route = useRoute(); | ||||||
|   const routeStore = useRouteStore(); |   const routeStore = useRouteStore(); | ||||||
|  |   const { selectedKey } = useMenu(); | ||||||
|  |  | ||||||
|   const activeFirstLevelMenuKey = ref(''); |   const activeFirstLevelMenuKey = ref(''); | ||||||
|  |  | ||||||
| @@ -16,12 +17,7 @@ function useMixMenu() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   function getActiveFirstLevelMenuKey() { |   function getActiveFirstLevelMenuKey() { | ||||||
|     const { hideInMenu, activeMenu } = route.meta; |     const [firstLevelRouteName] = selectedKey.value.split('_'); | ||||||
|     const name = route.name as string; |  | ||||||
|  |  | ||||||
|     const routeName = (hideInMenu ? activeMenu : name) || name; |  | ||||||
|  |  | ||||||
|     const [firstLevelRouteName] = routeName.split('_'); |  | ||||||
|  |  | ||||||
|     setActiveFirstLevelMenuKey(firstLevelRouteName); |     setActiveFirstLevelMenuKey(firstLevelRouteName); | ||||||
|   } |   } | ||||||
| @@ -68,3 +64,20 @@ function useMixMenu() { | |||||||
|     getActiveFirstLevelMenuKey |     getActiveFirstLevelMenuKey | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function useMenu() { | ||||||
|  |   const route = useRoute(); | ||||||
|  |  | ||||||
|  |   const selectedKey = computed(() => { | ||||||
|  |     const { hideInMenu, activeMenu } = route.meta; | ||||||
|  |     const name = route.name as string; | ||||||
|  |  | ||||||
|  |     const routeName = (hideInMenu ? activeMenu : name) || name; | ||||||
|  |  | ||||||
|  |     return routeName; | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     selectedKey | ||||||
|  |   }; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,26 +1,16 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { computed } from 'vue'; |  | ||||||
| import { useRoute } from 'vue-router'; |  | ||||||
| import { GLOBAL_HEADER_MENU_ID } from '@/constants/app'; | import { GLOBAL_HEADER_MENU_ID } from '@/constants/app'; | ||||||
| import { useRouteStore } from '@/store/modules/route'; | import { useRouteStore } from '@/store/modules/route'; | ||||||
| import { useRouterPush } from '@/hooks/common/router'; | import { useRouterPush } from '@/hooks/common/router'; | ||||||
|  | import { useMenu } from '../../../context'; | ||||||
|  |  | ||||||
| defineOptions({ | defineOptions({ | ||||||
|   name: 'HorizontalMenu' |   name: 'HorizontalMenu' | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const route = useRoute(); |  | ||||||
| const routeStore = useRouteStore(); | const routeStore = useRouteStore(); | ||||||
| const { routerPushByKeyWithMetaQuery } = useRouterPush(); | const { routerPushByKeyWithMetaQuery } = useRouterPush(); | ||||||
|  | const { selectedKey } = useMenu(); | ||||||
| const selectedKey = computed(() => { |  | ||||||
|   const { hideInMenu, activeMenu } = route.meta; |  | ||||||
|   const name = route.name as string; |  | ||||||
|  |  | ||||||
|   const routeName = (hideInMenu ? activeMenu : name) || name; |  | ||||||
|  |  | ||||||
|   return routeName; |  | ||||||
| }); |  | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
|   | |||||||
| @@ -1,31 +1,20 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { computed } from 'vue'; |  | ||||||
| import { useRoute } from 'vue-router'; |  | ||||||
| import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app'; | import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app'; | ||||||
| import { useAppStore } from '@/store/modules/app'; | import { useAppStore } from '@/store/modules/app'; | ||||||
| import { useThemeStore } from '@/store/modules/theme'; | import { useThemeStore } from '@/store/modules/theme'; | ||||||
| import { useRouterPush } from '@/hooks/common/router'; | import { useRouterPush } from '@/hooks/common/router'; | ||||||
| import FirstLevelMenu from '../components/first-level-menu.vue'; | import FirstLevelMenu from '../components/first-level-menu.vue'; | ||||||
| import { useMixMenuContext } from '../../../context'; | import { useMenu, useMixMenuContext } from '../../../context'; | ||||||
|  |  | ||||||
| defineOptions({ | defineOptions({ | ||||||
|   name: 'HorizontalMixMenu' |   name: 'HorizontalMixMenu' | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const route = useRoute(); |  | ||||||
| const appStore = useAppStore(); | const appStore = useAppStore(); | ||||||
| const themeStore = useThemeStore(); | const themeStore = useThemeStore(); | ||||||
| const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext(); |  | ||||||
| const { routerPushByKeyWithMetaQuery } = useRouterPush(); | const { routerPushByKeyWithMetaQuery } = useRouterPush(); | ||||||
|  | const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext(); | ||||||
| const selectedKey = computed(() => { | const { selectedKey } = useMenu(); | ||||||
|   const { hideInMenu, activeMenu } = route.meta; |  | ||||||
|   const name = route.name as string; |  | ||||||
|  |  | ||||||
|   const routeName = (hideInMenu ? activeMenu : name) || name; |  | ||||||
|  |  | ||||||
|   return routeName; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| function handleSelectMixMenu(menu: App.Global.Menu) { | function handleSelectMixMenu(menu: App.Global.Menu) { | ||||||
|   setActiveFirstLevelMenuKey(menu.key); |   setActiveFirstLevelMenuKey(menu.key); | ||||||
| @@ -56,9 +45,7 @@ function handleSelectMixMenu(menu: App.Global.Menu) { | |||||||
|       :theme-color="themeStore.themeColor" |       :theme-color="themeStore.themeColor" | ||||||
|       @select="handleSelectMixMenu" |       @select="handleSelectMixMenu" | ||||||
|       @toggle-sider-collapse="appStore.toggleSiderCollapse" |       @toggle-sider-collapse="appStore.toggleSiderCollapse" | ||||||
|     > |     /> | ||||||
|       <slot></slot> |  | ||||||
|     </FirstLevelMenu> |  | ||||||
|   </Teleport> |   </Teleport> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { computed, ref, watch } from 'vue'; | import { ref, watch } from 'vue'; | ||||||
| import { useRoute } from 'vue-router'; | import { useRoute } from 'vue-router'; | ||||||
| import type { RouteKey } from '@elegant-router/types'; | import type { RouteKey } from '@elegant-router/types'; | ||||||
| import { SimpleScrollbar } from '@sa/materials'; | import { SimpleScrollbar } from '@sa/materials'; | ||||||
| @@ -8,7 +8,7 @@ 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 { useRouteStore } from '@/store/modules/route'; | ||||||
| import { useRouterPush } from '@/hooks/common/router'; | import { useRouterPush } from '@/hooks/common/router'; | ||||||
| import { useMixMenuContext } from '../../../context'; | import { useMenu, useMixMenuContext } from '../../../context'; | ||||||
|  |  | ||||||
| defineOptions({ | defineOptions({ | ||||||
|   name: 'ReversedHorizontalMixMenu' |   name: 'ReversedHorizontalMixMenu' | ||||||
| @@ -18,6 +18,7 @@ const route = useRoute(); | |||||||
| const appStore = useAppStore(); | const appStore = useAppStore(); | ||||||
| const themeStore = useThemeStore(); | const themeStore = useThemeStore(); | ||||||
| const routeStore = useRouteStore(); | const routeStore = useRouteStore(); | ||||||
|  | const { routerPushByKeyWithMetaQuery } = useRouterPush(); | ||||||
| const { | const { | ||||||
|   firstLevelMenus, |   firstLevelMenus, | ||||||
|   childLevelMenus, |   childLevelMenus, | ||||||
| @@ -25,16 +26,7 @@ const { | |||||||
|   setActiveFirstLevelMenuKey, |   setActiveFirstLevelMenuKey, | ||||||
|   isActiveFirstLevelMenuHasChildren |   isActiveFirstLevelMenuHasChildren | ||||||
| } = useMixMenuContext(); | } = useMixMenuContext(); | ||||||
| const { routerPushByKeyWithMetaQuery } = useRouterPush(); | const { selectedKey } = useMenu(); | ||||||
|  |  | ||||||
| const selectedKey = computed(() => { |  | ||||||
|   const { hideInMenu, activeMenu } = route.meta; |  | ||||||
|   const name = route.name as string; |  | ||||||
|  |  | ||||||
|   const routeName = (hideInMenu ? activeMenu : name) || name; |  | ||||||
|  |  | ||||||
|   return routeName; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| function handleSelectMixMenu(key: RouteKey) { | function handleSelectMixMenu(key: RouteKey) { | ||||||
|   setActiveFirstLevelMenuKey(key); |   setActiveFirstLevelMenuKey(key); | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import { useThemeStore } from '@/store/modules/theme'; | |||||||
| import { useRouteStore } from '@/store/modules/route'; | import { useRouteStore } from '@/store/modules/route'; | ||||||
| import { useRouterPush } from '@/hooks/common/router'; | import { useRouterPush } from '@/hooks/common/router'; | ||||||
| import { GLOBAL_SIDER_MENU_ID } from '@/constants/app'; | import { GLOBAL_SIDER_MENU_ID } from '@/constants/app'; | ||||||
|  | import { useMenu } from '../../../context'; | ||||||
|  |  | ||||||
| defineOptions({ | defineOptions({ | ||||||
|   name: 'VerticalMenu' |   name: 'VerticalMenu' | ||||||
| @@ -17,18 +18,10 @@ const appStore = useAppStore(); | |||||||
| const themeStore = useThemeStore(); | const themeStore = useThemeStore(); | ||||||
| const routeStore = useRouteStore(); | const routeStore = useRouteStore(); | ||||||
| const { routerPushByKeyWithMetaQuery } = useRouterPush(); | const { routerPushByKeyWithMetaQuery } = useRouterPush(); | ||||||
|  | const { selectedKey } = useMenu(); | ||||||
|  |  | ||||||
| const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); | const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); | ||||||
|  |  | ||||||
| const selectedKey = computed(() => { |  | ||||||
|   const { hideInMenu, activeMenu } = route.meta; |  | ||||||
|   const name = route.name as string; |  | ||||||
|  |  | ||||||
|   const routeName = (hideInMenu ? activeMenu : name) || name; |  | ||||||
|  |  | ||||||
|   return routeName; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| const expandedKeys = ref<string[]>([]); | const expandedKeys = ref<string[]>([]); | ||||||
|  |  | ||||||
| function updateExpandedKeys() { | function updateExpandedKeys() { | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import { useRouteStore } from '@/store/modules/route'; | |||||||
| import { useRouterPush } from '@/hooks/common/router'; | import { useRouterPush } from '@/hooks/common/router'; | ||||||
| import { $t } from '@/locales'; | import { $t } from '@/locales'; | ||||||
| import { GLOBAL_SIDER_MENU_ID } from '@/constants/app'; | import { GLOBAL_SIDER_MENU_ID } from '@/constants/app'; | ||||||
| import { useMixMenuContext } from '../../../context'; | import { useMenu, useMixMenuContext } from '../../../context'; | ||||||
| import FirstLevelMenu from '../components/first-level-menu.vue'; | import FirstLevelMenu from '../components/first-level-menu.vue'; | ||||||
| import GlobalLogo from '../../global-logo/index.vue'; | import GlobalLogo from '../../global-logo/index.vue'; | ||||||
|  |  | ||||||
| @@ -31,6 +31,7 @@ const { | |||||||
|   getActiveFirstLevelMenuKey |   getActiveFirstLevelMenuKey | ||||||
|   // |   // | ||||||
| } = useMixMenuContext(); | } = useMixMenuContext(); | ||||||
|  | const { selectedKey } = useMenu(); | ||||||
|  |  | ||||||
| const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); | const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); | ||||||
|  |  | ||||||
| @@ -49,18 +50,12 @@ function handleSelectMixMenu(menu: App.Global.Menu) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function handleResetActiveMenu() { | function handleResetActiveMenu() { | ||||||
|   getActiveFirstLevelMenuKey(); |  | ||||||
|   setDrawerVisible(false); |   setDrawerVisible(false); | ||||||
|  |  | ||||||
|  |   if (!appStore.mixSiderFixed) { | ||||||
|  |     getActiveFirstLevelMenuKey(); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| const selectedKey = computed(() => { |  | ||||||
|   const { hideInMenu, activeMenu } = route.meta; |  | ||||||
|   const name = route.name as string; |  | ||||||
|  |  | ||||||
|   const routeName = (hideInMenu ? activeMenu : name) || name; |  | ||||||
|  |  | ||||||
|   return routeName; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| const expandedKeys = ref<string[]>([]); | const expandedKeys = ref<string[]>([]); | ||||||
|  |  | ||||||
| @@ -117,10 +112,8 @@ watch( | |||||||
|             <NMenu |             <NMenu | ||||||
|               v-model:expanded-keys="expandedKeys" |               v-model:expanded-keys="expandedKeys" | ||||||
|               mode="vertical" |               mode="vertical" | ||||||
|  |               :value="selectedKey" | ||||||
|               :options="childLevelMenus" |               :options="childLevelMenus" | ||||||
|               :collapsed="appStore.siderCollapse" |  | ||||||
|               :collapsed-width="themeStore.sider.collapsedWidth" |  | ||||||
|               :collapsed-icon-size="22" |  | ||||||
|               :inverted="inverted" |               :inverted="inverted" | ||||||
|               :indent="18" |               :indent="18" | ||||||
|               @update:value="routerPushByKeyWithMetaQuery" |               @update:value="routerPushByKeyWithMetaQuery" | ||||||
|   | |||||||
| @@ -25,6 +25,10 @@ function handleGrayscaleChange(value: boolean) { | |||||||
|   themeStore.setGrayscale(value); |   themeStore.setGrayscale(value); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function handleColourWeaknessChange(value: boolean) { | ||||||
|  |   themeStore.setColourWeakness(value); | ||||||
|  | } | ||||||
|  |  | ||||||
| const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layout.mode.includes('vertical')); | const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layout.mode.includes('vertical')); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| @@ -53,6 +57,9 @@ const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layo | |||||||
|     <SettingItem :label="$t('theme.grayscale')"> |     <SettingItem :label="$t('theme.grayscale')"> | ||||||
|       <NSwitch :value="themeStore.grayscale" @update:value="handleGrayscaleChange" /> |       <NSwitch :value="themeStore.grayscale" @update:value="handleGrayscaleChange" /> | ||||||
|     </SettingItem> |     </SettingItem> | ||||||
|  |     <SettingItem :label="$t('theme.colourWeakness')"> | ||||||
|  |       <NSwitch :value="themeStore.colourWeakness" @update:value="handleColourWeaknessChange" /> | ||||||
|  |     </SettingItem> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -101,6 +101,19 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra | |||||||
|     > |     > | ||||||
|       <NSwitch v-model:value="themeStore.footer.right" /> |       <NSwitch v-model:value="themeStore.footer.right" /> | ||||||
|     </SettingItem> |     </SettingItem> | ||||||
|  |     <SettingItem v-if="themeStore.watermark" 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')"> | ||||||
|  |       <NInput | ||||||
|  |         v-model:value="themeStore.watermark.text" | ||||||
|  |         autosize | ||||||
|  |         type="text" | ||||||
|  |         size="small" | ||||||
|  |         class="w-120px" | ||||||
|  |         placeholder="SoybeanAdmin" | ||||||
|  |       /> | ||||||
|  |     </SettingItem> | ||||||
|   </TransitionGroup> |   </TransitionGroup> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -65,6 +65,7 @@ const local: App.I18n.Schema = { | |||||||
|       auto: 'Follow System' |       auto: 'Follow System' | ||||||
|     }, |     }, | ||||||
|     grayscale: 'Grayscale', |     grayscale: 'Grayscale', | ||||||
|  |     colourWeakness: 'Colour Weakness', | ||||||
|     layoutMode: { |     layoutMode: { | ||||||
|       title: 'Layout Mode', |       title: 'Layout Mode', | ||||||
|       vertical: 'Vertical Menu Mode', |       vertical: 'Vertical Menu Mode', | ||||||
| @@ -134,6 +135,10 @@ const local: App.I18n.Schema = { | |||||||
|       height: 'Footer Height', |       height: 'Footer Height', | ||||||
|       right: 'Right Footer' |       right: 'Right Footer' | ||||||
|     }, |     }, | ||||||
|  |     watermark: { | ||||||
|  |       visible: 'Watermark Full Screen Visible', | ||||||
|  |       text: 'Watermark Text' | ||||||
|  |     }, | ||||||
|     themeDrawerTitle: 'Theme Configuration', |     themeDrawerTitle: 'Theme Configuration', | ||||||
|     pageFunTitle: 'Page Function', |     pageFunTitle: 'Page Function', | ||||||
|     configOperation: { |     configOperation: { | ||||||
|   | |||||||
| @@ -65,6 +65,7 @@ const local: App.I18n.Schema = { | |||||||
|       auto: '跟随系统' |       auto: '跟随系统' | ||||||
|     }, |     }, | ||||||
|     grayscale: '灰色模式', |     grayscale: '灰色模式', | ||||||
|  |     colourWeakness: '色弱模式', | ||||||
|     layoutMode: { |     layoutMode: { | ||||||
|       title: '布局模式', |       title: '布局模式', | ||||||
|       vertical: '左侧菜单模式', |       vertical: '左侧菜单模式', | ||||||
| @@ -134,6 +135,10 @@ const local: App.I18n.Schema = { | |||||||
|       height: '底部高度', |       height: '底部高度', | ||||||
|       right: '底部局右' |       right: '底部局右' | ||||||
|     }, |     }, | ||||||
|  |     watermark: { | ||||||
|  |       visible: '显示全屏水印', | ||||||
|  |       text: '水印文本' | ||||||
|  |     }, | ||||||
|     themeDrawerTitle: '主题配置', |     themeDrawerTitle: '主题配置', | ||||||
|     pageFunTitle: '页面功能', |     pageFunTitle: '页面功能', | ||||||
|     configOperation: { |     configOperation: { | ||||||
|   | |||||||
| @@ -1,8 +1,20 @@ | |||||||
| import { h } from 'vue'; | import { h } from 'vue'; | ||||||
|  | import type { App } from 'vue'; | ||||||
| import { NButton } from 'naive-ui'; | import { NButton } from 'naive-ui'; | ||||||
| import { $t } from '../locales'; | import { $t } from '@/locales'; | ||||||
|  |  | ||||||
|  | export function setupAppErrorHandle(app: App) { | ||||||
|  |   app.config.errorHandler = (err, vm, info) => { | ||||||
|  |     // eslint-disable-next-line no-console | ||||||
|  |     console.error(err, vm, info); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
| export function setupAppVersionNotification() { | export function setupAppVersionNotification() { | ||||||
|  |   const canAutoUpdateApp = import.meta.env.VITE_AUTOMATICALLY_DETECT_UPDATE === 'Y'; | ||||||
|  |  | ||||||
|  |   if (!canAutoUpdateApp) return; | ||||||
|  |  | ||||||
|   let isShow = false; |   let isShow = false; | ||||||
|  |  | ||||||
|   document.addEventListener('visibilitychange', async () => { |   document.addEventListener('visibilitychange', async () => { | ||||||
| @@ -52,9 +64,7 @@ export function setupAppVersionNotification() { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function getHtmlBuildTime() { | async function getHtmlBuildTime() { | ||||||
|   const baseURL = import.meta.env.VITE_BASE_URL; |   const res = await fetch(`/index.html?time=${Date.now()}`); | ||||||
|  |  | ||||||
|   const res = await fetch(`${baseURL}index.html`); |  | ||||||
|  |  | ||||||
|   const html = await res.text(); |   const html = await res.text(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -101,6 +101,9 @@ function transformElegantRouteToVueRoute( | |||||||
|         const singleLevelRoute: RouteRecordRaw = { |         const singleLevelRoute: RouteRecordRaw = { | ||||||
|           path, |           path, | ||||||
|           component: layouts[layout], |           component: layouts[layout], | ||||||
|  |           meta: { | ||||||
|  |             title: route.meta?.title || '' | ||||||
|  |           }, | ||||||
|           children: [ |           children: [ | ||||||
|             { |             { | ||||||
|               name, |               name, | ||||||
| @@ -132,7 +135,6 @@ function transformElegantRouteToVueRoute( | |||||||
|     return []; |     return []; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|    |  | ||||||
|   // add redirect to child |   // add redirect to child | ||||||
|   if (children?.length && !vueRoute.redirect) { |   if (children?.length && !vueRoute.redirect) { | ||||||
|     vueRoute.redirect = { |     vueRoute.redirect = { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { useAuthStore } from '@/store/modules/auth'; | |||||||
| import { $t } from '@/locales'; | import { $t } from '@/locales'; | ||||||
| import { localStg } from '@/utils/storage'; | import { localStg } from '@/utils/storage'; | ||||||
| import { getServiceBaseURL } from '@/utils/service'; | import { getServiceBaseURL } from '@/utils/service'; | ||||||
| import { handleRefreshToken, showErrorMsg } from './shared'; | import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared'; | ||||||
| import type { RequestInstanceState } from './type'; | import type { RequestInstanceState } from './type'; | ||||||
|  |  | ||||||
| const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; | const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; | ||||||
| @@ -19,12 +19,8 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     async onRequest(config) { |     async onRequest(config) { | ||||||
|       const { headers } = config; |       const Authorization = getAuthorization(); | ||||||
|  |       Object.assign(config.headers, { Authorization }); | ||||||
|       // set token |  | ||||||
|       const token = localStg.get('token'); |  | ||||||
|       const Authorization = token ? `Bearer ${token}` : null; |  | ||||||
|       Object.assign(headers, { Authorization }); |  | ||||||
|  |  | ||||||
|       return config; |       return config; | ||||||
|     }, |     }, | ||||||
| @@ -35,6 +31,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt | |||||||
|     }, |     }, | ||||||
|     async onBackendFail(response, instance) { |     async onBackendFail(response, instance) { | ||||||
|       const authStore = useAuthStore(); |       const authStore = useAuthStore(); | ||||||
|  |       const responseCode = String(response.data.code); | ||||||
|  |  | ||||||
|       function handleLogout() { |       function handleLogout() { | ||||||
|         authStore.resetStore(); |         authStore.resetStore(); | ||||||
| @@ -49,14 +46,14 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt | |||||||
|  |  | ||||||
|       // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page |       // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page | ||||||
|       const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; |       const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; | ||||||
|       if (logoutCodes.includes(response.data.code)) { |       if (logoutCodes.includes(responseCode)) { | ||||||
|         handleLogout(); |         handleLogout(); | ||||||
|         return null; |         return null; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal |       // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal | ||||||
|       const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; |       const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; | ||||||
|       if (modalLogoutCodes.includes(response.data.code) && !request.state.errMsgStack?.includes(response.data.msg)) { |       if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) { | ||||||
|         request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg]; |         request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg]; | ||||||
|  |  | ||||||
|         // prevent the user from refreshing the page |         // prevent the user from refreshing the page | ||||||
| @@ -82,15 +79,13 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt | |||||||
|       // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token |       // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token | ||||||
|       // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes` |       // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes` | ||||||
|       const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; |       const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; | ||||||
|       if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { |       if (expiredTokenCodes.includes(responseCode)) { | ||||||
|         request.state.isRefreshingToken = true; |         const success = await handleExpiredRequest(request.state); | ||||||
|  |         if (success) { | ||||||
|  |           const Authorization = getAuthorization(); | ||||||
|  |           Object.assign(response.config.headers, { Authorization }); | ||||||
|  |  | ||||||
|         const refreshConfig = await handleRefreshToken(response.config); |           return instance.request(response.config) as Promise<AxiosResponse>; | ||||||
|  |  | ||||||
|         request.state.isRefreshingToken = false; |  | ||||||
|  |  | ||||||
|         if (refreshConfig) { |  | ||||||
|           return instance.request(refreshConfig) as Promise<AxiosResponse>; |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -108,7 +103,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt | |||||||
|       // get backend error message and code |       // get backend error message and code | ||||||
|       if (error.code === BACKEND_ERROR_CODE) { |       if (error.code === BACKEND_ERROR_CODE) { | ||||||
|         message = error.response?.data?.msg || message; |         message = error.response?.data?.msg || message; | ||||||
|         backendErrorCode = error.response?.data?.code || ''; |         backendErrorCode = String(error.response?.data?.code || ''); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // the error message is displayed in the modal |       // the error message is displayed in the modal | ||||||
|   | |||||||
| @@ -1,34 +1,44 @@ | |||||||
| import type { AxiosRequestConfig } from 'axios'; |  | ||||||
| import { useAuthStore } from '@/store/modules/auth'; | import { useAuthStore } from '@/store/modules/auth'; | ||||||
| import { localStg } from '@/utils/storage'; | import { localStg } from '@/utils/storage'; | ||||||
| import { fetchRefreshToken } from '../api'; | import { fetchRefreshToken } from '../api'; | ||||||
| import type { RequestInstanceState } from './type'; | import type { RequestInstanceState } from './type'; | ||||||
|  |  | ||||||
| /** | export function getAuthorization() { | ||||||
|  * refresh token |   const token = localStg.get('token'); | ||||||
|  * |   const Authorization = token ? `Bearer ${token}` : null; | ||||||
|  * @param axiosConfig - request config when the token is expired |  | ||||||
|  */ |   return Authorization; | ||||||
| export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) { | } | ||||||
|  |  | ||||||
|  | /** refresh token */ | ||||||
|  | async function handleRefreshToken() { | ||||||
|   const { resetStore } = useAuthStore(); |   const { resetStore } = useAuthStore(); | ||||||
|  |  | ||||||
|   const refreshToken = localStg.get('refreshToken') || ''; |   const rToken = localStg.get('refreshToken') || ''; | ||||||
|   const { error, data } = await fetchRefreshToken(refreshToken); |   const { error, data } = await fetchRefreshToken(rToken); | ||||||
|   if (!error) { |   if (!error) { | ||||||
|     localStg.set('token', data.token); |     localStg.set('token', data.token); | ||||||
|     localStg.set('refreshToken', data.refreshToken); |     localStg.set('refreshToken', data.refreshToken); | ||||||
|  |     return true; | ||||||
|     const config = { ...axiosConfig }; |  | ||||||
|     if (config.headers) { |  | ||||||
|       config.headers.Authorization = data.token; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return config; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   resetStore(); |   resetStore(); | ||||||
|  |  | ||||||
|   return null; |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function handleExpiredRequest(state: RequestInstanceState) { | ||||||
|  |   if (!state.refreshTokenFn) { | ||||||
|  |     state.refreshTokenFn = handleRefreshToken(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const success = await state.refreshTokenFn; | ||||||
|  |  | ||||||
|  |   setTimeout(() => { | ||||||
|  |     state.refreshTokenFn = null; | ||||||
|  |   }, 1000); | ||||||
|  |  | ||||||
|  |   return success; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function showErrorMsg(state: RequestInstanceState, message: string) { | export function showErrorMsg(state: RequestInstanceState, message: string) { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| export interface RequestInstanceState { | export interface RequestInstanceState { | ||||||
|   /** whether the request is refreshing token */ |   /** whether the request is refreshing token */ | ||||||
|   isRefreshingToken: boolean; |   refreshTokenFn: Promise<boolean> | null; | ||||||
|   /** the request error message stack */ |   /** the request error message stack */ | ||||||
|   errMsgStack: string[]; |   errMsgStack: string[]; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -71,9 +71,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => { | |||||||
|       if (pass) { |       if (pass) { | ||||||
|         await routeStore.initAuthRoute(); |         await routeStore.initAuthRoute(); | ||||||
|  |  | ||||||
|         if (redirect) { |         await redirectFromLogin(redirect); | ||||||
|           await redirectFromLogin(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (routeStore.isInitAuthRoute) { |         if (routeStore.isInitAuthRoute) { | ||||||
|           window.$notification?.success({ |           window.$notification?.success({ | ||||||
|   | |||||||
| @@ -281,15 +281,25 @@ export function getBreadcrumbsByRoute( | |||||||
|   const key = route.name as string; |   const key = route.name as string; | ||||||
|   const activeKey = route.meta?.activeMenu; |   const activeKey = route.meta?.activeMenu; | ||||||
|  |  | ||||||
|   const menuKey = activeKey || key; |  | ||||||
|  |  | ||||||
|   for (const menu of menus) { |   for (const menu of menus) { | ||||||
|     if (menu.key === menuKey) { |     if (menu.key === key) { | ||||||
|       const breadcrumbMenu = menuKey !== activeKey ? menu : getGlobalMenuByBaseRoute(route); |       const breadcrumbMenu = menu; | ||||||
|  |  | ||||||
|       return [transformMenuToBreadcrumb(breadcrumbMenu)]; |       return [transformMenuToBreadcrumb(breadcrumbMenu)]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (menu.key === activeKey) { | ||||||
|  |       const ROUTE_DEGREE_SPLITTER = '_'; | ||||||
|  |  | ||||||
|  |       const parentKey = key.split(ROUTE_DEGREE_SPLITTER).slice(0, -1).join(ROUTE_DEGREE_SPLITTER); | ||||||
|  |  | ||||||
|  |       const breadcrumbMenu = getGlobalMenuByBaseRoute(route); | ||||||
|  |       if (parentKey !== activeKey) { | ||||||
|  |         return [transformMenuToBreadcrumb(breadcrumbMenu)]; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return [transformMenuToBreadcrumb(menu), transformMenuToBreadcrumb(breadcrumbMenu)]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (menu.children?.length) { |     if (menu.children?.length) { | ||||||
|       const result = getBreadcrumbsByRoute(route, menu.children); |       const result = getBreadcrumbsByRoute(route, menu.children); | ||||||
|       if (result.length > 0) { |       if (result.length > 0) { | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ import { | |||||||
|   createThemeToken, |   createThemeToken, | ||||||
|   getNaiveTheme, |   getNaiveTheme, | ||||||
|   initThemeSettings, |   initThemeSettings, | ||||||
|   toggleCssDarkMode, |   toggleAuxiliaryColorModes, | ||||||
|   toggleGrayscaleMode |   toggleCssDarkMode | ||||||
| } from './shared'; | } from './shared'; | ||||||
|  |  | ||||||
| /** Theme store */ | /** Theme store */ | ||||||
| @@ -33,6 +33,9 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { | |||||||
|   /** grayscale mode */ |   /** grayscale mode */ | ||||||
|   const grayscaleMode = computed(() => settings.value.grayscale); |   const grayscaleMode = computed(() => settings.value.grayscale); | ||||||
|  |  | ||||||
|  |   /** colourWeakness mode */ | ||||||
|  |   const colourWeaknessMode = computed(() => settings.value.colourWeakness); | ||||||
|  |  | ||||||
|   /** Theme colors */ |   /** Theme colors */ | ||||||
|   const themeColors = computed(() => { |   const themeColors = computed(() => { | ||||||
|     const { themeColor, otherColor, isInfoFollowPrimary } = settings.value; |     const { themeColor, otherColor, isInfoFollowPrimary } = settings.value; | ||||||
| @@ -79,6 +82,15 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { | |||||||
|     settings.value.grayscale = isGrayscale; |     settings.value.grayscale = isGrayscale; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Set colourWeakness value | ||||||
|  |    * | ||||||
|  |    * @param isColourWeakness | ||||||
|  |    */ | ||||||
|  |   function setColourWeakness(isColourWeakness: boolean) { | ||||||
|  |     settings.value.colourWeakness = isColourWeakness; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** Toggle theme scheme */ |   /** Toggle theme scheme */ | ||||||
|   function toggleThemeScheme() { |   function toggleThemeScheme() { | ||||||
|     const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto']; |     const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto']; | ||||||
| @@ -167,9 +179,9 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     watch( |     watch( | ||||||
|       grayscaleMode, |       [grayscaleMode, colourWeaknessMode], | ||||||
|       val => { |       val => { | ||||||
|         toggleGrayscaleMode(val); |         toggleAuxiliaryColorModes(val[0], val[1]); | ||||||
|       }, |       }, | ||||||
|       { immediate: true } |       { immediate: true } | ||||||
|     ); |     ); | ||||||
| @@ -197,6 +209,7 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { | |||||||
|     naiveTheme, |     naiveTheme, | ||||||
|     settingsJson, |     settingsJson, | ||||||
|     setGrayscale, |     setGrayscale, | ||||||
|  |     setColourWeakness, | ||||||
|     resetStore, |     resetStore, | ||||||
|     setThemeScheme, |     setThemeScheme, | ||||||
|     toggleThemeScheme, |     toggleThemeScheme, | ||||||
|   | |||||||
| @@ -180,20 +180,16 @@ export function toggleCssDarkMode(darkMode = false) { | |||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Toggle grayscale mode |  * Toggle auxiliary color modes | ||||||
|  * |  * | ||||||
|  * @param grayscaleMode Is grayscale mode |  * @param grayscaleMode | ||||||
|  |  * @param colourWeakness | ||||||
|  */ |  */ | ||||||
| export function toggleGrayscaleMode(grayscaleMode = false) { | export function toggleAuxiliaryColorModes(grayscaleMode = false, colourWeakness = false) { | ||||||
|   const GRAYSCALE_CLASS = 'grayscale'; |   const htmlElement = document.documentElement; | ||||||
|  |   htmlElement.style.filter = [grayscaleMode ? 'grayscale(100%)' : '', colourWeakness ? 'invert(80%)' : ''] | ||||||
|   const { add, remove } = toggleHtmlClass(GRAYSCALE_CLASS); |     .filter(Boolean) | ||||||
|  |     .join(' '); | ||||||
|   if (grayscaleMode) { |  | ||||||
|     add(); |  | ||||||
|   } else { |  | ||||||
|     remove(); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type NaiveColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active'; | type NaiveColorScene = '' | 'Suppl' | 'Hover' | 'Pressed' | 'Active'; | ||||||
|   | |||||||
| @@ -11,7 +11,3 @@ body, | |||||||
| html { | html { | ||||||
|   overflow-x: hidden; |   overflow-x: hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
| html.grayscale { |  | ||||||
|   filter: grayscale(100%); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| export const themeSettings: App.Theme.ThemeSetting = { | export const themeSettings: App.Theme.ThemeSetting = { | ||||||
|   themeScheme: 'light', |   themeScheme: 'light', | ||||||
|   grayscale: false, |   grayscale: false, | ||||||
|  |   colourWeakness: false, | ||||||
|   recommendColor: false, |   recommendColor: false, | ||||||
|   themeColor: '#646cff', |   themeColor: '#646cff', | ||||||
|   otherColor: { |   otherColor: { | ||||||
| @@ -48,6 +49,10 @@ export const themeSettings: App.Theme.ThemeSetting = { | |||||||
|     height: 48, |     height: 48, | ||||||
|     right: true |     right: true | ||||||
|   }, |   }, | ||||||
|  |   watermark: { | ||||||
|  |     visible: false, | ||||||
|  |     text: 'SoybeanAdmin' | ||||||
|  |   }, | ||||||
|   tokens: { |   tokens: { | ||||||
|     light: { |     light: { | ||||||
|       colors: { |       colors: { | ||||||
|   | |||||||
							
								
								
									
										141
									
								
								src/typings/api.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -20,6 +20,9 @@ declare namespace Api { | |||||||
|       records: T[]; |       records: T[]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** common search params of table */ | ||||||
|  |     type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * enable status |      * enable status | ||||||
|      * |      * | ||||||
| @@ -81,142 +84,4 @@ declare namespace Api { | |||||||
|       home: import('@elegant-router/types').LastLevelRouteKey; |       home: import('@elegant-router/types').LastLevelRouteKey; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * namespace SystemManage |  | ||||||
|    * |  | ||||||
|    * backend api module: "systemManage" |  | ||||||
|    */ |  | ||||||
|   namespace SystemManage { |  | ||||||
|     type CommonSearchParams = Pick<Common.PaginatingCommonParams, 'current' | 'size'>; |  | ||||||
|  |  | ||||||
|     /** role */ |  | ||||||
|     type Role = Common.CommonRecord<{ |  | ||||||
|       /** role name */ |  | ||||||
|       roleName: string; |  | ||||||
|       /** role code */ |  | ||||||
|       roleCode: string; |  | ||||||
|       /** role description */ |  | ||||||
|       roleDesc: string; |  | ||||||
|     }>; |  | ||||||
|  |  | ||||||
|     /** role search params */ |  | ||||||
|     type RoleSearchParams = CommonType.RecordNullable< |  | ||||||
|       Pick<Api.SystemManage.Role, 'roleName' | 'roleCode' | 'status'> & CommonSearchParams |  | ||||||
|     >; |  | ||||||
|  |  | ||||||
|     /** role list */ |  | ||||||
|     type RoleList = Common.PaginatingQueryRecord<Role>; |  | ||||||
|  |  | ||||||
|     /** all role */ |  | ||||||
|     type AllRole = Pick<Role, 'id' | 'roleName' | 'roleCode'>; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * user gender |  | ||||||
|      * |  | ||||||
|      * - "1": "male" |  | ||||||
|      * - "2": "female" |  | ||||||
|      */ |  | ||||||
|     type UserGender = '1' | '2'; |  | ||||||
|  |  | ||||||
|     /** user */ |  | ||||||
|     type User = Common.CommonRecord<{ |  | ||||||
|       /** user name */ |  | ||||||
|       userName: string; |  | ||||||
|       /** user gender */ |  | ||||||
|       userGender: UserGender | null; |  | ||||||
|       /** user nick name */ |  | ||||||
|       nickName: string; |  | ||||||
|       /** user phone */ |  | ||||||
|       userPhone: string; |  | ||||||
|       /** user email */ |  | ||||||
|       userEmail: string; |  | ||||||
|       /** user role code collection */ |  | ||||||
|       userRoles: string[]; |  | ||||||
|     }>; |  | ||||||
|  |  | ||||||
|     /** user search params */ |  | ||||||
|     type UserSearchParams = CommonType.RecordNullable< |  | ||||||
|       Pick<Api.SystemManage.User, 'userName' | 'userGender' | 'nickName' | 'userPhone' | 'userEmail' | 'status'> & |  | ||||||
|         CommonSearchParams |  | ||||||
|     >; |  | ||||||
|  |  | ||||||
|     /** user list */ |  | ||||||
|     type UserList = Common.PaginatingQueryRecord<User>; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * menu type |  | ||||||
|      * |  | ||||||
|      * - "1": directory |  | ||||||
|      * - "2": menu |  | ||||||
|      */ |  | ||||||
|     type MenuType = '1' | '2'; |  | ||||||
|  |  | ||||||
|     type MenuButton = { |  | ||||||
|       /** |  | ||||||
|        * button code |  | ||||||
|        * |  | ||||||
|        * it can be used to control the button permission |  | ||||||
|        */ |  | ||||||
|       code: string; |  | ||||||
|       /** button description */ |  | ||||||
|       desc: string; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * icon type |  | ||||||
|      * |  | ||||||
|      * - "1": iconify icon |  | ||||||
|      * - "2": local icon |  | ||||||
|      */ |  | ||||||
|     type IconType = '1' | '2'; |  | ||||||
|  |  | ||||||
|     type MenuPropsOfRoute = Pick< |  | ||||||
|       import('vue-router').RouteMeta, |  | ||||||
|       | 'i18nKey' |  | ||||||
|       | 'keepAlive' |  | ||||||
|       | 'constant' |  | ||||||
|       | 'order' |  | ||||||
|       | 'href' |  | ||||||
|       | 'hideInMenu' |  | ||||||
|       | 'activeMenu' |  | ||||||
|       | 'multiTab' |  | ||||||
|       | 'fixedIndexInTab' |  | ||||||
|       | 'query' |  | ||||||
|     >; |  | ||||||
|  |  | ||||||
|     type Menu = Common.CommonRecord<{ |  | ||||||
|       /** parent menu id */ |  | ||||||
|       parentId: number; |  | ||||||
|       /** menu type */ |  | ||||||
|       menuType: MenuType; |  | ||||||
|       /** menu name */ |  | ||||||
|       menuName: string; |  | ||||||
|       /** route name */ |  | ||||||
|       routeName: string; |  | ||||||
|       /** route path */ |  | ||||||
|       routePath: string; |  | ||||||
|       /** component */ |  | ||||||
|       component?: string; |  | ||||||
|       /** iconify icon name or local icon name */ |  | ||||||
|       icon: string; |  | ||||||
|       /** icon type */ |  | ||||||
|       iconType: IconType; |  | ||||||
|       /** buttons */ |  | ||||||
|       buttons?: MenuButton[] | null; |  | ||||||
|       /** children menu */ |  | ||||||
|       children?: Menu[] | null; |  | ||||||
|     }> & |  | ||||||
|       MenuPropsOfRoute; |  | ||||||
|  |  | ||||||
|     /** menu list */ |  | ||||||
|     type MenuList = Common.PaginatingQueryRecord<Menu>; |  | ||||||
|  |  | ||||||
|     type MenuTree = { |  | ||||||
|       id: number; |  | ||||||
|       label: string; |  | ||||||
|       pId: number; |  | ||||||
|       children?: MenuTree[]; |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								src/typings/app.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -10,6 +10,8 @@ declare namespace App { | |||||||
|       themeScheme: UnionKey.ThemeScheme; |       themeScheme: UnionKey.ThemeScheme; | ||||||
|       /** grayscale mode */ |       /** grayscale mode */ | ||||||
|       grayscale: boolean; |       grayscale: boolean; | ||||||
|  |       /** colour weakness mode */ | ||||||
|  |       colourWeakness: boolean; | ||||||
|       /** Whether to recommend color */ |       /** Whether to recommend color */ | ||||||
|       recommendColor: boolean; |       recommendColor: boolean; | ||||||
|       /** Theme color */ |       /** Theme color */ | ||||||
| @@ -93,6 +95,13 @@ declare namespace App { | |||||||
|         /** Whether float the footer to the right when the layout is 'horizontal-mix' */ |         /** Whether float the footer to the right when the layout is 'horizontal-mix' */ | ||||||
|         right: boolean; |         right: boolean; | ||||||
|       }; |       }; | ||||||
|  |       /** Watermark */ | ||||||
|  |       watermark?: { | ||||||
|  |         /** Whether to show the watermark */ | ||||||
|  |         visible: boolean; | ||||||
|  |         /** Watermark text */ | ||||||
|  |         text: string; | ||||||
|  |       }; | ||||||
|       /** define some theme settings tokens, will transform to css variables */ |       /** define some theme settings tokens, will transform to css variables */ | ||||||
|       tokens: { |       tokens: { | ||||||
|         light: ThemeSettingToken; |         light: ThemeSettingToken; | ||||||
| @@ -332,6 +341,7 @@ declare namespace App { | |||||||
|       theme: { |       theme: { | ||||||
|         themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>; |         themeSchema: { title: string } & Record<UnionKey.ThemeScheme, string>; | ||||||
|         grayscale: string; |         grayscale: string; | ||||||
|  |         colourWeakness: string; | ||||||
|         layoutMode: { title: string; reverseHorizontalMix: string } & Record<UnionKey.ThemeLayoutMode, string>; |         layoutMode: { title: string; reverseHorizontalMix: string } & Record<UnionKey.ThemeLayoutMode, string>; | ||||||
|         recommendColor: string; |         recommendColor: string; | ||||||
|         recommendColorDesc: string; |         recommendColorDesc: string; | ||||||
| @@ -372,6 +382,10 @@ declare namespace App { | |||||||
|           height: string; |           height: string; | ||||||
|           right: string; |           right: string; | ||||||
|         }; |         }; | ||||||
|  |         watermark: { | ||||||
|  |           visible: string; | ||||||
|  |           text: string; | ||||||
|  |         }; | ||||||
|         themeDrawerTitle: string; |         themeDrawerTitle: string; | ||||||
|         pageFunTitle: string; |         pageFunTitle: string; | ||||||
|         configOperation: { |         configOperation: { | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/typings/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -84,6 +84,7 @@ declare module 'vue' { | |||||||
|     NThing: typeof import('naive-ui')['NThing'] |     NThing: typeof import('naive-ui')['NThing'] | ||||||
|     NTooltip: typeof import('naive-ui')['NTooltip'] |     NTooltip: typeof import('naive-ui')['NTooltip'] | ||||||
|     NTree: typeof import('naive-ui')['NTree'] |     NTree: typeof import('naive-ui')['NTree'] | ||||||
|  |     NWatermark: typeof import('naive-ui')['NWatermark'] | ||||||
|     PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] |     PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] | ||||||
|     ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] |     ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] | ||||||
|     RouterLink: typeof import('vue-router')['RouterLink'] |     RouterLink: typeof import('vue-router')['RouterLink'] | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/typings/env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -103,6 +103,8 @@ declare namespace Env { | |||||||
|     readonly VITE_ICONIFY_URL?: string; |     readonly VITE_ICONIFY_URL?: string; | ||||||
|     /** Used to differentiate storage across different domains */ |     /** Used to differentiate storage across different domains */ | ||||||
|     readonly VITE_STORAGE_PREFIX?: string; |     readonly VITE_STORAGE_PREFIX?: string; | ||||||
|  |     /** Whether to automatically detect updates after configuring application packaging */ | ||||||
|  |     readonly VITE_AUTOMATICALLY_DETECT_UPDATE?: CommonType.YesOrNo; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/typings/naive-ui.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -26,7 +26,7 @@ declare namespace NaiveUI { | |||||||
|  |  | ||||||
|   type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>; |   type TableColumn<T> = TableColumnWithKey<T> | DataTableSelectionColumn<T> | DataTableExpandColumn<T>; | ||||||
|  |  | ||||||
|   type TableApiFn<T = any, R = Api.SystemManage.CommonSearchParams> = ( |   type TableApiFn<T = any, R = Api.Common.CommonSearchParams> = ( | ||||||
|     params: R |     params: R | ||||||
|   ) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>; |   ) => Promise<FlatResponseData<Api.Common.PaginatingQueryRecord<T>>>; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import json5 from 'json5'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Create service config by current env |  * Create service config by current env | ||||||
|  * |  * | ||||||
| @@ -8,10 +10,10 @@ export function createServiceConfig(env: Env.ImportMeta) { | |||||||
|  |  | ||||||
|   let other = {} as Record<App.Service.OtherBaseURLKey, string>; |   let other = {} as Record<App.Service.OtherBaseURLKey, string>; | ||||||
|   try { |   try { | ||||||
|     other = JSON.parse(VITE_OTHER_SERVICE_BASE_URL); |     other = json5.parse(VITE_OTHER_SERVICE_BASE_URL); | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     // eslint-disable-next-line no-console |     // eslint-disable-next-line no-console | ||||||
|     console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid JSON string'); |     console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid json5 string'); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const httpConfig: App.Service.SimpleServiceConfig = { |   const httpConfig: App.Service.SimpleServiceConfig = { | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16)); | |||||||
|  |  | ||||||
| <template> | <template> | ||||||
|   <NSpace vertical :size="16"> |   <NSpace vertical :size="16"> | ||||||
|     <NAlert :title="$t('common.warning')" type="warning" closable> |     <NAlert :title="$t('common.warning')" type="warning"> | ||||||
|       {{ $t('page.home.branchDesc') }} |       {{ $t('page.home.branchDesc') }} | ||||||
|     </NAlert> |     </NAlert> | ||||||
|     <HeaderBanner /> |     <HeaderBanner /> | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ export default defineConfig(configEnv => { | |||||||
|  |  | ||||||
|   const buildTime = getBuildTime(); |   const buildTime = getBuildTime(); | ||||||
|  |  | ||||||
|  |   const enableProxy = configEnv.command === 'serve' && !configEnv.isPreview; | ||||||
|  |  | ||||||
|   return { |   return { | ||||||
|     base: viteEnv.VITE_BASE_URL, |     base: viteEnv.VITE_BASE_URL, | ||||||
|     resolve: { |     resolve: { | ||||||
| @@ -32,7 +34,7 @@ export default defineConfig(configEnv => { | |||||||
|       host: '0.0.0.0', |       host: '0.0.0.0', | ||||||
|       port: 9527, |       port: 9527, | ||||||
|       open: true, |       open: true, | ||||||
|       proxy: createViteProxy(viteEnv, configEnv.command === 'serve'), |       proxy: createViteProxy(viteEnv, enableProxy), | ||||||
|       fs: { |       fs: { | ||||||
|         cachedChecks: false |         cachedChecks: false | ||||||
|       } |       } | ||||||
|   | |||||||