version: v4.6.2-beta

This commit is contained in:
XiaoDaiGua-Ray 2024-01-25 17:12:32 +08:00
parent bb61081ddd
commit cc0577eb8f
42 changed files with 1054 additions and 419 deletions

View File

@ -1,5 +1,73 @@
# CHANGE LOG # CHANGE LOG
## 4.6.2
为了支持同域名下同时部署多套系统,重构了 `cache` 工具包支持前缀配置。并且暴露重构所有的缓存 `key` 配置项,为了便捷的进行私有数据缓存。
## Feats
- 更新 `vue` 版本至 `3.4.15`,核心性能优化更新
- `cache` 工具包相关
- 重构 `getStorage` 方法,支持前缀配置
- 重构 `setStorage` 方法,支持前缀配置
- 重构 `hasStorage` 方法,支持前缀配置
- 重构 `removeStorage` 方法 - 支持前缀配置 - 更新预留 `key`,现在更新为:`__all__`, `__all_sessionStorage__`, `__all_localStorage__`
> 默认不启用该功能,如果需要启用,可以在调用 `cache` 包方法的时候手动配置 `prefix` 属性为 `true`,默认会读取 `prefixKey` 配置项,如果未设置则会尝试读取 `APP_CATCH_KEY_PREFIX`。并且 `useStorage` 之类的第三方工具库并未集成该方法。
- 暴露所有缓存 `key`,允许自定义所有缓存 `key`
- `designConfig` 配置相关
- `appNaiveUIThemeOverrides` 配置项支持按照 `dark`, `light` 两个主题分别配置
- 新增 `appNaiveUIThemeOverridesCommon` 配置项
- 优化 `GlobalSearch` 组件样式
- 更新搜索按钮样式,由图标变为按钮样式(`GlobalSearchButton`
- 更新 `appThemeColors` 色盘
- `hasMenuIcon` 更名为 `createMenuIcon`
- 默认绑定过渡动画更改为 `scale`
- 更新内容区域背景色(`$layoutContentBackgroundColorDark``$layoutContentBackgroundColorLight`
- 更新底部区域背景色(`$layoutFooterBackgroundColorDark``$layoutFooterBackgroundColorLight`
- `RouteMeta` 配置项相关
- 新增 `extra` 配置项,用于配置标记
```ts
import { t } from '@/hooks'
import { LAYOUT } from '@/router/constant'
import type { AppRouteRecordRaw } from '@/router/type'
const cacheDemo: AppRouteRecordRaw = {
// ...your route config,
meta: {
// ...other meta config,
extra: 'new',
},
}
// 当然你也可以配置 extra 为一个对象
AppMenuExtraOptions {
extraLabel?: string
extraIcon?: string | VNode
extraType?: TagProps['type']
}
const cacheDemo: AppRouteRecordRaw = {
...your route config,
meta: {
...other meta config,
extra: {
extraLabel: 'new',
extraIcon: 'icon' || <Icon />,
extraType: 'primary' || 'success' || 'warning' || 'error' || 'info' || 'default',
},
},
}
```
## Fixes
- 修复 `naive-ui` 修改主题色不能准确的同步到全局的问题
## 4.6.1 ## 4.6.1
## Feats ## Feats

View File

@ -1,7 +1,7 @@
{ {
"name": "ray-template", "name": "ray-template",
"private": false, "private": false,
"version": "4.6.1", "version": "4.6.2-beta",
"type": "module", "type": "module",
"engines": { "engines": {
"node": "^18.0.0 || >=20.0.0", "node": "^18.0.0 || >=20.0.0",
@ -48,7 +48,7 @@
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0", "pinia-plugin-persistedstate": "^3.2.0",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"vue": "^3.4.14", "vue": "^3.4.15",
"vue-hooks-plus": "1.8.5", "vue-hooks-plus": "1.8.5",
"vue-i18n": "^9.9.0", "vue-i18n": "^9.9.0",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",

196
pnpm-lock.yaml generated
View File

@ -7,7 +7,7 @@ settings:
dependencies: dependencies:
'@vueuse/core': '@vueuse/core':
specifier: ^10.7.1 specifier: ^10.7.1
version: 10.7.1(vue@3.4.14) version: 10.7.1(vue@3.4.15)
awesome-qr: awesome-qr:
specifier: 2.1.5-rc.0 specifier: 2.1.5-rc.0
version: 2.1.5-rc.0 version: 2.1.5-rc.0
@ -43,10 +43,10 @@ dependencies:
version: 1.1.0 version: 1.1.0
naive-ui: naive-ui:
specifier: ^2.37.3 specifier: ^2.37.3
version: 2.37.3(vue@3.4.14) version: 2.37.3(vue@3.4.15)
pinia: pinia:
specifier: ^2.1.7 specifier: ^2.1.7
version: 2.1.7(typescript@5.2.2)(vue@3.4.14) version: 2.1.7(typescript@5.2.2)(vue@3.4.15)
pinia-plugin-persistedstate: pinia-plugin-persistedstate:
specifier: ^3.2.0 specifier: ^3.2.0
version: 3.2.0(pinia@2.1.7) version: 3.2.0(pinia@2.1.7)
@ -54,17 +54,17 @@ dependencies:
specifier: ^1.6.0 specifier: ^1.6.0
version: 1.6.0 version: 1.6.0
vue: vue:
specifier: ^3.4.14 specifier: ^3.4.15
version: 3.4.14(typescript@5.2.2) version: 3.4.15(typescript@5.2.2)
vue-hooks-plus: vue-hooks-plus:
specifier: 1.8.5 specifier: 1.8.5
version: 1.8.5(vue@3.4.14) version: 1.8.5(vue@3.4.15)
vue-i18n: vue-i18n:
specifier: ^9.9.0 specifier: ^9.9.0
version: 9.9.0(vue@3.4.14) version: 9.9.0(vue@3.4.15)
vue-router: vue-router:
specifier: ^4.2.5 specifier: ^4.2.5
version: 4.2.5(vue@3.4.14) version: 4.2.5(vue@3.4.15)
xlsx: xlsx:
specifier: ^0.18.5 specifier: ^0.18.5
version: 0.18.5 version: 0.18.5
@ -108,10 +108,10 @@ devDependencies:
version: 6.5.0(eslint@8.52.0)(typescript@5.2.2) version: 6.5.0(eslint@8.52.0)(typescript@5.2.2)
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3(vite@5.0.11)(vue@3.4.14) version: 5.0.3(vite@5.0.11)(vue@3.4.15)
'@vitejs/plugin-vue-jsx': '@vitejs/plugin-vue-jsx':
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0(vite@5.0.11)(vue@3.4.14) version: 3.1.0(vite@5.0.11)(vue@3.4.15)
'@vue-hooks-plus/resolvers': '@vue-hooks-plus/resolvers':
specifier: 1.2.4 specifier: 1.2.4
version: 1.2.4(vue-hooks-plus@1.8.5) version: 1.2.4(vue-hooks-plus@1.8.5)
@ -183,7 +183,7 @@ devDependencies:
version: 0.16.6(@vueuse/core@10.7.1) version: 0.16.6(@vueuse/core@10.7.1)
unplugin-vue-components: unplugin-vue-components:
specifier: ^0.25.2 specifier: ^0.25.2
version: 0.25.2(vue@3.4.14) version: 0.25.2(vue@3.4.15)
vite: vite:
specifier: ^5.0.11 specifier: ^5.0.11
version: 5.0.11(@types/node@20.4.7)(sass@1.69.5) version: 5.0.11(@types/node@20.4.7)(sass@1.69.5)
@ -927,12 +927,12 @@ packages:
css-render: 0.15.12 css-render: 0.15.12
dev: false dev: false
/@css-render/vue3-ssr@0.15.12(vue@3.4.14): /@css-render/vue3-ssr@0.15.12(vue@3.4.15):
resolution: {integrity: sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==} resolution: {integrity: sha512-AQLGhhaE0F+rwybRCkKUdzBdTEM/5PZBYy+fSYe1T9z9+yxMuV/k7ZRqa4M69X+EI1W8pa4kc9Iq2VjQkZx4rg==}
peerDependencies: peerDependencies:
vue: ^3.0.11 vue: ^3.0.11
dependencies: dependencies:
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
dev: false dev: false
/@emotion/hash@0.8.0: /@emotion/hash@0.8.0:
@ -1430,7 +1430,7 @@ packages:
magic-string: 0.30.5 magic-string: 0.30.5
mlly: 1.4.1 mlly: 1.4.1
source-map-js: 1.0.2 source-map-js: 1.0.2
vue-i18n: 9.9.0(vue@3.4.14) vue-i18n: 9.9.0(vue@3.4.15)
yaml-eslint-parser: 1.2.2 yaml-eslint-parser: 1.2.2
dev: true dev: true
@ -1492,7 +1492,7 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
unplugin: 1.4.0 unplugin: 1.4.0
vue-i18n: 9.9.0(vue@3.4.14) vue-i18n: 9.9.0(vue@3.4.15)
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- supports-color - supports-color
@ -2111,7 +2111,7 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true dev: true
/@vitejs/plugin-vue-jsx@3.1.0(vite@5.0.11)(vue@3.4.14): /@vitejs/plugin-vue-jsx@3.1.0(vite@5.0.11)(vue@3.4.15):
resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==} resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -2122,12 +2122,12 @@ packages:
'@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.6) '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.6)
'@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.6) '@vue/babel-plugin-jsx': 1.1.5(@babel/core@7.23.6)
vite: 5.0.11(@types/node@20.4.7)(sass@1.69.5) vite: 5.0.11(@types/node@20.4.7)(sass@1.69.5)
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@vitejs/plugin-vue@5.0.3(vite@5.0.11)(vue@3.4.14): /@vitejs/plugin-vue@5.0.3(vite@5.0.11)(vue@3.4.15):
resolution: {integrity: sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==} resolution: {integrity: sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
peerDependencies: peerDependencies:
@ -2135,7 +2135,7 @@ packages:
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 5.0.11(@types/node@20.4.7)(sass@1.69.5) vite: 5.0.11(@types/node@20.4.7)(sass@1.69.5)
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
dev: true dev: true
/@volar/language-core@1.10.1: /@volar/language-core@1.10.1:
@ -2163,7 +2163,7 @@ packages:
vue-hooks-plus: ^1.5.2 vue-hooks-plus: ^1.5.2
dependencies: dependencies:
local-pkg: 0.4.3 local-pkg: 0.4.3
vue-hooks-plus: 1.8.5(vue@3.4.14) vue-hooks-plus: 1.8.5(vue@3.4.15)
dev: true dev: true
/@vue/babel-helper-vue-transform-on@1.1.5: /@vue/babel-helper-vue-transform-on@1.1.5:
@ -2207,11 +2207,11 @@ packages:
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
/@vue/compiler-core@3.4.14: /@vue/compiler-core@3.4.15:
resolution: {integrity: sha512-ro4Zzl/MPdWs7XwxT7omHRxAjMbDFRZEEjD+2m3NBf8YzAe3HuoSEZosXQo+m1GQ1G3LQ1LdmNh1RKTYe+ssEg==} resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
dependencies: dependencies:
'@babel/parser': 7.23.6 '@babel/parser': 7.23.6
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
entities: 4.5.0 entities: 4.5.0
estree-walker: 2.0.2 estree-walker: 2.0.2
source-map-js: 1.0.2 source-map-js: 1.0.2
@ -2230,11 +2230,11 @@ packages:
'@vue/shared': 3.3.8 '@vue/shared': 3.3.8
dev: true dev: true
/@vue/compiler-dom@3.4.14: /@vue/compiler-dom@3.4.15:
resolution: {integrity: sha512-nOZTY+veWNa0DKAceNWxorAbWm0INHdQq7cejFaWM1WYnoNSJbSEKYtE7Ir6lR/+mo9fttZpPVI9ZFGJ1juUEQ==} resolution: {integrity: sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==}
dependencies: dependencies:
'@vue/compiler-core': 3.4.14 '@vue/compiler-core': 3.4.15
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
/@vue/compiler-sfc@3.3.8: /@vue/compiler-sfc@3.3.8:
resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==} resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==}
@ -2251,14 +2251,14 @@ packages:
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
/@vue/compiler-sfc@3.4.14: /@vue/compiler-sfc@3.4.15:
resolution: {integrity: sha512-1vHc9Kv1jV+YBZC/RJxQJ9JCxildTI+qrhtDh6tPkR1O8S+olBUekimY0km0ZNn8nG1wjtFAe9XHij+YLR8cRQ==} resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==}
dependencies: dependencies:
'@babel/parser': 7.23.6 '@babel/parser': 7.23.6
'@vue/compiler-core': 3.4.14 '@vue/compiler-core': 3.4.15
'@vue/compiler-dom': 3.4.14 '@vue/compiler-dom': 3.4.15
'@vue/compiler-ssr': 3.4.14 '@vue/compiler-ssr': 3.4.15
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
estree-walker: 2.0.2 estree-walker: 2.0.2
magic-string: 0.30.5 magic-string: 0.30.5
postcss: 8.4.33 postcss: 8.4.33
@ -2271,11 +2271,11 @@ packages:
'@vue/shared': 3.3.8 '@vue/shared': 3.3.8
dev: true dev: true
/@vue/compiler-ssr@3.4.14: /@vue/compiler-ssr@3.4.15:
resolution: {integrity: sha512-bXT6+oAGlFjTYVOTtFJ4l4Jab1wjsC0cfSfOe2B4Z0N2vD2zOBSQ9w694RsCfhjk+bC2DY5Gubb1rHZVii107Q==} resolution: {integrity: sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==}
dependencies: dependencies:
'@vue/compiler-dom': 3.4.14 '@vue/compiler-dom': 3.4.15
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
/@vue/devtools-api@6.5.1: /@vue/devtools-api@6.5.1:
resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==} resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==}
@ -2350,32 +2350,32 @@ packages:
'@vue/shared': 3.3.8 '@vue/shared': 3.3.8
dev: true dev: true
/@vue/reactivity@3.4.14: /@vue/reactivity@3.4.15:
resolution: {integrity: sha512-xRYwze5Q4tK7tT2J4uy4XLhK/AIXdU5EBUu9PLnIHcOKXO0uyXpNNMzlQKuq7B+zwtq6K2wuUL39pHA6ZQzObw==} resolution: {integrity: sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==}
dependencies: dependencies:
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
/@vue/runtime-core@3.4.14: /@vue/runtime-core@3.4.15:
resolution: {integrity: sha512-qu+NMkfujCoZL6cfqK5NOfxgXJROSlP2ZPs4CTcVR+mLrwl4TtycF5Tgo0QupkdBL+2kigc6EsJlTcuuZC1NaQ==} resolution: {integrity: sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==}
dependencies: dependencies:
'@vue/reactivity': 3.4.14 '@vue/reactivity': 3.4.15
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
/@vue/runtime-dom@3.4.14: /@vue/runtime-dom@3.4.15:
resolution: {integrity: sha512-B85XmcR4E7XsirEHVqhmy4HPbRT9WLFWV9Uhie3OapV9m1MEN9+Er6hmUIE6d8/l2sUygpK9RstFM2bmHEUigA==} resolution: {integrity: sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==}
dependencies: dependencies:
'@vue/runtime-core': 3.4.14 '@vue/runtime-core': 3.4.15
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
csstype: 3.1.3 csstype: 3.1.3
/@vue/server-renderer@3.4.14(vue@3.4.14): /@vue/server-renderer@3.4.15(vue@3.4.15):
resolution: {integrity: sha512-pwSKXQfYdJBTpvWHGEYI+akDE18TXAiLcGn+Q/2Fj8wQSHWztoo7PSvfMNqu6NDhp309QXXbPFEGCU5p85HqkA==} resolution: {integrity: sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==}
peerDependencies: peerDependencies:
vue: 3.4.14 vue: 3.4.15
dependencies: dependencies:
'@vue/compiler-ssr': 3.4.14 '@vue/compiler-ssr': 3.4.15
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
/@vue/shared@3.3.13: /@vue/shared@3.3.13:
resolution: {integrity: sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==} resolution: {integrity: sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==}
@ -2385,8 +2385,8 @@ packages:
resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==} resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==}
dev: true dev: true
/@vue/shared@3.4.14: /@vue/shared@3.4.15:
resolution: {integrity: sha512-nmi3BtLpvqXAWoRZ6HQ+pFJOHBU4UnH3vD3opgmwXac7vhaHKA9nj1VeGjMggdB9eLtW83eHyPCmOU1qzdsC7Q==} resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==}
/@vue/typescript@1.8.8(typescript@5.2.2): /@vue/typescript@1.8.8(typescript@5.2.2):
resolution: {integrity: sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==} resolution: {integrity: sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==}
@ -2397,13 +2397,13 @@ packages:
- typescript - typescript
dev: true dev: true
/@vueuse/core@10.7.1(vue@3.4.14): /@vueuse/core@10.7.1(vue@3.4.15):
resolution: {integrity: sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==} resolution: {integrity: sha512-74mWHlaesJSWGp1ihg76vAnfVq9NTv1YT0SYhAQ6zwFNdBkkP+CKKJmVOEHcdSnLXCXYiL5e7MaewblfiYLP7g==}
dependencies: dependencies:
'@types/web-bluetooth': 0.0.20 '@types/web-bluetooth': 0.0.20
'@vueuse/metadata': 10.7.1 '@vueuse/metadata': 10.7.1
'@vueuse/shared': 10.7.1(vue@3.4.14) '@vueuse/shared': 10.7.1(vue@3.4.15)
vue-demi: 0.14.6(vue@3.4.14) vue-demi: 0.14.6(vue@3.4.15)
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
@ -2411,10 +2411,10 @@ packages:
/@vueuse/metadata@10.7.1: /@vueuse/metadata@10.7.1:
resolution: {integrity: sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==} resolution: {integrity: sha512-jX8MbX5UX067DYVsbtrmKn6eG6KMcXxLRLlurGkZku5ZYT3vxgBjui2zajvUZ18QLIjrgBkFRsu7CqTAg18QFw==}
/@vueuse/shared@10.7.1(vue@3.4.14): /@vueuse/shared@10.7.1(vue@3.4.15):
resolution: {integrity: sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==} resolution: {integrity: sha512-v0jbRR31LSgRY/C5i5X279A/WQjD6/JsMzGa+eqt658oJ75IvQXAeONmwvEMrvJQKnRElq/frzBR7fhmWY5uLw==}
dependencies: dependencies:
vue-demi: 0.14.6(vue@3.4.14) vue-demi: 0.14.6(vue@3.4.15)
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
@ -5978,13 +5978,13 @@ packages:
minimatch: 3.1.2 minimatch: 3.1.2
dev: true dev: true
/naive-ui@2.37.3(vue@3.4.14): /naive-ui@2.37.3(vue@3.4.15):
resolution: {integrity: sha512-aUkHFXVIluSi8Me+npbcsdv1NYhVMj5t9YaruoCESlqmfqspj+R2QHEVXkTtUI1kQwVrABMCtAGq/wountqjZA==} resolution: {integrity: sha512-aUkHFXVIluSi8Me+npbcsdv1NYhVMj5t9YaruoCESlqmfqspj+R2QHEVXkTtUI1kQwVrABMCtAGq/wountqjZA==}
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
dependencies: dependencies:
'@css-render/plugin-bem': 0.15.12(css-render@0.15.12) '@css-render/plugin-bem': 0.15.12(css-render@0.15.12)
'@css-render/vue3-ssr': 0.15.12(vue@3.4.14) '@css-render/vue3-ssr': 0.15.12(vue@3.4.15)
'@types/katex': 0.16.7 '@types/katex': 0.16.7
'@types/lodash': 4.14.202 '@types/lodash': 4.14.202
'@types/lodash-es': 4.17.11 '@types/lodash-es': 4.17.11
@ -5999,10 +5999,10 @@ packages:
lodash-es: 4.17.21 lodash-es: 4.17.21
seemly: 0.3.8 seemly: 0.3.8
treemate: 0.3.11 treemate: 0.3.11
vdirs: 0.1.8(vue@3.4.14) vdirs: 0.1.8(vue@3.4.15)
vooks: 0.2.12(vue@3.4.14) vooks: 0.2.12(vue@3.4.15)
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
vueuc: 0.4.58(vue@3.4.14) vueuc: 0.4.58(vue@3.4.15)
dev: false dev: false
/nan@2.17.0: /nan@2.17.0:
@ -6389,10 +6389,10 @@ packages:
peerDependencies: peerDependencies:
pinia: ^2.0.0 pinia: ^2.0.0
dependencies: dependencies:
pinia: 2.1.7(typescript@5.2.2)(vue@3.4.14) pinia: 2.1.7(typescript@5.2.2)(vue@3.4.15)
dev: false dev: false
/pinia@2.1.7(typescript@5.2.2)(vue@3.4.14): /pinia@2.1.7(typescript@5.2.2)(vue@3.4.15):
resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==}
peerDependencies: peerDependencies:
'@vue/composition-api': ^1.4.0 '@vue/composition-api': ^1.4.0
@ -6406,8 +6406,8 @@ packages:
dependencies: dependencies:
'@vue/devtools-api': 6.5.1 '@vue/devtools-api': 6.5.1
typescript: 5.2.2 typescript: 5.2.2
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
vue-demi: 0.14.6(vue@3.4.14) vue-demi: 0.14.6(vue@3.4.15)
dev: false dev: false
/pkg-types@1.0.3: /pkg-types@1.0.3:
@ -7680,7 +7680,7 @@ packages:
dependencies: dependencies:
'@antfu/utils': 0.7.6 '@antfu/utils': 0.7.6
'@rollup/pluginutils': 5.0.4 '@rollup/pluginutils': 5.0.4
'@vueuse/core': 10.7.1(vue@3.4.14) '@vueuse/core': 10.7.1(vue@3.4.15)
fast-glob: 3.3.1 fast-glob: 3.3.1
local-pkg: 0.4.3 local-pkg: 0.4.3
magic-string: 0.30.5 magic-string: 0.30.5
@ -7691,7 +7691,7 @@ packages:
- rollup - rollup
dev: true dev: true
/unplugin-vue-components@0.25.2(vue@3.4.14): /unplugin-vue-components@0.25.2(vue@3.4.15):
resolution: {integrity: sha512-OVmLFqILH6w+eM8fyt/d/eoJT9A6WO51NZLf1vC5c1FZ4rmq2bbGxTy8WP2Jm7xwFdukaIdv819+UI7RClPyCA==} resolution: {integrity: sha512-OVmLFqILH6w+eM8fyt/d/eoJT9A6WO51NZLf1vC5c1FZ4rmq2bbGxTy8WP2Jm7xwFdukaIdv819+UI7RClPyCA==}
engines: {node: '>=14'} engines: {node: '>=14'}
peerDependencies: peerDependencies:
@ -7714,7 +7714,7 @@ packages:
minimatch: 9.0.3 minimatch: 9.0.3
resolve: 1.22.5 resolve: 1.22.5
unplugin: 1.4.0 unplugin: 1.4.0
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- supports-color - supports-color
@ -7805,13 +7805,13 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: true dev: true
/vdirs@0.1.8(vue@3.4.14): /vdirs@0.1.8(vue@3.4.15):
resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==} resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==}
peerDependencies: peerDependencies:
vue: ^3.0.11 vue: ^3.0.11
dependencies: dependencies:
evtd: 0.2.4 evtd: 0.2.4
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
dev: false dev: false
/vite-plugin-cdn2@0.15.2: /vite-plugin-cdn2@0.15.2:
@ -7994,16 +7994,16 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/vooks@0.2.12(vue@3.4.14): /vooks@0.2.12(vue@3.4.15):
resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==} resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==}
peerDependencies: peerDependencies:
vue: ^3.0.0 vue: ^3.0.0
dependencies: dependencies:
evtd: 0.2.4 evtd: 0.2.4
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
dev: false dev: false
/vue-demi@0.14.6(vue@3.4.14): /vue-demi@0.14.6(vue@3.4.15):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
@ -8015,7 +8015,7 @@ packages:
'@vue/composition-api': '@vue/composition-api':
optional: true optional: true
dependencies: dependencies:
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
/vue-eslint-parser@9.3.1(eslint@8.52.0): /vue-eslint-parser@9.3.1(eslint@8.52.0):
resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==} resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
@ -8035,7 +8035,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/vue-hooks-plus@1.8.5(vue@3.4.14): /vue-hooks-plus@1.8.5(vue@3.4.15):
resolution: {integrity: sha512-cIatTWz6QQcoSCDn7jadQ3zMr799FmNiHyb59yUvR7Ws5KDJ/KdIMHHx/b0XDKzbGhQ61kcJ78zJKAKhOV0pWw==} resolution: {integrity: sha512-cIatTWz6QQcoSCDn7jadQ3zMr799FmNiHyb59yUvR7Ws5KDJ/KdIMHHx/b0XDKzbGhQ61kcJ78zJKAKhOV0pWw==}
peerDependencies: peerDependencies:
vue: ^3.2.25 vue: ^3.2.25
@ -8047,9 +8047,9 @@ packages:
qs: 6.11.2 qs: 6.11.2
query-string: 7.1.3 query-string: 7.1.3
screenfull: 5.2.0 screenfull: 5.2.0
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
/vue-i18n@9.9.0(vue@3.4.14): /vue-i18n@9.9.0(vue@3.4.15):
resolution: {integrity: sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==} resolution: {integrity: sha512-xQ5SxszUAqK5n84N+uUyHH/PiQl9xZ24FOxyAaNonmOQgXeN+rD9z/6DStOpOxNFQn4Cgcquot05gZc+CdOujA==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
peerDependencies: peerDependencies:
@ -8058,15 +8058,15 @@ packages:
'@intlify/core-base': 9.9.0 '@intlify/core-base': 9.9.0
'@intlify/shared': 9.9.0 '@intlify/shared': 9.9.0
'@vue/devtools-api': 6.5.1 '@vue/devtools-api': 6.5.1
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
/vue-router@4.2.5(vue@3.4.14): /vue-router@4.2.5(vue@3.4.15):
resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==} resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==}
peerDependencies: peerDependencies:
vue: ^3.2.0 vue: ^3.2.0
dependencies: dependencies:
'@vue/devtools-api': 6.5.1 '@vue/devtools-api': 6.5.1
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
dev: false dev: false
/vue-template-compiler@2.7.14: /vue-template-compiler@2.7.14:
@ -8088,34 +8088,34 @@ packages:
typescript: 5.2.2 typescript: 5.2.2
dev: true dev: true
/vue@3.4.14(typescript@5.2.2): /vue@3.4.15(typescript@5.2.2):
resolution: {integrity: sha512-Rop5Al/ZcBbBz+KjPZaZDgHDX0kUP4duEzDbm+1o91uxYUNmJrZSBuegsNIJvUGy+epLevNRNhLjm08VKTgGyw==} resolution: {integrity: sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@vue/compiler-dom': 3.4.14 '@vue/compiler-dom': 3.4.15
'@vue/compiler-sfc': 3.4.14 '@vue/compiler-sfc': 3.4.15
'@vue/runtime-dom': 3.4.14 '@vue/runtime-dom': 3.4.15
'@vue/server-renderer': 3.4.14(vue@3.4.14) '@vue/server-renderer': 3.4.15(vue@3.4.15)
'@vue/shared': 3.4.14 '@vue/shared': 3.4.15
typescript: 5.2.2 typescript: 5.2.2
/vueuc@0.4.58(vue@3.4.14): /vueuc@0.4.58(vue@3.4.15):
resolution: {integrity: sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==} resolution: {integrity: sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==}
peerDependencies: peerDependencies:
vue: ^3.0.11 vue: ^3.0.11
dependencies: dependencies:
'@css-render/vue3-ssr': 0.15.12(vue@3.4.14) '@css-render/vue3-ssr': 0.15.12(vue@3.4.15)
'@juggle/resize-observer': 3.4.0 '@juggle/resize-observer': 3.4.0
css-render: 0.15.12 css-render: 0.15.12
evtd: 0.2.4 evtd: 0.2.4
seemly: 0.3.8 seemly: 0.3.8
vdirs: 0.1.8(vue@3.4.14) vdirs: 0.1.8(vue@3.4.15)
vooks: 0.2.12(vue@3.4.14) vooks: 0.2.12(vue@3.4.15)
vue: 3.4.14(typescript@5.2.2) vue: 3.4.15(typescript@5.2.2)
dev: false dev: false
/webidl-conversions@3.0.1: /webidl-conversions@3.0.1:

View File

@ -17,10 +17,16 @@
*/ */
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import { APP_CATCH_KEY } from '@/app-config'
const appLockScreen = useStorage('isAppLockScreen', false, sessionStorage, { const appLockScreen = useStorage(
mergeDefaults: true, APP_CATCH_KEY.isAppLockScreen,
}) false,
sessionStorage,
{
mergeDefaults: true,
},
)
const useAppLockScreen = () => { const useAppLockScreen = () => {
const setLockAppScreen = (bool: boolean) => { const setLockAppScreen = (bool: boolean) => {

View File

@ -18,6 +18,7 @@ import {
getStorage, getStorage,
} from '@/utils' } from '@/utils'
import { useSettingGetters } from '@/store' import { useSettingGetters } from '@/store'
import { APP_CATCH_KEY } from '@/app-config'
import type { SettingState } from '@/store/modules/setting/type' import type { SettingState } from '@/store/modules/setting/type'
@ -34,7 +35,7 @@ export default defineComponent({
const body = document.body const body = document.body
const primaryColorOverride = getStorage<SettingState>( const primaryColorOverride = getStorage<SettingState>(
'piniaSettingStore', APP_CATCH_KEY.appPiniaSettingStore,
'localStorage', 'localStorage',
) // 获取缓存 naive ui 配置项 ) // 获取缓存 naive ui 配置项

View File

@ -19,15 +19,18 @@ import { RModal } from '@/components'
import { getStorage, setStorage } from '@/utils' import { getStorage, setStorage } from '@/utils'
import { useSigningActions } from '@/store' import { useSigningActions } from '@/store'
import { APP_CATCH_KEY } from '@/app-config'
export default defineComponent({ export default defineComponent({
name: 'AppVersionProvider', name: 'AppVersionProvider',
setup() { setup() {
const storageKey = 'appVersionProvider'
const { const {
pkg: { version }, pkg: { version },
} = __APP_CFG__ } = __APP_CFG__
const cacheVersion = getStorage<string>(storageKey, 'localStorage') const cacheVersion = getStorage<string>(
APP_CATCH_KEY.appVersionProvider,
'localStorage',
)
const modalShow = ref(false) const modalShow = ref(false)
const { logout } = useSigningActions() const { logout } = useSigningActions()
@ -36,7 +39,11 @@ export default defineComponent({
if (version !== cacheVersion) { if (version !== cacheVersion) {
modalShow.value = true modalShow.value = true
setStorage<string>(storageKey, version, 'localStorage') setStorage<string>(
APP_CATCH_KEY.appVersionProvider,
version,
'localStorage',
)
} }
} }

View File

@ -77,27 +77,53 @@ export const APP_MENU_CONFIG: Readonly<AppMenuConfig> = {
menuAccordion: false, menuAccordion: false,
} }
/**
*
* key
* key 使 getStorage setStorage key
*
*
*
* @example
* export const APP_CATCH_KEY_PREFIX: = 'ray-template:'
*
* 'ray-template:signing'
*/
export const APP_CATCH_KEY_PREFIX = ''
/** /**
* *
* key * key
* , key
* *
* : * :
* - signing: 登陆信息缓存 key * - signing: 登陆信息缓存 key
* - localeLanguage: 国际化默认缓存 key * - localeLanguage: 国际化默认缓存 key
* - token: token key * - token: token key
* - appMenuKey: 菜单缓存 key * - appMenuKey: 菜单缓存 key
* - appPiniaSettingStore: pinia setting store key
* - appPiniaKeepAliveStore: pinia keep alive store key
* - appPiniaMenuStore: pinia menu store key
* - appPiniaSigningStore: pinia signing store key
* - appVersionProvider: 版本信息缓存 key
*/ */
export const APP_CATCH_KEY = { export const APP_CATCH_KEY = {
signing: 'signing', signing: 'signing',
localeLanguage: 'localeLanguage', localeLanguage: 'localeLanguage',
token: 'token', token: 'token',
appMenuKey: 'menuKey', appMenuKey: 'menuKey',
appPiniaSettingStore: 'piniaSettingStore',
appPiniaKeepAliveStore: 'piniaKeepAliveStore',
appPiniaMenuStore: 'piniaMenuStore',
appPiniaSigningStore: 'piniaSigningStore',
appVersionProvider: 'appVersionProvider',
isAppLockScreen: 'isAppLockScreen',
} as const } as const
/** /**
* *
* *
* `transform`
* : `transform-fade-bottom`
*/ */
export const CONTENT_TRANSITION_OPTIONS = [ export const CONTENT_TRANSITION_OPTIONS = [
{ {

View File

@ -21,12 +21,11 @@ export const APP_THEME: AppTheme = {
*/ */
appThemeColors: [ appThemeColors: [
'#2d8cf0', '#2d8cf0',
'#0960bd', '#3f9eff',
'#536dfe', '#ff42bc',
'#ff5c93',
'#ee4f12', '#ee4f12',
'#9c27b0', '#a6e4f7',
'#ff9800', '#dbcb02',
'#18A058', '#18A058',
], ],
/** 系统主题色 */ /** 系统主题色 */
@ -60,7 +59,14 @@ export const APP_THEME: AppTheme = {
* } * }
* ``` * ```
*/ */
appNaiveUIThemeOverrides: {}, appNaiveUIThemeOverrides: {
dark: {},
light: {},
},
appNaiveUIThemeOverridesCommon: {
dark: {},
light: {},
},
/** /**
* *
* echart * echart

View File

@ -11,6 +11,29 @@
import { useSettingActions, useSettingGetters } from '@/store' import { useSettingActions, useSettingGetters } from '@/store'
import { useI18n } from '@/hooks' import { useI18n } from '@/hooks'
import { APP_THEME } from '@/app-config'
const setThemeOverrides = (theme: boolean) => {
const { getPrimaryColorOverride } = useSettingGetters()
const { updateSettingState } = useSettingActions()
updateSettingState(
'primaryColorOverride',
theme
? Object.assign(
{},
getPrimaryColorOverride.value,
APP_THEME.appNaiveUIThemeOverrides.dark,
APP_THEME.appNaiveUIThemeOverridesCommon.dark,
)
: Object.assign(
{},
getPrimaryColorOverride.value,
APP_THEME.appNaiveUIThemeOverrides.light,
APP_THEME.appNaiveUIThemeOverridesCommon.light,
),
)
}
export const useTheme = () => { export const useTheme = () => {
/** /**
@ -45,6 +68,7 @@ export const useTheme = () => {
const { updateSettingState } = useSettingActions() const { updateSettingState } = useSettingActions()
updateSettingState('appTheme', true) updateSettingState('appTheme', true)
setThemeOverrides(true)
} }
/** /**
@ -58,6 +82,7 @@ export const useTheme = () => {
const { updateSettingState } = useSettingActions() const { updateSettingState } = useSettingActions()
updateSettingState('appTheme', false) updateSettingState('appTheme', false)
setThemeOverrides(false)
} }
/** /**
@ -77,6 +102,7 @@ export const useTheme = () => {
const { updateSettingState } = useSettingActions() const { updateSettingState } = useSettingActions()
updateSettingState('appTheme', !theme) updateSettingState('appTheme', !theme)
setThemeOverrides(!theme)
} }
return { return {

View File

@ -468,6 +468,7 @@ export default defineComponent({
align="center" align="center"
justify="space-between" justify="space-between"
inline inline
size={[16, 0]}
> >
<RIcon <RIcon
name="expanded" name="expanded"
@ -557,7 +558,7 @@ export default defineComponent({
align="center" align="center"
inline inline
wrap={false} wrap={false}
size={[6, 6]} size={[8, 0]}
> >
<RIcon <RIcon
name="expanded" name="expanded"

View File

@ -11,102 +11,48 @@ $globalSearchWidth: 650px;
box-sizing: border-box; box-sizing: border-box;
& .global-search__card { & .global-search__card {
width: $globalSearchWidth;
border-radius: 6px; border-radius: 6px;
padding: 12px; min-width: 800px;
& .ray-icon { & .ray-icon {
color: var(--ray-theme-primary-color); color: var(--ray-theme-primary-color);
} }
& .global-search__card-header { & .n-card__action {
margin-bottom: 12px; padding: 16px 12px 12px 12px;
} }
& .global-search__card-content { & .n-card__content {
height: auto; min-height: 115px;
max-height: calc(100% - 98px); }
padding: 8px 0;
& .global-search__empty { & .content-item {
margin: 24px; padding: 12px;
} border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s var(--r-bezier);
border: 1px solid var(--n-border-color);
& .global-search__empty-content { & .content-item-icon {
font-size: 18px; @include flexCenter;
color: #969faf;
font-weight: 600;
& .ray-icon {
color: #969faf;
}
}
& .content-item {
padding: 12px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s var(--r-bezier);
& .content-item-icon {
@include flexCenter;
}
} }
} }
& .global-search__card-footer { & .item-icon {
width: 100%; min-width: 24px;
border-radius: 4px;
& .card-footer__tip-wrapper { border: 1px solid var(--n-border-color);
display: flex; padding: 3px 6px;
align-items: center; background-color: var(--n-action-color);
margin-top: 24px;
& .tip-wrapper-item {
display: flex;
align-items: center;
& .item-icon {
display: flex;
align-items: center;
margin-right: 4px;
& span {
color: var(--ray-theme-primary-color);
}
}
}
}
} }
}
}
}
.global-search--dark { & .item-icon,
@include useAppTheme('dark') { & .item-label {
& .global-search__card { transform: scale(0.75);
background-color: #242424; font-weight: bolder;
}
& .global-search__card-content .content-item { & .global-search__card-content .content-item {
background-color: #2f2f2f;
&.content-item--active,
&:hover {
background-color: var(--ray-theme-primary-fade-color);
}
}
}
}
}
.global-search--light {
@include useAppTheme('light') {
& .global-search__card {
background-color: #f9f9f9;
& .global-search__card-content .content-item {
background-color: #ffffff;
&.content-item--active, &.content-item--active,
&:hover { &:hover {
background-color: var(--ray-theme-primary-fade-color); background-color: var(--ray-theme-primary-fade-color);

View File

@ -20,7 +20,15 @@
import './index.scss' import './index.scss'
import { NInput, NModal, NResult, NScrollbar, NFlex } from 'naive-ui' import {
NInput,
NModal,
NResult,
NScrollbar,
NFlex,
NSpin,
NCard,
} from 'naive-ui'
import { RIcon } from '@/components' import { RIcon } from '@/components'
import { queryElements, addClass, removeClass } from '@/utils' import { queryElements, addClass, removeClass } from '@/utils'
@ -62,22 +70,17 @@ export default defineComponent({
}) })
const helperTipOptions = [ const helperTipOptions = [
{ {
icon: 'cmd / ctrl + k', icon: ['↑', '↓'],
label: '唤起',
plain: true,
},
{
icon: '↑ ↓',
label: '切换', label: '切换',
plain: true, plain: true,
}, },
{ {
icon: '↵', icon: ['↵'],
label: '选择', label: '选择',
plain: true, plain: true,
}, },
{ {
icon: 'esc', icon: ['esc'],
label: '关闭', label: '关闭',
plain: true, plain: true,
}, },
@ -87,6 +90,7 @@ export default defineComponent({
/** 缓存索引 */ /** 缓存索引 */
let preSearchElementIndex = searchElementIndex let preSearchElementIndex = searchElementIndex
const { isTabletOrSmaller } = useDevice() const { isTabletOrSmaller } = useDevice()
const loading = ref(false)
/** 初始化一些值 */ /** 初始化一些值 */
const resetSearchSomeValue = () => { const resetSearchSomeValue = () => {
@ -111,10 +115,21 @@ export default defineComponent({
const fuzzySearchMenuOptions = (value: string) => { const fuzzySearchMenuOptions = (value: string) => {
const arr: AppMenuOption[] = [] const arr: AppMenuOption[] = []
if (value) {
loading.value = true
} else {
loading.value = false
state.searchOptions = []
return
}
const filterArr = (options: AppMenuOption[]) => { const filterArr = (options: AppMenuOption[]) => {
options.forEach((curr) => { for (const curr of options) {
if (curr.children?.length && validMenuItemShow(curr)) { if (curr.children?.length && validMenuItemShow(curr)) {
filterArr(curr.children) filterArr(curr.children)
continue
} }
/** 处理菜单名与输入值, 不区分大小写 */ /** 处理菜单名与输入值, 不区分大小写 */
@ -129,23 +144,27 @@ export default defineComponent({
) { ) {
arr.push(curr) arr.push(curr)
} }
}
}
setTimeout(() => {
if (value) {
filterArr(getMenuOptions.value)
state.searchOptions = arr
} else {
state.searchOptions = []
}
nextTick().then(() => {
autoFocusingSearchItem()
}) })
}
if (value) { loading.value = false
filterArr(getMenuOptions.value) }, 500)
state.searchOptions = arr
} else {
state.searchOptions = []
}
nextTick().then(() => {
autoFocusingSearchItem()
})
} }
const handleSearchItemClick = (option: AppMenuOption) => { const searchItemClick = (option: AppMenuOption) => {
if (option) { if (option) {
const { meta } = option const { meta } = option
@ -220,7 +239,11 @@ export default defineComponent({
const registerChangeSearchElementIndex = (e: KeyboardEvent) => { const registerChangeSearchElementIndex = (e: KeyboardEvent) => {
const keyCode = e.key const keyCode = e.key
if (keyCode === 'ArrowUp' || keyCode === 'ArrowDown') { if (
keyCode === 'ArrowUp' ||
keyCode === 'ArrowDown' ||
keyCode === 'Enter'
) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
} }
@ -242,7 +265,7 @@ export default defineComponent({
const option = state.searchOptions[searchElementIndex] const option = state.searchOptions[searchElementIndex]
if (option) { if (option) {
handleSearchItemClick(option) searchItemClick(option)
} }
break break
@ -259,7 +282,7 @@ export default defineComponent({
align="center" align="center"
class="content-item" class="content-item"
{...{ {...{
onClick: handleSearchItemClick.bind(this, menuOption), onClick: searchItemClick.bind(this, menuOption),
data_path: menuOption.path, data_path: menuOption.path,
}} }}
> >
@ -275,25 +298,31 @@ export default defineComponent({
} }
}) })
useEventListener(window, 'keydown', (e: KeyboardEvent) => { useEventListener(
registerArouseKeyboard(e) window,
registerChangeSearchElementIndex(e) 'keydown',
}) (e: KeyboardEvent) => {
registerArouseKeyboard(e)
registerChangeSearchElementIndex(e)
},
true,
)
return { return {
...toRefs(state), ...toRefs(state),
modelShow, modelShow,
helperTipOptions, helperTipOptions,
fuzzySearchMenuOptions: debounce(fuzzySearchMenuOptions, 300), fuzzySearchMenuOptions: debounce(fuzzySearchMenuOptions, 300),
handleSearchItemClick, searchItemClick,
RenderPreIcon, RenderPreIcon,
isTabletOrSmaller, isTabletOrSmaller,
SearchItem, SearchItem,
loading,
} }
}, },
render() { render() {
const { isTabletOrSmaller, searchOptions } = this const { isTabletOrSmaller, searchOptions, loading } = this
const { SearchItem, fuzzySearchMenuOptions } = this const { SearchItem, fuzzySearchMenuOptions, $t } = this
return isTabletOrSmaller ? ( return isTabletOrSmaller ? (
<div style="display: none;"></div> <div style="display: none;"></div>
@ -305,64 +334,85 @@ export default defineComponent({
> >
<div class="global-search global-search--dark global-search--light"> <div class="global-search global-search--dark global-search--light">
<div class="global-search__wrapper"> <div class="global-search__wrapper">
<div class="global-search__card"> <NCard
<div class="global-search__card-header"> class="global-search__card"
<NInput headerStyle={{
size="large" padding: '12px 12px 0 12px',
v-model:value={this.searchValue} }}
clearable contentStyle={{
onInput={fuzzySearchMenuOptions.bind(this)} padding: '12px',
> }}
{{ segmented={{
prefix: () => <RIcon name="search" size="24" />, action: 'soft',
}} }}
</NInput> >
</div> {{
<NScrollbar class="global-search__card-content"> header: () => (
{searchOptions.length ? ( <NInput
<NFlex vertical size={[8, 8]}> size="large"
{searchOptions.map((curr) => ( v-model:value={this.searchValue}
<SearchItem menuOption={curr} key={curr.fullPath} /> clearable
onInput={fuzzySearchMenuOptions.bind(this)}
>
{{
prefix: () => <RIcon name="search" size="24" />,
}}
</NInput>
),
default: () => (
<NScrollbar>
<NSpin show={loading}>
{searchOptions.length ? (
<NFlex
vertical
size={[0, 6]}
class="global-search__card-content"
>
{searchOptions.map((curr) => (
<SearchItem menuOption={curr} key={curr.fullPath} />
))}
</NFlex>
) : (
<NResult size="large" class="global-search__empty">
{{
icon: () => null,
default: () => (
<NFlex
justify="center"
class="global-search__empty-content"
>
<RIcon name="empty" size="24" />
</NFlex>
),
}}
</NResult>
)}
</NSpin>
</NScrollbar>
),
action: () => (
<NFlex justify="flex-start" align="center" size={[16, 0]}>
{this.helperTipOptions.map((curr) => (
<NFlex key={curr.label} size={[4, 0]}>
{curr.icon.map((icon) => (
<NFlex
class="item-icon"
align="center"
justify="center"
>
{icon}
</NFlex>
))}
<NFlex class="item-label" align="center">
{curr.label}
</NFlex>
</NFlex>
))} ))}
</NFlex> </NFlex>
) : ( ),
<NResult size="large" class="global-search__empty"> }}
{{ </NCard>
icon: () => null,
default: () => (
<NFlex
justify="center"
class="global-search__empty-content"
>
<RIcon name="empty" size="24" />
</NFlex>
),
}}
</NResult>
)}
</NScrollbar>
<div class="global-search__card-footer">
<NFlex
class="card-footer__tip-wrapper"
align="center"
size={[24, 8]}
>
{this.helperTipOptions.map((curr) => (
<div class="tip-wrapper-item" key={curr.label}>
<div class="item-icon">
{curr.plain ? (
<span>{curr.icon}</span>
) : (
<RIcon name={curr.icon} size="18" />
)}
</div>
<div class="item-label">{curr.label}</div>
</div>
))}
</NFlex>
</div>
</div>
</div> </div>
</div> </div>
</NModal> </NModal>

View File

@ -0,0 +1,73 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2024-01-23
*
* @workspace ray-template
*
* @remark
*/
import { NButton, NFlex } from 'naive-ui'
import { RIcon } from '@/components'
import { detectOperatingSystem, call } from '@/utils'
import type { PropType } from 'vue'
import type { MaybeArray } from '@/types'
export default defineComponent({
name: 'GlobalSearchButton',
props: {
onClick: {
type: [Function, Array] as PropType<MaybeArray<(e: MouseEvent) => void>>,
default: null,
},
},
setup(props) {
const getShortcutKeyAboutSystem = () => {
const operatingSystem = detectOperatingSystem()
if (operatingSystem === 'MacOS') {
return '⌘ K'
}
if (operatingSystem === 'Windows') {
return 'CTRL + K'
}
return 'CTRL + K'
}
const click = (e: MouseEvent) => {
const { onClick } = props
if (onClick) {
call(onClick, e)
}
}
return {
getShortcutKeyAboutSystem,
click,
}
},
render() {
const { getShortcutKeyAboutSystem, click, $t } = this
return (
<NButton bordered={false} onClick={click.bind(this)}>
{{
icon: () => <RIcon name="search" size="16" />,
default: () => (
<NFlex align="center">
{$t('headerTooltip.Search')}
<NButton size="tiny">{getShortcutKeyAboutSystem()}</NButton>
</NFlex>
),
}}
</NButton>
)
},
})

View File

@ -29,7 +29,7 @@ import { useSettingGetters, useSettingActions } from '@/store'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { Placement } from '@/types' import type { Placement } from '@/types'
const SettingDrawer = defineComponent({ export default defineComponent({
name: 'SettingDrawer', name: 'SettingDrawer',
props: { props: {
show: { show: {
@ -82,7 +82,7 @@ const SettingDrawer = defineComponent({
} }
}, },
render() { render() {
const { $t } = this const { $t, changePrimaryColor, updateSettingState } = this
return ( return (
<NDrawer <NDrawer
@ -102,7 +102,7 @@ const SettingDrawer = defineComponent({
<NColorPicker <NColorPicker
swatches={APP_THEME.appThemeColors} swatches={APP_THEME.appThemeColors}
v-model:value={this.getPrimaryColorOverride.common!.primaryColor} v-model:value={this.getPrimaryColorOverride.common!.primaryColor}
onUpdateValue={this.changePrimaryColor.bind(this)} onUpdateValue={changePrimaryColor.bind(this)}
/> />
<NDivider titlePlacement="center"> <NDivider titlePlacement="center">
{$t('headerSettingOptions.ContentTransition')} {$t('headerSettingOptions.ContentTransition')}
@ -111,7 +111,7 @@ const SettingDrawer = defineComponent({
v-model:value={this.modelSwitchReactive.getContentTransition} v-model:value={this.modelSwitchReactive.getContentTransition}
options={CONTENT_TRANSITION_OPTIONS} options={CONTENT_TRANSITION_OPTIONS}
onUpdateValue={(value) => { onUpdateValue={(value) => {
this.updateSettingState('contentTransition', value) updateSettingState('contentTransition', value)
}} }}
/> />
<NDivider titlePlacement="center"> <NDivider titlePlacement="center">
@ -122,7 +122,7 @@ const SettingDrawer = defineComponent({
<NSwitch <NSwitch
v-model:value={this.modelSwitchReactive.getMenuTagSwitch} v-model:value={this.modelSwitchReactive.getMenuTagSwitch}
onUpdateValue={(bool: boolean) => onUpdateValue={(bool: boolean) =>
this.updateSettingState('menuTagSwitch', bool) updateSettingState('menuTagSwitch', bool)
} }
/> />
</NDescriptionsItem> </NDescriptionsItem>
@ -130,7 +130,7 @@ const SettingDrawer = defineComponent({
<NSwitch <NSwitch
v-model:value={this.modelSwitchReactive.getBreadcrumbSwitch} v-model:value={this.modelSwitchReactive.getBreadcrumbSwitch}
onUpdateValue={(bool: boolean) => onUpdateValue={(bool: boolean) =>
this.updateSettingState('breadcrumbSwitch', bool) updateSettingState('breadcrumbSwitch', bool)
} }
/> />
</NDescriptionsItem> </NDescriptionsItem>
@ -138,7 +138,7 @@ const SettingDrawer = defineComponent({
<NSwitch <NSwitch
v-model:value={this.modelSwitchReactive.getWatermarkSwitch} v-model:value={this.modelSwitchReactive.getWatermarkSwitch}
onUpdateValue={(bool: boolean) => onUpdateValue={(bool: boolean) =>
this.updateSettingState('watermarkSwitch', bool) updateSettingState('watermarkSwitch', bool)
} }
/> />
</NDescriptionsItem> </NDescriptionsItem>
@ -146,7 +146,7 @@ const SettingDrawer = defineComponent({
<NSwitch <NSwitch
v-model:value={this.modelSwitchReactive.getCopyrightSwitch} v-model:value={this.modelSwitchReactive.getCopyrightSwitch}
onUpdateValue={(bool: boolean) => onUpdateValue={(bool: boolean) =>
this.updateSettingState('copyrightSwitch', bool) updateSettingState('copyrightSwitch', bool)
} }
/> />
</NDescriptionsItem> </NDescriptionsItem>
@ -157,5 +157,3 @@ const SettingDrawer = defineComponent({
) )
}, },
}) })
export default SettingDrawer

View File

@ -0,0 +1,13 @@
import TooltipIcon from './TooltipIcon'
import SettingDrawer from './SettingDrawer'
import Breadcrumb from './Breadcrumb'
import GlobalSearch from './GlobalSearch'
import GlobalSearchButton from './GlobalSearchButton'
export {
TooltipIcon,
SettingDrawer,
Breadcrumb,
GlobalSearch,
GlobalSearchButton,
}

View File

@ -21,10 +21,13 @@ import './index.scss'
import { NLayoutHeader, NFlex, NDropdown } from 'naive-ui' import { NLayoutHeader, NFlex, NDropdown } from 'naive-ui'
import { RIcon } from '@/components' import { RIcon } from '@/components'
import TooltipIcon from '@/layout/components/SiderBar/components/TooltipIcon' import {
import SettingDrawer from './components/SettingDrawer' TooltipIcon,
import Breadcrumb from './components/Breadcrumb' SettingDrawer,
import GlobalSearch from './components/GlobalSearch' Breadcrumb,
GlobalSearch,
GlobalSearchButton,
} from './components'
import AppAvatar from '@/app-components/app/AppAvatar' import AppAvatar from '@/app-components/app/AppAvatar'
import { LOCAL_OPTIONS } from '@/app-config' import { LOCAL_OPTIONS } from '@/app-config'
@ -39,7 +42,8 @@ import { getVariableToRefs, setVariable } from '@/global-variable'
import { useFullscreen } from 'vue-hooks-plus' import { useFullscreen } from 'vue-hooks-plus'
import { useSettingGetters, useSettingActions } from '@/store' import { useSettingGetters, useSettingActions } from '@/store'
import type { IconEventMapOptions, IconEventMap } from './type' import type { IconEventMapOptions } from './type'
import type { VNode } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'AppSiderBar', name: 'AppSiderBar',
@ -77,6 +81,9 @@ export default defineComponent({
}), }),
) )
const iconEventMap: IconEventMapOptions = { const iconEventMap: IconEventMapOptions = {
search: () => {
globalSearchShown.value = true
},
setting: () => { setting: () => {
showSettings.value = true showSettings.value = true
}, },
@ -90,9 +97,6 @@ export default defineComponent({
toggleFullscreen() toggleFullscreen()
}, },
search: () => {
globalSearchShown.value = true
},
lock: () => { lock: () => {
updateSettingState('lockScreenSwitch', true) updateSettingState('lockScreenSwitch', true)
}, },
@ -105,6 +109,15 @@ export default defineComponent({
iconEventMap[key]?.() iconEventMap[key]?.()
} }
/**
*
* @param vnode
* @returns , null, vnode
*/
const isRenderVNode = (vnode: VNode) => {
return isTabletOrSmaller.value ? null : vnode
}
return { return {
leftIconOptions, leftIconOptions,
rightTooltipIconOptions, rightTooltipIconOptions,
@ -114,9 +127,18 @@ export default defineComponent({
getDrawerPlacement, getDrawerPlacement,
getBreadcrumbSwitch, getBreadcrumbSwitch,
globalSearchShown, globalSearchShown,
isRenderVNode,
} }
}, },
render() { render() {
const {
rightTooltipIconOptions,
leftIconOptions,
getDrawerPlacement,
getBreadcrumbSwitch,
} = this
const { toolIconClick, updateLocale, isRenderVNode } = this
return ( return (
<NLayoutHeader class="layout-header" bordered> <NLayoutHeader class="layout-header" bordered>
<GlobalSearch v-model:show={this.globalSearchShown} /> <GlobalSearch v-model:show={this.globalSearchShown} />
@ -126,7 +148,7 @@ export default defineComponent({
justify="space-between" justify="space-between"
> >
<NFlex align="center"> <NFlex align="center">
{this.leftIconOptions.map((curr) => ( {leftIconOptions.map((curr) => (
<TooltipIcon <TooltipIcon
key={curr.name} key={curr.name}
iconName={curr.name} iconName={curr.name}
@ -134,13 +156,22 @@ export default defineComponent({
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip
} }
customClassName={curr.iconClass} customClassName={curr.iconClass}
onClick={this.toolIconClick.bind(this, curr.name)} onClick={toolIconClick.bind(this, curr.name)}
/> />
))} ))}
{this.getBreadcrumbSwitch ? <Breadcrumb /> : null} {getBreadcrumbSwitch ? <Breadcrumb /> : null}
</NFlex> </NFlex>
<NFlex align="center"> <NFlex align="center" size={[16, 0]}>
{this.rightTooltipIconOptions.map((curr) => ( {isRenderVNode(
<GlobalSearchButton
onClick={(e) => {
e.stopPropagation()
this.globalSearchShown = true
}}
/>,
)}
{rightTooltipIconOptions.map((curr) => (
<TooltipIcon <TooltipIcon
key={curr.name} key={curr.name}
iconName={curr.name} iconName={curr.name}
@ -148,14 +179,12 @@ export default defineComponent({
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip
} }
customClassName={curr.iconClass} customClassName={curr.iconClass}
onClick={this.toolIconClick.bind(this, curr.name)} onClick={toolIconClick.bind(this, curr.name)}
/> />
))} ))}
<NDropdown <NDropdown
options={LOCAL_OPTIONS} options={LOCAL_OPTIONS}
onSelect={(key: string | number) => onSelect={(key: string | number) => updateLocale(String(key))}
this.updateLocale(String(key))
}
trigger="click" trigger="click"
> >
<RIcon <RIcon
@ -176,7 +205,7 @@ export default defineComponent({
</NFlex> </NFlex>
<SettingDrawer <SettingDrawer
v-model:show={this.showSettings} v-model:show={this.showSettings}
placement={this.getDrawerPlacement} placement={getDrawerPlacement}
/> />
</NLayoutHeader> </NLayoutHeader>
) )

View File

@ -126,15 +126,7 @@ export const createRightIconOptions = (opts: IconOptionsFC) => {
eventKey: 'setting', eventKey: 'setting',
}, },
] ]
const notTableOrSmallerOptions: IconOptions[] = [ const notTableOrSmallerOptions: IconOptions[] = [...basicOptions]
{
name: 'search',
size: 18,
tooltip: t('headerTooltip.Search'),
eventKey: 'search',
},
...basicOptions,
]
const tableOrSmallerOptions: IconOptions[] = [...basicOptions] const tableOrSmallerOptions: IconOptions[] = [...basicOptions]
return isTabletOrSmaller!.value return isTabletOrSmaller!.value

View File

@ -1,5 +1,5 @@
import type { DropdownOption } from 'naive-ui' import type { DropdownOption } from 'naive-ui'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref, VNode } from 'vue'
export interface IconEventMapOptions { export interface IconEventMapOptions {
[propName: string]: (...args: unknown[]) => unknown [propName: string]: (...args: unknown[]) => unknown
@ -21,6 +21,7 @@ export interface IconOptions {
eventKey?: string eventKey?: string
dropdown?: IconDropdownOptions dropdown?: IconDropdownOptions
iconClass?: string iconClass?: string
render?: VNode | JSX.Element
} }
export interface IconOptionsFC { export interface IconOptionsFC {

View File

@ -16,7 +16,9 @@
right: -40px; right: -40px;
top: -40px; top: -40px;
@include flexCenter; @include flexCenter;
transition: color 0.3s var(--r-bezier), background-color 0.3s var(--r-bezier); transition:
color 0.3s var(--r-bezier),
background-color 0.3s var(--r-bezier);
& .ray-icon { & .ray-icon {
transform: translate(-14px, 14px); transform: translate(-14px, 14px);
@ -50,3 +52,11 @@
} }
} }
} }
.ray-template--dark .r-layout-full__viewer-content {
background-color: $layoutContentBackgroundColorDark;
}
.ray-template--light .r-layout-full__viewer-content {
background-color: $layoutContentBackgroundColorLight;
}

View File

@ -1,4 +1,13 @@
.layout-footer-wrapper { .layout-footer-wrapper {
padding: 0 20px 8px 20px; height: 48px;
text-align: center; text-align: center;
line-height: 48px;
}
.ray-template--dark .layout-footer-wrapper {
background-color: $layoutFooterBackgroundColorDark;
}
.ray-template--light .layout-footer-wrapper {
background-color: $layoutFooterBackgroundColorLight;
} }

View File

@ -0,0 +1,6 @@
import ContentWrapper from './ContentWrapper'
import FooterWrapper from './FooterWrapper'
import HeaderWrapper from './HeaderWrapper'
import FeatureWrapper from './FeatureWrapper'
export { ContentWrapper, FooterWrapper, HeaderWrapper, FeatureWrapper }

View File

@ -13,10 +13,12 @@ import './index.scss'
import { NLayout, NLayoutContent } from 'naive-ui' import { NLayout, NLayoutContent } from 'naive-ui'
import Menu from './components/Menu' import Menu from './components/Menu'
import ContentWrapper from '@/layout/default/ContentWrapper' import {
import FooterWrapper from '@/layout/default/FooterWrapper' ContentWrapper,
import HeaderWrapper from './default/HeaderWrapper' FooterWrapper,
import FeatureWrapper from './default/FeatureWrapper' HeaderWrapper,
FeatureWrapper,
} from './default'
import { LAYOUT_CONTENT_REF } from '@/app-config' import { LAYOUT_CONTENT_REF } from '@/app-config'
import { layoutHeaderCssVars } from '@/layout/layoutResize' import { layoutHeaderCssVars } from '@/layout/layoutResize'

View File

@ -125,11 +125,9 @@ export const naiveLocales = (key: string) => {
* @remark , `main.ts` , `i18n` * @remark , `main.ts` , `i18n`
*/ */
export const getAppDefaultLanguage = () => { export const getAppDefaultLanguage = () => {
const language = getStorage( const language = getStorage(APP_CATCH_KEY.localeLanguage, 'localStorage', {
APP_CATCH_KEY.localeLanguage, defaultValue: SYSTEM_DEFAULT_LOCAL,
'localStorage', })
SYSTEM_DEFAULT_LOCAL,
)
return language as keyof AppCurrentAppMessages return language as keyof AppCurrentAppMessages
} }

View File

@ -23,5 +23,6 @@
"SvgIcon": "SVG Icon", "SvgIcon": "SVG Icon",
"TemplateHooks": "Template Api", "TemplateHooks": "Template Api",
"Modal": "Modal", "Modal": "Modal",
"ContextMenu": "Right Click Menu" "ContextMenu": "Right Click Menu",
"CacheDemo": "Cache Utils Demo"
} }

View File

@ -23,5 +23,6 @@
"SvgIcon": "SVG 图标", "SvgIcon": "SVG 图标",
"TemplateHooks": "模板内置 Api", "TemplateHooks": "模板内置 Api",
"Modal": "模态框", "Modal": "模态框",
"ContextMenu": "右键菜单" "ContextMenu": "右键菜单",
"CacheDemo": "缓存工具函数"
} }

View File

@ -44,7 +44,9 @@ export const permissionRouter = (router: Router) => {
const catchRoutePath = getStorage( const catchRoutePath = getStorage(
APP_CATCH_KEY.appMenuKey, APP_CATCH_KEY.appMenuKey,
'sessionStorage', 'sessionStorage',
getRootPath.value, {
defaultValue: getRootPath.value,
},
) )
const { meta, name } = to const { meta, name } = to

View File

@ -0,0 +1,18 @@
import { t } from '@/hooks'
import { LAYOUT } from '@/router/constant'
import type { AppRouteRecordRaw } from '@/router/type'
const cacheDemo: AppRouteRecordRaw = {
path: '/cache-demo',
name: 'CacheDemo',
component: () => import('@/views/demo/cache-demo/index'),
meta: {
i18nKey: t('menu.CacheDemo'),
icon: 'other',
order: 3,
extra: 'new',
},
}
export default cacheDemo

View File

@ -3,12 +3,19 @@
import type { RouteRecordRaw } from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
import type { Recordable } from '@/types' import type { Recordable } from '@/types'
import type { DefineComponent, VNode } from 'vue' import type { DefineComponent, VNode } from 'vue'
import type { TagProps } from 'naive-ui'
export type Component<T = any> = export type Component<T = any> =
| DefineComponent<{}, {}, any> | DefineComponent<{}, {}, any>
| (() => Promise<typeof import('*.vue')>) | (() => Promise<typeof import('*.vue')>)
| (() => Promise<T>) | (() => Promise<T>)
export interface AppMenuExtraOptions {
extraLabel?: string
extraIcon?: string | VNode
extraType?: TagProps['type']
}
export interface AppRouteMeta { export interface AppRouteMeta {
i18nKey?: string i18nKey?: string
icon?: string | VNode icon?: string | VNode
@ -21,6 +28,7 @@ export interface AppRouteMeta {
keepAlive?: boolean keepAlive?: boolean
sameLevel?: boolean sameLevel?: boolean
env?: string | string[] env?: string | string[]
extra?: string | AppMenuExtraOptions
} }
// @ts-ignore // @ts-ignore

View File

@ -19,6 +19,7 @@
*/ */
import { APP_KEEP_ALIVE } from '@/app-config' import { APP_KEEP_ALIVE } from '@/app-config'
import { APP_CATCH_KEY } from '@/app-config'
import type { KeepAliveStoreState } from './type' import type { KeepAliveStoreState } from './type'
import type { AppMenuOption } from '@/types' import type { AppMenuOption } from '@/types'
@ -76,7 +77,7 @@ export const piniaKeepAliveStore = defineStore(
}, },
{ {
persist: { persist: {
key: 'piniaKeepAliveStore', key: APP_CATCH_KEY.appPiniaKeepAliveStore,
storage: window.sessionStorage, storage: window.sessionStorage,
paths: ['keepAliveInclude'], paths: ['keepAliveInclude'],
}, },

View File

@ -15,12 +15,14 @@ import { APP_MENU_CONFIG, APP_CATCH_KEY } from '@/app-config'
import { RIcon } from '@/components' import { RIcon } from '@/components'
import { getStorage, isValueType } from '@/utils' import { getStorage, isValueType } from '@/utils'
import { useAppRoot } from '@/hooks' import { useAppRoot } from '@/hooks'
import { NTag } from 'naive-ui'
import type { import type {
AppMenuOption, AppMenuOption,
MenuTagOptions, MenuTagOptions,
AppMenuKey, AppMenuKey,
} from '@/types/modules/app' } from '@/types/modules/app'
import type { TagProps } from 'naive-ui'
/** /**
* *
@ -143,28 +145,72 @@ export const updateDocumentTitle = (option: AppMenuOption) => {
document.title = breadcrumbLabel + ' - ' + spliceTitle document.title = breadcrumbLabel + ' - ' + spliceTitle
} }
export const hasMenuIcon = (option: AppMenuOption) => { export const createMenuIcon = (option: AppMenuOption) => {
const { meta } = option const {
meta: { icon },
} = option
if (!meta.icon) { if (!icon) {
return return
} }
if (isValueType<object>(meta.icon, 'Object')) { if (isValueType<object>(icon, 'Object')) {
return () => meta.icon return () => icon
} }
const icon = h( const _icon = h(
RIcon, RIcon,
{ {
name: meta!.icon as string, name: icon,
size: APP_MENU_CONFIG.menuCollapsedIconSize, size: APP_MENU_CONFIG.menuCollapsedIconSize,
cursor: 'pointer', cursor: 'pointer',
}, },
{}, {},
) )
return () => icon return () => _icon
}
export const createMenuExtra = (option: AppMenuOption) => {
const {
meta: { extra },
} = option
if (!extra) {
return
}
const tagProps: TagProps = {
type: 'primary',
size: 'small',
round: true,
bordered: false,
strong: true,
}
if (isValueType<object>(extra, 'Object')) {
const { extraLabel, extraIcon, extraType } = extra
return () => {
return h(
NTag,
{
...tagProps,
type: extraType || 'primary',
},
{
default: () => extraLabel,
icon: () => extraIcon,
},
)
}
}
return () => {
return h(NTag, tagProps, {
default: () => extra,
})
}
} }
/** 获取缓存的 menu key, 如果未获取到则使用 getRootPath 当作默认激活路由菜单 */ /** 获取缓存的 menu key, 如果未获取到则使用 getRootPath 当作默认激活路由菜单 */
@ -173,7 +219,9 @@ export const getCatchMenuKey = () => {
const cacheMenuKey = getStorage<AppMenuKey>( const cacheMenuKey = getStorage<AppMenuKey>(
APP_CATCH_KEY.appMenuKey, APP_CATCH_KEY.appMenuKey,
'sessionStorage', 'sessionStorage',
getRootPath.value, {
defaultValue: getRootPath.value,
},
) )
return cacheMenuKey return cacheMenuKey

View File

@ -30,8 +30,9 @@ import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot'
import { import {
parseAndFindMatchingNodes, parseAndFindMatchingNodes,
updateDocumentTitle, updateDocumentTitle,
hasMenuIcon, createMenuIcon,
getCatchMenuKey, getCatchMenuKey,
createMenuExtra,
} from './helper' } from './helper'
import { useI18n } from '@/hooks' import { useI18n } from '@/hooks'
import { getAppRawRoutes } from '@/router/appRouteModules' import { getAppRawRoutes } from '@/router/appRouteModules'
@ -60,13 +61,23 @@ export const piniaMenuStore = defineStore(
}) })
const isSetupAppMenuLock = ref(true) const isSetupAppMenuLock = ref(true)
/**
*
* @param option
* @returns
*
* AppMenu
*
* @example
* resolveOption({ path: '/dashboard', name: 'Dashboard', meta: { i18nKey: 'menu.Dashboard' } })
* resolveOption({ ...VueRouterRouteOption })
*/
const resolveOption = (option: AppMenuOption) => { const resolveOption = (option: AppMenuOption) => {
const { meta } = option const { meta } = option
const { i18nKey, noLocalTitle } = meta
/** 设置 label, i18nKey 优先级最高 */ /** 设置 label, i18nKey 优先级最高 */
const label = computed(() => const label = computed(() => (i18nKey ? t(`${i18nKey}`) : noLocalTitle))
meta?.i18nKey ? t(`${meta!.i18nKey}`) : meta?.noLocalTitle,
)
/** /**
* *
* *
@ -84,7 +95,8 @@ export const piniaMenuStore = defineStore(
} as AppMenuOption } as AppMenuOption
/** 合并 icon */ /** 合并 icon */
const attr: AppMenuOption = Object.assign({}, route, { const attr: AppMenuOption = Object.assign({}, route, {
icon: hasMenuIcon(option), icon: createMenuIcon(option),
extra: createMenuExtra(option),
}) })
if (option.fullPath === getCatchMenuKey()) { if (option.fullPath === getCatchMenuKey()) {
@ -368,7 +380,7 @@ export const piniaMenuStore = defineStore(
}, },
{ {
persist: { persist: {
key: 'piniaMenuStore', key: APP_CATCH_KEY.appPiniaMenuStore,
storage: window.sessionStorage, storage: window.sessionStorage,
paths: ['breadcrumbOptions', 'menuKey', 'menuTagOptions'], paths: ['breadcrumbOptions', 'menuKey', 'menuTagOptions'],
}, },

View File

@ -2,8 +2,9 @@ import { getAppDefaultLanguage } from '@/locales/helper'
import { set } from 'lodash-es' import { set } from 'lodash-es'
import { colorToRgba, setStorage } from '@/utils' import { colorToRgba, setStorage } from '@/utils'
import { useI18n, useDayjs } from '@/hooks' import { useI18n, useDayjs } from '@/hooks'
import { APP_THEME } from '@/app-config'
import { APP_CATCH_KEY } from '@/app-config' import { APP_CATCH_KEY } from '@/app-config'
import { watchOnce } from '@vueuse/core'
import { APP_THEME } from '@/app-config'
import type { SettingState } from '@/store/modules/setting/type' import type { SettingState } from '@/store/modules/setting/type'
import type { LocalKey } from '@/hooks' import type { LocalKey } from '@/hooks'
@ -21,9 +22,8 @@ export const piniaSettingStore = defineStore(
const settingState = reactive<SettingState>({ const settingState = reactive<SettingState>({
drawerPlacement: 'right', drawerPlacement: 'right',
primaryColorOverride: { primaryColorOverride: {
...APP_THEME.appNaiveUIThemeOverrides,
common: { common: {
primaryColor: primaryColor, // 主题色 primaryColor: primaryColor,
primaryColorHover: primaryColor, primaryColorHover: primaryColor,
}, },
}, },
@ -61,22 +61,24 @@ export const piniaSettingStore = defineStore(
setStorage(APP_CATCH_KEY.localeLanguage, key, 'localStorage') setStorage(APP_CATCH_KEY.localeLanguage, key, 'localStorage')
} }
/** 切换主题色 */ /**
*
* naive-ui
*/
const changePrimaryColor = (value: string, alpha = 0.3) => { const changePrimaryColor = (value: string, alpha = 0.3) => {
set( const alphaColor = colorToRgba(value, alpha)
settingState, const themeOverrides = {
'settingState.primaryColorOverride.common.primaryColorHover', primaryColor: value,
value, primaryColorHover: value,
) }
settingState.primaryColorOverride.common = themeOverrides
const body = document.body const body = document.body
/** 设置主题色变量 */ /** 设置主题色变量 */
body.style.setProperty('--ray-theme-primary-color', value) body.style.setProperty('--ray-theme-primary-color', value)
body.style.setProperty( body.style.setProperty('--ray-theme-primary-fade-color', alphaColor)
'--ray-theme-primary-fade-color',
colorToRgba(value, alpha),
)
} }
/** /**
@ -108,6 +110,33 @@ export const piniaSettingStore = defineStore(
cb?.() cb?.()
} }
/**
*
*
*
*/
watchOnce(
() => settingState.appTheme,
(ndata) => {
ndata
? Object.assign(
{},
settingState.primaryColorOverride,
APP_THEME.appNaiveUIThemeOverrides.dark,
APP_THEME.appNaiveUIThemeOverridesCommon.dark,
)
: Object.assign(
{},
settingState.primaryColorOverride,
APP_THEME.appNaiveUIThemeOverrides.light,
APP_THEME.appNaiveUIThemeOverridesCommon.light,
)
},
{
immediate: true,
},
)
return { return {
...toRefs(settingState), ...toRefs(settingState),
updateLocale, updateLocale,
@ -117,7 +146,7 @@ export const piniaSettingStore = defineStore(
}, },
{ {
persist: { persist: {
key: 'piniaSettingStore', key: APP_CATCH_KEY.appPiniaSettingStore,
}, },
}, },
) )

View File

@ -21,6 +21,7 @@
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { removeStorage } from '@/utils' import { removeStorage } from '@/utils'
import { APP_CATCH_KEY } from '@/app-config'
import type { import type {
SigningForm, SigningForm,
@ -78,7 +79,7 @@ export const piniaSigningStore = defineStore(
*/ */
const logout = () => { const logout = () => {
window.$message.info('账号退出中...') window.$message.info('账号退出中...')
removeStorage('all-sessionStorage') removeStorage('__all_sessionStorage__')
setTimeout(() => window.location.reload()) setTimeout(() => window.location.reload())
} }
@ -91,7 +92,7 @@ export const piniaSigningStore = defineStore(
}, },
{ {
persist: { persist: {
key: 'piniaSigningStore', key: APP_CATCH_KEY.appPiniaSigningStore,
paths: ['signingCallback'], paths: ['signingCallback'],
storage: sessionStorage, storage: sessionStorage,
}, },

View File

@ -1,4 +1,4 @@
// layout content 区域默认开启继承宽高避免 global lodaing 丢失高度 // layout content 区域默认开启继承宽高避免 global loading 丢失高度
.r-layout-full__viewer-content .n-spin-container, .r-layout-full__viewer-content .n-spin-container,
.r-layout-full__viewer-content .n-spin-container .n-spin-content { .r-layout-full__viewer-content .n-spin-container .n-spin-content {
width: 100%; width: 100%;
@ -6,8 +6,18 @@
} }
// 拓展 AppMenu Item样式 // 拓展 AppMenu Item样式
.r-menu--app:not(.n-menu--collapsed) .n-menu-item-content.n-menu-item-content--selected:before, .r-menu--app:not(.n-menu--collapsed)
.n-menu-item-content.n-menu-item-content--selected:before,
.r-menu--app:not(.n-menu--collapsed) .n-menu-item-content:hover:before { .r-menu--app:not(.n-menu--collapsed) .n-menu-item-content:hover:before {
border-left: 4px solid var(--ray-theme-primary-color); border-left: 4px solid var(--ray-theme-primary-color);
transition: border-left 0.1s; transition: border-left 0.1s;
} }
// 手动设置 AppMenu Item Header 样式兼容 extra 的情况
.r-menu--app
.n-menu-item-content.n-menu-item-content--selected
.n-menu-item-content-header,
.r-menu--app .n-menu-item-content .n-menu-item-content-header {
display: flex;
gap: 0 6px;
}

View File

@ -1,3 +1,11 @@
$layoutRouterViewContainer: 18px; $layoutRouterViewContainer: 18px;
$layoutHeaderHeight: 64px; $layoutHeaderHeight: 64px;
$layoutMenuHeight: 46px; $layoutMenuHeight: 46px;
// 内容区域背景色dark
$layoutContentBackgroundColorDark: #101014;
// 内容区域背景色light
$layoutContentBackgroundColorLight: #f7f9f8;
// 底部区域背景色dark
$layoutFooterBackgroundColorDark: rgba(24, 24, 28, 1);
// 底部区域背景色light
$layoutFooterBackgroundColorLight: rgba(255, 255, 255, 1);

View File

@ -6,3 +6,4 @@ export type * from './modules/component'
export type * from './modules/helper' export type * from './modules/helper'
export type * from './modules/utils' export type * from './modules/utils'
export type * from './modules/vue' export type * from './modules/vue'
export { OperatingSystem } from './modules/utils'

View File

@ -5,9 +5,11 @@ export type StorageLike = 'sessionStorage' | 'localStorage'
export type RemoveStorageKey = export type RemoveStorageKey =
| string | string
| 'all' | '__all__'
| 'all-sessionStorage' | '__all_sessionStorage__'
| 'all-localStorage' | '__all_localStorage__'
export type RemoveStorageType = StorageLike
export type ValidateValueType = export type ValidateValueType =
| 'BigUint64Array' | 'BigUint64Array'
@ -88,3 +90,18 @@ export type ElementSelector = string | `attr:${string}`
export type MaybeArray<T> = T | T[] export type MaybeArray<T> = T | T[]
export type DownloadAnyFileDataType = Blob | File | string | ArrayBuffer export type DownloadAnyFileDataType = Blob | File | string | ArrayBuffer
export enum OperatingSystem {
Windows = 'Windows',
MacOS = 'MacOS',
Linux = 'Linux',
Android = 'Android',
IOS = 'IOS',
Unknown = 'Unknown',
}
export interface StorageOptions<T = any> {
prefix?: boolean
prefixKey?: string
defaultValue?: T
}

View File

@ -78,6 +78,13 @@ export type AppConfigExport = Config & UserConfigExport
export interface AppTheme { export interface AppTheme {
appThemeColors: string[] appThemeColors: string[]
appPrimaryColor: AppPrimaryColor appPrimaryColor: AppPrimaryColor
appNaiveUIThemeOverrides: GlobalThemeOverrides appNaiveUIThemeOverrides: {
dark: GlobalThemeOverrides
light: GlobalThemeOverrides
}
echartTheme: string echartTheme: string
appNaiveUIThemeOverridesCommon: {
dark: GlobalThemeOverrides['common']
light: GlobalThemeOverrides['common']
}
} }

View File

@ -1,3 +1,5 @@
import { OperatingSystem } from '@/types'
import type { import type {
ValidateValueType, ValidateValueType,
DownloadAnyFileDataType, DownloadAnyFileDataType,
@ -341,3 +343,37 @@ export const callWithAsyncErrorHandling = async <
return void 0 return void 0
} }
} }
/**
*
*
* Unknown
*
* @example
* detectOperatingSystem() => 'Windows' | 'MacOS' | 'Linux' | 'Android' | 'IOS' | 'Unknown'
*/
export const detectOperatingSystem = () => {
const userAgent = navigator.userAgent
if (/windows/i.test(userAgent)) {
return OperatingSystem.Windows
}
if (/macintosh|mac os x/i.test(userAgent)) {
return OperatingSystem.MacOS
}
if (/linux/i.test(userAgent)) {
return OperatingSystem.Linux
}
if (/android/i.test(userAgent)) {
return OperatingSystem.Android
}
if (/iphone|ipad|ipod/i.test(userAgent)) {
return OperatingSystem.IOS
}
return OperatingSystem.Unknown
}

View File

@ -9,77 +9,109 @@
* @remark * @remark
*/ */
import type { StorageLike, RemoveStorageKey } from '@/types' import { APP_CATCH_KEY_PREFIX } from '@/app-config'
import type {
StorageLike,
RemoveStorageKey,
StorageOptions,
RemoveStorageType,
} from '@/types'
/** /**
* *
* @param key cache key * @param key key
* @param storageType session or local * @param storageType
* @param options
* *
* key * key
* sessionStorage * sessionStorage
*/ */
function hasStorage(key: string, storageType: StorageLike = 'sessionStorage') { function hasStorage(
return getStorage(key, storageType) !== null key: string,
storageType: StorageLike = 'sessionStorage',
options?: StorageOptions,
) {
const { prefix, prefixKey } = options ?? {}
const _prefix = prefix ? prefixKey || APP_CATCH_KEY_PREFIX : ''
const storage =
storageType === 'localStorage' ? window.localStorage : window.sessionStorage
return !!Object.keys(storage).find((curr) => curr === _prefix + key)
} }
/** /**
* *
* @param key key * @param key key
* @param value * @param type
* @param options
*
* sessionStorage
*/ */
function setStorage<T = unknown>( function setStorage<T = unknown>(
key: string, key: string,
value: T, value: T,
type: StorageLike = 'sessionStorage', storageType: StorageLike = 'sessionStorage',
options?: StorageOptions,
) { ) {
if (!key) { if (!key) {
console.error('Failed to set stored data: key is empty or undefined') console.error(
`[setStorage]: Failed to set stored data: key ${key} is empty`,
)
return return
} }
const { prefix, prefixKey } = options ?? {}
const _prefix = prefix ? prefixKey || APP_CATCH_KEY_PREFIX : ''
try { try {
const waitCacheValue = JSON.stringify(value) const waitCacheValue = JSON.stringify(value)
type === 'localStorage' storageType === 'localStorage'
? window.localStorage.setItem(key, waitCacheValue) ? window.localStorage.setItem(_prefix + key, waitCacheValue)
: window.sessionStorage.setItem(key, waitCacheValue) : window.sessionStorage.setItem(_prefix + key, waitCacheValue)
} catch (error) { } catch (error) {
console.error(`Failed to set stored data for key '${key}'`, error) console.error(
`[setStorage]: Failed to set stored data for key '${key}'`,
error,
)
} }
} }
function getStorage<T = unknown>( function getStorage<T = unknown>(
key: string, key: string,
storageType: StorageLike, storageType: StorageLike,
defaultValue: T, options?: StorageOptions<T>,
): T ): T
function getStorage<T = unknown>( function getStorage<T = unknown>(
key: string, key: string,
storageType?: StorageLike, storageType?: StorageLike,
defaultValue?: T, options?: StorageOptions<T>,
): T | null ): T | null
/** /**
* *
* @param key cache * @param key key
* @param storageType session or local * @param type
* @param defaultValue default value * @param options
* *
* * sessionStorage
*/ */
function getStorage<T = unknown>( function getStorage<T = unknown>(
key: string, key: string,
storageType: StorageLike = 'sessionStorage', storageType: StorageLike = 'sessionStorage',
defaultValue?: T, options?: StorageOptions<T>,
): T | null { ): T | null {
const { prefix, prefixKey, defaultValue } = options ?? {}
const _prefix = prefix ? prefixKey || APP_CATCH_KEY_PREFIX : ''
try { try {
const data = const data =
storageType === 'localStorage' storageType === 'localStorage'
? window.localStorage.getItem(key) ? window.localStorage.getItem(_prefix + key)
: window.sessionStorage.getItem(key) : window.sessionStorage.getItem(_prefix + key)
if (data === null) { if (data === null) {
return defaultValue ?? null return defaultValue ?? null
@ -87,7 +119,10 @@ function getStorage<T = unknown>(
return JSON.parse(data) as T return JSON.parse(data) as T
} catch (error) { } catch (error) {
console.error(`Failed to get stored data for key '${key}'`, error) console.error(
`[getStorage]: Failed to get stored data for key '${key}'`,
error,
)
return defaultValue ?? null return defaultValue ?? null
} }
@ -96,43 +131,79 @@ function getStorage<T = unknown>(
/** /**
* *
* @param key key * @param key key
* @param type
* @param options
* *
* key: * sessionStorage
* - all: 删除所有缓存值 *
* - all-sessionStorage: 删除所有 sessionStorage * __all____all_sessionStorage____all_localStorage__ key
* - all-localStorage: 删除所有 localStorage * sessionStorage localStorage
*
* @example
* removeStorage('__all__') // 清空所有缓存
* removeStorage('__all_sessionStorage__') // 清空 sessionStorage 缓存
* removeStorage('__all_localStorage__') // 清空 localStorage 缓存
* removeStorage('signing') // 清空 session 中 signing 缓存字段
*/ */
function removeStorage( function removeStorage(
key: RemoveStorageKey, key: RemoveStorageKey,
type: StorageLike = 'sessionStorage', storageType: RemoveStorageType = 'sessionStorage',
options?: StorageOptions,
) { ) {
if (!key) {
console.error(
`[removeStorage]: Failed to remove stored data: key ${key} is empty or undefined`,
)
return
}
const { prefix, prefixKey } = options ?? {}
const _prefix = prefix ? prefixKey || APP_CATCH_KEY_PREFIX : ''
const localStorageKeys = Object.keys(window.localStorage)
const sessionStorageKeys = Object.keys(window.sessionStorage)
const remove = (isAll: boolean, removeType?: StorageLike) => {
const keys = isAll
? [...sessionStorageKeys, ...localStorageKeys]
: removeType === 'localStorage'
? localStorageKeys
: sessionStorageKeys
keys.forEach((curr) => {
if (key === '__all__') {
window.sessionStorage.removeItem(_prefix + curr)
window.localStorage.removeItem(_prefix + curr)
} else {
removeType === 'localStorage'
? window.localStorage.removeItem(_prefix + curr)
: window.sessionStorage.removeItem(_prefix + curr)
}
})
}
switch (key) { switch (key) {
case 'all': case '__all__':
window.window.localStorage.clear() remove(true)
window.sessionStorage.clear()
break break
case 'all-sessionStorage': case '__all_sessionStorage__':
window.sessionStorage.clear() remove(false, 'sessionStorage')
break break
case 'all-localStorage': case '__all_localStorage__':
window.localStorage.clear() remove(false, 'localStorage')
break break
default: default:
if (!key) { storageType === 'localStorage'
console.error('Failed to remove stored data: key is empty or undefined') ? window.localStorage.removeItem(_prefix + key)
: window.sessionStorage.removeItem(_prefix + key)
return break
}
type === 'localStorage'
? window.localStorage.removeItem(key)
: window.sessionStorage.removeItem(key)
} }
} }

View File

@ -2,7 +2,6 @@ import './index.scss'
import { import {
NCard, NCard,
NLayout,
NDescriptions, NDescriptions,
NDescriptionsItem, NDescriptionsItem,
NTag, NTag,
@ -87,7 +86,7 @@ const Dashboard = defineComponent({
}, },
render() { render() {
return ( return (
<NLayout class="dashboard-layout layout-full"> <NFlex vertical>
<NCard> <NCard>
{{ {{
header: () => <RIcon name="ray" size="64" />, header: () => <RIcon name="ray" size="64" />,
@ -126,7 +125,7 @@ const Dashboard = defineComponent({
<NCard title="友情链接"> <NCard title="友情链接">
<RayLink /> <RayLink />
</NCard> </NCard>
</NLayout> </NFlex>
) )
}, },
}) })

View File

@ -0,0 +1,97 @@
/**
*
* @author Ray <https://github.com/XiaoDaiGua-Ray>
*
* @date 2024-01-24
*
* @workspace ray-template
*
* @remark
*/
import { NButton, NCard, NFlex, NInput } from 'naive-ui'
import { getStorage, setStorage, removeStorage, hasStorage } from '@/utils'
export default defineComponent({
name: 'CacheDemo',
setup() {
const nameValue = ref('Ray')
const currentPrefix = ref('ray:')
return {
nameValue,
currentPrefix,
}
},
render() {
return (
<NCard title="cache 工具包示例">
{{
default: () => (
<NFlex vertical>
<h3> setStorage </h3>
<NInput v-model:value={this.nameValue} placeholder="请输入姓名" />
<NInput
v-model:value={this.currentPrefix}
placeholder="请输入前缀"
/>
</NFlex>
),
action: () => (
<NFlex>
<NButton
onClick={() => {
setStorage('name', this.nameValue, 'sessionStorage', {
prefix: true,
prefixKey: this.currentPrefix,
})
window.$message.success('设置成功')
}}
>
setStorage
</NButton>
<NButton
onClick={() => {
const name = getStorage('name', 'sessionStorage', {
prefix: true,
prefixKey: this.currentPrefix,
})
window.$message.success(`获取到的姓名为:${name}`)
}}
>
getStorage
</NButton>
<NButton
onClick={() => {
removeStorage('name', 'sessionStorage', {
prefix: true,
prefixKey: this.currentPrefix,
})
window.$message.success('删除成功')
}}
>
removeStorage
</NButton>
<NButton
onClick={() => {
const cacheKey = hasStorage('name', 'sessionStorage', {
prefix: true,
prefixKey: this.currentPrefix,
})
window.$message.success(`是否存在:${cacheKey}`)
}}
>
hasStorage
</NButton>
</NFlex>
),
}}
</NCard>
)
},
})