mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-04-05 19:42:07 +08:00
version: v4.6.2-beta
This commit is contained in:
parent
bb61081ddd
commit
cc0577eb8f
68
CHANGELOG.md
68
CHANGELOG.md
@ -1,5 +1,73 @@
|
||||
# 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
|
||||
|
||||
## Feats
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ray-template",
|
||||
"private": false,
|
||||
"version": "4.6.1",
|
||||
"version": "4.6.2-beta",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0",
|
||||
@ -48,7 +48,7 @@
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"print-js": "^1.6.0",
|
||||
"vue": "^3.4.14",
|
||||
"vue": "^3.4.15",
|
||||
"vue-hooks-plus": "1.8.5",
|
||||
"vue-i18n": "^9.9.0",
|
||||
"vue-router": "^4.2.5",
|
||||
|
196
pnpm-lock.yaml
generated
196
pnpm-lock.yaml
generated
@ -7,7 +7,7 @@ settings:
|
||||
dependencies:
|
||||
'@vueuse/core':
|
||||
specifier: ^10.7.1
|
||||
version: 10.7.1(vue@3.4.14)
|
||||
version: 10.7.1(vue@3.4.15)
|
||||
awesome-qr:
|
||||
specifier: 2.1.5-rc.0
|
||||
version: 2.1.5-rc.0
|
||||
@ -43,10 +43,10 @@ dependencies:
|
||||
version: 1.1.0
|
||||
naive-ui:
|
||||
specifier: ^2.37.3
|
||||
version: 2.37.3(vue@3.4.14)
|
||||
version: 2.37.3(vue@3.4.15)
|
||||
pinia:
|
||||
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:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0(pinia@2.1.7)
|
||||
@ -54,17 +54,17 @@ dependencies:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
vue:
|
||||
specifier: ^3.4.14
|
||||
version: 3.4.14(typescript@5.2.2)
|
||||
specifier: ^3.4.15
|
||||
version: 3.4.15(typescript@5.2.2)
|
||||
vue-hooks-plus:
|
||||
specifier: 1.8.5
|
||||
version: 1.8.5(vue@3.4.14)
|
||||
version: 1.8.5(vue@3.4.15)
|
||||
vue-i18n:
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0(vue@3.4.14)
|
||||
version: 9.9.0(vue@3.4.15)
|
||||
vue-router:
|
||||
specifier: ^4.2.5
|
||||
version: 4.2.5(vue@3.4.14)
|
||||
version: 4.2.5(vue@3.4.15)
|
||||
xlsx:
|
||||
specifier: ^0.18.5
|
||||
version: 0.18.5
|
||||
@ -108,10 +108,10 @@ devDependencies:
|
||||
version: 6.5.0(eslint@8.52.0)(typescript@5.2.2)
|
||||
'@vitejs/plugin-vue':
|
||||
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':
|
||||
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':
|
||||
specifier: 1.2.4
|
||||
version: 1.2.4(vue-hooks-plus@1.8.5)
|
||||
@ -183,7 +183,7 @@ devDependencies:
|
||||
version: 0.16.6(@vueuse/core@10.7.1)
|
||||
unplugin-vue-components:
|
||||
specifier: ^0.25.2
|
||||
version: 0.25.2(vue@3.4.14)
|
||||
version: 0.25.2(vue@3.4.15)
|
||||
vite:
|
||||
specifier: ^5.0.11
|
||||
version: 5.0.11(@types/node@20.4.7)(sass@1.69.5)
|
||||
@ -927,12 +927,12 @@ packages:
|
||||
css-render: 0.15.12
|
||||
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==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.11
|
||||
dependencies:
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/@emotion/hash@0.8.0:
|
||||
@ -1430,7 +1430,7 @@ packages:
|
||||
magic-string: 0.30.5
|
||||
mlly: 1.4.1
|
||||
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
|
||||
dev: true
|
||||
|
||||
@ -1492,7 +1492,7 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
unplugin: 1.4.0
|
||||
vue-i18n: 9.9.0(vue@3.4.14)
|
||||
vue-i18n: 9.9.0(vue@3.4.15)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
@ -2111,7 +2111,7 @@ packages:
|
||||
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
|
||||
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==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
@ -2122,12 +2122,12 @@ packages:
|
||||
'@babel/plugin-transform-typescript': 7.23.6(@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)
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
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==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
peerDependencies:
|
||||
@ -2135,7 +2135,7 @@ packages:
|
||||
vue: ^3.2.25
|
||||
dependencies:
|
||||
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
|
||||
|
||||
/@volar/language-core@1.10.1:
|
||||
@ -2163,7 +2163,7 @@ packages:
|
||||
vue-hooks-plus: ^1.5.2
|
||||
dependencies:
|
||||
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
|
||||
|
||||
/@vue/babel-helper-vue-transform-on@1.1.5:
|
||||
@ -2207,11 +2207,11 @@ packages:
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-core@3.4.14:
|
||||
resolution: {integrity: sha512-ro4Zzl/MPdWs7XwxT7omHRxAjMbDFRZEEjD+2m3NBf8YzAe3HuoSEZosXQo+m1GQ1G3LQ1LdmNh1RKTYe+ssEg==}
|
||||
/@vue/compiler-core@3.4.15:
|
||||
resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.6
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/shared': 3.4.15
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.0.2
|
||||
@ -2230,11 +2230,11 @@ packages:
|
||||
'@vue/shared': 3.3.8
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-dom@3.4.14:
|
||||
resolution: {integrity: sha512-nOZTY+veWNa0DKAceNWxorAbWm0INHdQq7cejFaWM1WYnoNSJbSEKYtE7Ir6lR/+mo9fttZpPVI9ZFGJ1juUEQ==}
|
||||
/@vue/compiler-dom@3.4.15:
|
||||
resolution: {integrity: sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==}
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.4.14
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/compiler-core': 3.4.15
|
||||
'@vue/shared': 3.4.15
|
||||
|
||||
/@vue/compiler-sfc@3.3.8:
|
||||
resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==}
|
||||
@ -2251,14 +2251,14 @@ packages:
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-sfc@3.4.14:
|
||||
resolution: {integrity: sha512-1vHc9Kv1jV+YBZC/RJxQJ9JCxildTI+qrhtDh6tPkR1O8S+olBUekimY0km0ZNn8nG1wjtFAe9XHij+YLR8cRQ==}
|
||||
/@vue/compiler-sfc@3.4.15:
|
||||
resolution: {integrity: sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==}
|
||||
dependencies:
|
||||
'@babel/parser': 7.23.6
|
||||
'@vue/compiler-core': 3.4.14
|
||||
'@vue/compiler-dom': 3.4.14
|
||||
'@vue/compiler-ssr': 3.4.14
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/compiler-core': 3.4.15
|
||||
'@vue/compiler-dom': 3.4.15
|
||||
'@vue/compiler-ssr': 3.4.15
|
||||
'@vue/shared': 3.4.15
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.5
|
||||
postcss: 8.4.33
|
||||
@ -2271,11 +2271,11 @@ packages:
|
||||
'@vue/shared': 3.3.8
|
||||
dev: true
|
||||
|
||||
/@vue/compiler-ssr@3.4.14:
|
||||
resolution: {integrity: sha512-bXT6+oAGlFjTYVOTtFJ4l4Jab1wjsC0cfSfOe2B4Z0N2vD2zOBSQ9w694RsCfhjk+bC2DY5Gubb1rHZVii107Q==}
|
||||
/@vue/compiler-ssr@3.4.15:
|
||||
resolution: {integrity: sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==}
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.4.14
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/compiler-dom': 3.4.15
|
||||
'@vue/shared': 3.4.15
|
||||
|
||||
/@vue/devtools-api@6.5.1:
|
||||
resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==}
|
||||
@ -2350,32 +2350,32 @@ packages:
|
||||
'@vue/shared': 3.3.8
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity@3.4.14:
|
||||
resolution: {integrity: sha512-xRYwze5Q4tK7tT2J4uy4XLhK/AIXdU5EBUu9PLnIHcOKXO0uyXpNNMzlQKuq7B+zwtq6K2wuUL39pHA6ZQzObw==}
|
||||
/@vue/reactivity@3.4.15:
|
||||
resolution: {integrity: sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==}
|
||||
dependencies:
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/shared': 3.4.15
|
||||
|
||||
/@vue/runtime-core@3.4.14:
|
||||
resolution: {integrity: sha512-qu+NMkfujCoZL6cfqK5NOfxgXJROSlP2ZPs4CTcVR+mLrwl4TtycF5Tgo0QupkdBL+2kigc6EsJlTcuuZC1NaQ==}
|
||||
/@vue/runtime-core@3.4.15:
|
||||
resolution: {integrity: sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==}
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.4.14
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/reactivity': 3.4.15
|
||||
'@vue/shared': 3.4.15
|
||||
|
||||
/@vue/runtime-dom@3.4.14:
|
||||
resolution: {integrity: sha512-B85XmcR4E7XsirEHVqhmy4HPbRT9WLFWV9Uhie3OapV9m1MEN9+Er6hmUIE6d8/l2sUygpK9RstFM2bmHEUigA==}
|
||||
/@vue/runtime-dom@3.4.15:
|
||||
resolution: {integrity: sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==}
|
||||
dependencies:
|
||||
'@vue/runtime-core': 3.4.14
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/runtime-core': 3.4.15
|
||||
'@vue/shared': 3.4.15
|
||||
csstype: 3.1.3
|
||||
|
||||
/@vue/server-renderer@3.4.14(vue@3.4.14):
|
||||
resolution: {integrity: sha512-pwSKXQfYdJBTpvWHGEYI+akDE18TXAiLcGn+Q/2Fj8wQSHWztoo7PSvfMNqu6NDhp309QXXbPFEGCU5p85HqkA==}
|
||||
/@vue/server-renderer@3.4.15(vue@3.4.15):
|
||||
resolution: {integrity: sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==}
|
||||
peerDependencies:
|
||||
vue: 3.4.14
|
||||
vue: 3.4.15
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.4.14
|
||||
'@vue/shared': 3.4.14
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
'@vue/compiler-ssr': 3.4.15
|
||||
'@vue/shared': 3.4.15
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
|
||||
/@vue/shared@3.3.13:
|
||||
resolution: {integrity: sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==}
|
||||
@ -2385,8 +2385,8 @@ packages:
|
||||
resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==}
|
||||
dev: true
|
||||
|
||||
/@vue/shared@3.4.14:
|
||||
resolution: {integrity: sha512-nmi3BtLpvqXAWoRZ6HQ+pFJOHBU4UnH3vD3opgmwXac7vhaHKA9nj1VeGjMggdB9eLtW83eHyPCmOU1qzdsC7Q==}
|
||||
/@vue/shared@3.4.15:
|
||||
resolution: {integrity: sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==}
|
||||
|
||||
/@vue/typescript@1.8.8(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==}
|
||||
@ -2397,13 +2397,13 @@ packages:
|
||||
- typescript
|
||||
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==}
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.20
|
||||
'@vueuse/metadata': 10.7.1
|
||||
'@vueuse/shared': 10.7.1(vue@3.4.14)
|
||||
vue-demi: 0.14.6(vue@3.4.14)
|
||||
'@vueuse/shared': 10.7.1(vue@3.4.15)
|
||||
vue-demi: 0.14.6(vue@3.4.15)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
@ -2411,10 +2411,10 @@ packages:
|
||||
/@vueuse/metadata@10.7.1:
|
||||
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==}
|
||||
dependencies:
|
||||
vue-demi: 0.14.6(vue@3.4.14)
|
||||
vue-demi: 0.14.6(vue@3.4.15)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
@ -5978,13 +5978,13 @@ packages:
|
||||
minimatch: 3.1.2
|
||||
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==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
dependencies:
|
||||
'@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/lodash': 4.14.202
|
||||
'@types/lodash-es': 4.17.11
|
||||
@ -5999,10 +5999,10 @@ packages:
|
||||
lodash-es: 4.17.21
|
||||
seemly: 0.3.8
|
||||
treemate: 0.3.11
|
||||
vdirs: 0.1.8(vue@3.4.14)
|
||||
vooks: 0.2.12(vue@3.4.14)
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vueuc: 0.4.58(vue@3.4.14)
|
||||
vdirs: 0.1.8(vue@3.4.15)
|
||||
vooks: 0.2.12(vue@3.4.15)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
vueuc: 0.4.58(vue@3.4.15)
|
||||
dev: false
|
||||
|
||||
/nan@2.17.0:
|
||||
@ -6389,10 +6389,10 @@ packages:
|
||||
peerDependencies:
|
||||
pinia: ^2.0.0
|
||||
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
|
||||
|
||||
/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==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.4.0
|
||||
@ -6406,8 +6406,8 @@ packages:
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.5.1
|
||||
typescript: 5.2.2
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue-demi: 0.14.6(vue@3.4.14)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
vue-demi: 0.14.6(vue@3.4.15)
|
||||
dev: false
|
||||
|
||||
/pkg-types@1.0.3:
|
||||
@ -7680,7 +7680,7 @@ packages:
|
||||
dependencies:
|
||||
'@antfu/utils': 0.7.6
|
||||
'@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
|
||||
local-pkg: 0.4.3
|
||||
magic-string: 0.30.5
|
||||
@ -7691,7 +7691,7 @@ packages:
|
||||
- rollup
|
||||
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==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
@ -7714,7 +7714,7 @@ packages:
|
||||
minimatch: 9.0.3
|
||||
resolve: 1.22.5
|
||||
unplugin: 1.4.0
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
@ -7805,13 +7805,13 @@ packages:
|
||||
engines: {node: '>= 0.8'}
|
||||
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==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.11
|
||||
dependencies:
|
||||
evtd: 0.2.4
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/vite-plugin-cdn2@0.15.2:
|
||||
@ -7994,16 +7994,16 @@ packages:
|
||||
fsevents: 2.3.3
|
||||
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==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
dependencies:
|
||||
evtd: 0.2.4
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
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==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
@ -8015,7 +8015,7 @@ packages:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
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):
|
||||
resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
|
||||
@ -8035,7 +8035,7 @@ packages:
|
||||
- supports-color
|
||||
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==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.25
|
||||
@ -8047,9 +8047,9 @@ packages:
|
||||
qs: 6.11.2
|
||||
query-string: 7.1.3
|
||||
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==}
|
||||
engines: {node: '>= 16'}
|
||||
peerDependencies:
|
||||
@ -8058,15 +8058,15 @@ packages:
|
||||
'@intlify/core-base': 9.9.0
|
||||
'@intlify/shared': 9.9.0
|
||||
'@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==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.5.1
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/vue-template-compiler@2.7.14:
|
||||
@ -8088,34 +8088,34 @@ packages:
|
||||
typescript: 5.2.2
|
||||
dev: true
|
||||
|
||||
/vue@3.4.14(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-Rop5Al/ZcBbBz+KjPZaZDgHDX0kUP4duEzDbm+1o91uxYUNmJrZSBuegsNIJvUGy+epLevNRNhLjm08VKTgGyw==}
|
||||
/vue@3.4.15(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.4.14
|
||||
'@vue/compiler-sfc': 3.4.14
|
||||
'@vue/runtime-dom': 3.4.14
|
||||
'@vue/server-renderer': 3.4.14(vue@3.4.14)
|
||||
'@vue/shared': 3.4.14
|
||||
'@vue/compiler-dom': 3.4.15
|
||||
'@vue/compiler-sfc': 3.4.15
|
||||
'@vue/runtime-dom': 3.4.15
|
||||
'@vue/server-renderer': 3.4.15(vue@3.4.15)
|
||||
'@vue/shared': 3.4.15
|
||||
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==}
|
||||
peerDependencies:
|
||||
vue: ^3.0.11
|
||||
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
|
||||
css-render: 0.15.12
|
||||
evtd: 0.2.4
|
||||
seemly: 0.3.8
|
||||
vdirs: 0.1.8(vue@3.4.14)
|
||||
vooks: 0.2.12(vue@3.4.14)
|
||||
vue: 3.4.14(typescript@5.2.2)
|
||||
vdirs: 0.1.8(vue@3.4.15)
|
||||
vooks: 0.2.12(vue@3.4.15)
|
||||
vue: 3.4.15(typescript@5.2.2)
|
||||
dev: false
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
|
@ -17,10 +17,16 @@
|
||||
*/
|
||||
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
|
||||
const appLockScreen = useStorage('isAppLockScreen', false, sessionStorage, {
|
||||
mergeDefaults: true,
|
||||
})
|
||||
const appLockScreen = useStorage(
|
||||
APP_CATCH_KEY.isAppLockScreen,
|
||||
false,
|
||||
sessionStorage,
|
||||
{
|
||||
mergeDefaults: true,
|
||||
},
|
||||
)
|
||||
|
||||
const useAppLockScreen = () => {
|
||||
const setLockAppScreen = (bool: boolean) => {
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
getStorage,
|
||||
} from '@/utils'
|
||||
import { useSettingGetters } from '@/store'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
|
||||
import type { SettingState } from '@/store/modules/setting/type'
|
||||
|
||||
@ -34,7 +35,7 @@ export default defineComponent({
|
||||
const body = document.body
|
||||
|
||||
const primaryColorOverride = getStorage<SettingState>(
|
||||
'piniaSettingStore',
|
||||
APP_CATCH_KEY.appPiniaSettingStore,
|
||||
'localStorage',
|
||||
) // 获取缓存 naive ui 配置项
|
||||
|
||||
|
@ -19,15 +19,18 @@ import { RModal } from '@/components'
|
||||
|
||||
import { getStorage, setStorage } from '@/utils'
|
||||
import { useSigningActions } from '@/store'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppVersionProvider',
|
||||
setup() {
|
||||
const storageKey = 'appVersionProvider'
|
||||
const {
|
||||
pkg: { version },
|
||||
} = __APP_CFG__
|
||||
const cacheVersion = getStorage<string>(storageKey, 'localStorage')
|
||||
const cacheVersion = getStorage<string>(
|
||||
APP_CATCH_KEY.appVersionProvider,
|
||||
'localStorage',
|
||||
)
|
||||
const modalShow = ref(false)
|
||||
const { logout } = useSigningActions()
|
||||
|
||||
@ -36,7 +39,11 @@ export default defineComponent({
|
||||
if (version !== cacheVersion) {
|
||||
modalShow.value = true
|
||||
|
||||
setStorage<string>(storageKey, version, 'localStorage')
|
||||
setStorage<string>(
|
||||
APP_CATCH_KEY.appVersionProvider,
|
||||
version,
|
||||
'localStorage',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,27 +77,53 @@ export const APP_MENU_CONFIG: Readonly<AppMenuConfig> = {
|
||||
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 暂不开放
|
||||
*
|
||||
* 说明:
|
||||
* - signing: 登陆信息缓存 key
|
||||
* - localeLanguage: 国际化默认缓存 key
|
||||
* - token: token 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 = {
|
||||
signing: 'signing',
|
||||
localeLanguage: 'localeLanguage',
|
||||
token: 'token',
|
||||
appMenuKey: 'menuKey',
|
||||
appPiniaSettingStore: 'piniaSettingStore',
|
||||
appPiniaKeepAliveStore: 'piniaKeepAliveStore',
|
||||
appPiniaMenuStore: 'piniaMenuStore',
|
||||
appPiniaSigningStore: 'piniaSigningStore',
|
||||
appVersionProvider: 'appVersionProvider',
|
||||
isAppLockScreen: 'isAppLockScreen',
|
||||
} as const
|
||||
|
||||
/**
|
||||
*
|
||||
* 系统内容切换动画配置
|
||||
* 但是在配置的时候,会自动拼接一个 `transform` 前缀
|
||||
* 例如: `transform-fade-bottom`
|
||||
*/
|
||||
export const CONTENT_TRANSITION_OPTIONS = [
|
||||
{
|
||||
|
@ -21,12 +21,11 @@ export const APP_THEME: AppTheme = {
|
||||
*/
|
||||
appThemeColors: [
|
||||
'#2d8cf0',
|
||||
'#0960bd',
|
||||
'#536dfe',
|
||||
'#ff5c93',
|
||||
'#3f9eff',
|
||||
'#ff42bc',
|
||||
'#ee4f12',
|
||||
'#9c27b0',
|
||||
'#ff9800',
|
||||
'#a6e4f7',
|
||||
'#dbcb02',
|
||||
'#18A058',
|
||||
],
|
||||
/** 系统主题色 */
|
||||
@ -60,7 +59,14 @@ export const APP_THEME: AppTheme = {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
appNaiveUIThemeOverrides: {},
|
||||
appNaiveUIThemeOverrides: {
|
||||
dark: {},
|
||||
light: {},
|
||||
},
|
||||
appNaiveUIThemeOverridesCommon: {
|
||||
dark: {},
|
||||
light: {},
|
||||
},
|
||||
/**
|
||||
*
|
||||
* 配置 echart 主题颜色
|
||||
|
@ -11,6 +11,29 @@
|
||||
|
||||
import { useSettingActions, useSettingGetters } from '@/store'
|
||||
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 = () => {
|
||||
/**
|
||||
@ -45,6 +68,7 @@ export const useTheme = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('appTheme', true)
|
||||
setThemeOverrides(true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,6 +82,7 @@ export const useTheme = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('appTheme', false)
|
||||
setThemeOverrides(false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,6 +102,7 @@ export const useTheme = () => {
|
||||
const { updateSettingState } = useSettingActions()
|
||||
|
||||
updateSettingState('appTheme', !theme)
|
||||
setThemeOverrides(!theme)
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -468,6 +468,7 @@ export default defineComponent({
|
||||
align="center"
|
||||
justify="space-between"
|
||||
inline
|
||||
size={[16, 0]}
|
||||
>
|
||||
<RIcon
|
||||
name="expanded"
|
||||
@ -557,7 +558,7 @@ export default defineComponent({
|
||||
align="center"
|
||||
inline
|
||||
wrap={false}
|
||||
size={[6, 6]}
|
||||
size={[8, 0]}
|
||||
>
|
||||
<RIcon
|
||||
name="expanded"
|
||||
|
@ -11,102 +11,48 @@ $globalSearchWidth: 650px;
|
||||
box-sizing: border-box;
|
||||
|
||||
& .global-search__card {
|
||||
width: $globalSearchWidth;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
min-width: 800px;
|
||||
|
||||
& .ray-icon {
|
||||
color: var(--ray-theme-primary-color);
|
||||
}
|
||||
|
||||
& .global-search__card-header {
|
||||
margin-bottom: 12px;
|
||||
& .n-card__action {
|
||||
padding: 16px 12px 12px 12px;
|
||||
}
|
||||
|
||||
& .global-search__card-content {
|
||||
height: auto;
|
||||
max-height: calc(100% - 98px);
|
||||
padding: 8px 0;
|
||||
& .n-card__content {
|
||||
min-height: 115px;
|
||||
}
|
||||
|
||||
& .global-search__empty {
|
||||
margin: 24px;
|
||||
}
|
||||
& .content-item {
|
||||
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 {
|
||||
font-size: 18px;
|
||||
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;
|
||||
}
|
||||
& .content-item-icon {
|
||||
@include flexCenter;
|
||||
}
|
||||
}
|
||||
|
||||
& .global-search__card-footer {
|
||||
width: 100%;
|
||||
|
||||
& .card-footer__tip-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
& .item-icon {
|
||||
min-width: 24px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--n-border-color);
|
||||
padding: 3px 6px;
|
||||
background-color: var(--n-action-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.global-search--dark {
|
||||
@include useAppTheme('dark') {
|
||||
& .global-search__card {
|
||||
background-color: #242424;
|
||||
& .item-icon,
|
||||
& .item-label {
|
||||
transform: scale(0.75);
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
& .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,
|
||||
&:hover {
|
||||
background-color: var(--ray-theme-primary-fade-color);
|
||||
|
@ -20,7 +20,15 @@
|
||||
|
||||
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 { queryElements, addClass, removeClass } from '@/utils'
|
||||
@ -62,22 +70,17 @@ export default defineComponent({
|
||||
})
|
||||
const helperTipOptions = [
|
||||
{
|
||||
icon: 'cmd / ctrl + k',
|
||||
label: '唤起',
|
||||
plain: true,
|
||||
},
|
||||
{
|
||||
icon: '↑ ↓',
|
||||
icon: ['↑', '↓'],
|
||||
label: '切换',
|
||||
plain: true,
|
||||
},
|
||||
{
|
||||
icon: '↵',
|
||||
icon: ['↵'],
|
||||
label: '选择',
|
||||
plain: true,
|
||||
},
|
||||
{
|
||||
icon: 'esc',
|
||||
icon: ['esc'],
|
||||
label: '关闭',
|
||||
plain: true,
|
||||
},
|
||||
@ -87,6 +90,7 @@ export default defineComponent({
|
||||
/** 缓存索引 */
|
||||
let preSearchElementIndex = searchElementIndex
|
||||
const { isTabletOrSmaller } = useDevice()
|
||||
const loading = ref(false)
|
||||
|
||||
/** 初始化一些值 */
|
||||
const resetSearchSomeValue = () => {
|
||||
@ -111,10 +115,21 @@ export default defineComponent({
|
||||
const fuzzySearchMenuOptions = (value: string) => {
|
||||
const arr: AppMenuOption[] = []
|
||||
|
||||
if (value) {
|
||||
loading.value = true
|
||||
} else {
|
||||
loading.value = false
|
||||
state.searchOptions = []
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const filterArr = (options: AppMenuOption[]) => {
|
||||
options.forEach((curr) => {
|
||||
for (const curr of options) {
|
||||
if (curr.children?.length && validMenuItemShow(curr)) {
|
||||
filterArr(curr.children)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
/** 处理菜单名与输入值, 不区分大小写 */
|
||||
@ -129,23 +144,27 @@ export default defineComponent({
|
||||
) {
|
||||
arr.push(curr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (value) {
|
||||
filterArr(getMenuOptions.value)
|
||||
|
||||
state.searchOptions = arr
|
||||
} else {
|
||||
state.searchOptions = []
|
||||
}
|
||||
|
||||
nextTick().then(() => {
|
||||
autoFocusingSearchItem()
|
||||
})
|
||||
}
|
||||
|
||||
if (value) {
|
||||
filterArr(getMenuOptions.value)
|
||||
|
||||
state.searchOptions = arr
|
||||
} else {
|
||||
state.searchOptions = []
|
||||
}
|
||||
|
||||
nextTick().then(() => {
|
||||
autoFocusingSearchItem()
|
||||
})
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleSearchItemClick = (option: AppMenuOption) => {
|
||||
const searchItemClick = (option: AppMenuOption) => {
|
||||
if (option) {
|
||||
const { meta } = option
|
||||
|
||||
@ -220,7 +239,11 @@ export default defineComponent({
|
||||
const registerChangeSearchElementIndex = (e: KeyboardEvent) => {
|
||||
const keyCode = e.key
|
||||
|
||||
if (keyCode === 'ArrowUp' || keyCode === 'ArrowDown') {
|
||||
if (
|
||||
keyCode === 'ArrowUp' ||
|
||||
keyCode === 'ArrowDown' ||
|
||||
keyCode === 'Enter'
|
||||
) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
@ -242,7 +265,7 @@ export default defineComponent({
|
||||
const option = state.searchOptions[searchElementIndex]
|
||||
|
||||
if (option) {
|
||||
handleSearchItemClick(option)
|
||||
searchItemClick(option)
|
||||
}
|
||||
|
||||
break
|
||||
@ -259,7 +282,7 @@ export default defineComponent({
|
||||
align="center"
|
||||
class="content-item"
|
||||
{...{
|
||||
onClick: handleSearchItemClick.bind(this, menuOption),
|
||||
onClick: searchItemClick.bind(this, menuOption),
|
||||
data_path: menuOption.path,
|
||||
}}
|
||||
>
|
||||
@ -275,25 +298,31 @@ export default defineComponent({
|
||||
}
|
||||
})
|
||||
|
||||
useEventListener(window, 'keydown', (e: KeyboardEvent) => {
|
||||
registerArouseKeyboard(e)
|
||||
registerChangeSearchElementIndex(e)
|
||||
})
|
||||
useEventListener(
|
||||
window,
|
||||
'keydown',
|
||||
(e: KeyboardEvent) => {
|
||||
registerArouseKeyboard(e)
|
||||
registerChangeSearchElementIndex(e)
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
modelShow,
|
||||
helperTipOptions,
|
||||
fuzzySearchMenuOptions: debounce(fuzzySearchMenuOptions, 300),
|
||||
handleSearchItemClick,
|
||||
searchItemClick,
|
||||
RenderPreIcon,
|
||||
isTabletOrSmaller,
|
||||
SearchItem,
|
||||
loading,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { isTabletOrSmaller, searchOptions } = this
|
||||
const { SearchItem, fuzzySearchMenuOptions } = this
|
||||
const { isTabletOrSmaller, searchOptions, loading } = this
|
||||
const { SearchItem, fuzzySearchMenuOptions, $t } = this
|
||||
|
||||
return isTabletOrSmaller ? (
|
||||
<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__wrapper">
|
||||
<div class="global-search__card">
|
||||
<div class="global-search__card-header">
|
||||
<NInput
|
||||
size="large"
|
||||
v-model:value={this.searchValue}
|
||||
clearable
|
||||
onInput={fuzzySearchMenuOptions.bind(this)}
|
||||
>
|
||||
{{
|
||||
prefix: () => <RIcon name="search" size="24" />,
|
||||
}}
|
||||
</NInput>
|
||||
</div>
|
||||
<NScrollbar class="global-search__card-content">
|
||||
{searchOptions.length ? (
|
||||
<NFlex vertical size={[8, 8]}>
|
||||
{searchOptions.map((curr) => (
|
||||
<SearchItem menuOption={curr} key={curr.fullPath} />
|
||||
<NCard
|
||||
class="global-search__card"
|
||||
headerStyle={{
|
||||
padding: '12px 12px 0 12px',
|
||||
}}
|
||||
contentStyle={{
|
||||
padding: '12px',
|
||||
}}
|
||||
segmented={{
|
||||
action: 'soft',
|
||||
}}
|
||||
>
|
||||
{{
|
||||
header: () => (
|
||||
<NInput
|
||||
size="large"
|
||||
v-model:value={this.searchValue}
|
||||
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>
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
</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>
|
||||
),
|
||||
}}
|
||||
</NCard>
|
||||
</div>
|
||||
</div>
|
||||
</NModal>
|
||||
|
@ -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>
|
||||
)
|
||||
},
|
||||
})
|
@ -29,7 +29,7 @@ import { useSettingGetters, useSettingActions } from '@/store'
|
||||
import type { PropType } from 'vue'
|
||||
import type { Placement } from '@/types'
|
||||
|
||||
const SettingDrawer = defineComponent({
|
||||
export default defineComponent({
|
||||
name: 'SettingDrawer',
|
||||
props: {
|
||||
show: {
|
||||
@ -82,7 +82,7 @@ const SettingDrawer = defineComponent({
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { $t } = this
|
||||
const { $t, changePrimaryColor, updateSettingState } = this
|
||||
|
||||
return (
|
||||
<NDrawer
|
||||
@ -102,7 +102,7 @@ const SettingDrawer = defineComponent({
|
||||
<NColorPicker
|
||||
swatches={APP_THEME.appThemeColors}
|
||||
v-model:value={this.getPrimaryColorOverride.common!.primaryColor}
|
||||
onUpdateValue={this.changePrimaryColor.bind(this)}
|
||||
onUpdateValue={changePrimaryColor.bind(this)}
|
||||
/>
|
||||
<NDivider titlePlacement="center">
|
||||
{$t('headerSettingOptions.ContentTransition')}
|
||||
@ -111,7 +111,7 @@ const SettingDrawer = defineComponent({
|
||||
v-model:value={this.modelSwitchReactive.getContentTransition}
|
||||
options={CONTENT_TRANSITION_OPTIONS}
|
||||
onUpdateValue={(value) => {
|
||||
this.updateSettingState('contentTransition', value)
|
||||
updateSettingState('contentTransition', value)
|
||||
}}
|
||||
/>
|
||||
<NDivider titlePlacement="center">
|
||||
@ -122,7 +122,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getMenuTagSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.updateSettingState('menuTagSwitch', bool)
|
||||
updateSettingState('menuTagSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -130,7 +130,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getBreadcrumbSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.updateSettingState('breadcrumbSwitch', bool)
|
||||
updateSettingState('breadcrumbSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -138,7 +138,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getWatermarkSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.updateSettingState('watermarkSwitch', bool)
|
||||
updateSettingState('watermarkSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -146,7 +146,7 @@ const SettingDrawer = defineComponent({
|
||||
<NSwitch
|
||||
v-model:value={this.modelSwitchReactive.getCopyrightSwitch}
|
||||
onUpdateValue={(bool: boolean) =>
|
||||
this.updateSettingState('copyrightSwitch', bool)
|
||||
updateSettingState('copyrightSwitch', bool)
|
||||
}
|
||||
/>
|
||||
</NDescriptionsItem>
|
||||
@ -157,5 +157,3 @@ const SettingDrawer = defineComponent({
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default SettingDrawer
|
||||
|
13
src/layout/components/SiderBar/components/index.ts
Normal file
13
src/layout/components/SiderBar/components/index.ts
Normal 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,
|
||||
}
|
@ -21,10 +21,13 @@ import './index.scss'
|
||||
|
||||
import { NLayoutHeader, NFlex, NDropdown } from 'naive-ui'
|
||||
import { RIcon } from '@/components'
|
||||
import TooltipIcon from '@/layout/components/SiderBar/components/TooltipIcon'
|
||||
import SettingDrawer from './components/SettingDrawer'
|
||||
import Breadcrumb from './components/Breadcrumb'
|
||||
import GlobalSearch from './components/GlobalSearch'
|
||||
import {
|
||||
TooltipIcon,
|
||||
SettingDrawer,
|
||||
Breadcrumb,
|
||||
GlobalSearch,
|
||||
GlobalSearchButton,
|
||||
} from './components'
|
||||
import AppAvatar from '@/app-components/app/AppAvatar'
|
||||
|
||||
import { LOCAL_OPTIONS } from '@/app-config'
|
||||
@ -39,7 +42,8 @@ import { getVariableToRefs, setVariable } from '@/global-variable'
|
||||
import { useFullscreen } from 'vue-hooks-plus'
|
||||
import { useSettingGetters, useSettingActions } from '@/store'
|
||||
|
||||
import type { IconEventMapOptions, IconEventMap } from './type'
|
||||
import type { IconEventMapOptions } from './type'
|
||||
import type { VNode } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AppSiderBar',
|
||||
@ -77,6 +81,9 @@ export default defineComponent({
|
||||
}),
|
||||
)
|
||||
const iconEventMap: IconEventMapOptions = {
|
||||
search: () => {
|
||||
globalSearchShown.value = true
|
||||
},
|
||||
setting: () => {
|
||||
showSettings.value = true
|
||||
},
|
||||
@ -90,9 +97,6 @@ export default defineComponent({
|
||||
|
||||
toggleFullscreen()
|
||||
},
|
||||
search: () => {
|
||||
globalSearchShown.value = true
|
||||
},
|
||||
lock: () => {
|
||||
updateSettingState('lockScreenSwitch', true)
|
||||
},
|
||||
@ -105,6 +109,15 @@ export default defineComponent({
|
||||
iconEventMap[key]?.()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param vnode 需要渲染的节点
|
||||
* @returns 如果是平板或者更小的设备, 就返回 null, 否则返回 vnode
|
||||
*/
|
||||
const isRenderVNode = (vnode: VNode) => {
|
||||
return isTabletOrSmaller.value ? null : vnode
|
||||
}
|
||||
|
||||
return {
|
||||
leftIconOptions,
|
||||
rightTooltipIconOptions,
|
||||
@ -114,9 +127,18 @@ export default defineComponent({
|
||||
getDrawerPlacement,
|
||||
getBreadcrumbSwitch,
|
||||
globalSearchShown,
|
||||
isRenderVNode,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const {
|
||||
rightTooltipIconOptions,
|
||||
leftIconOptions,
|
||||
getDrawerPlacement,
|
||||
getBreadcrumbSwitch,
|
||||
} = this
|
||||
const { toolIconClick, updateLocale, isRenderVNode } = this
|
||||
|
||||
return (
|
||||
<NLayoutHeader class="layout-header" bordered>
|
||||
<GlobalSearch v-model:show={this.globalSearchShown} />
|
||||
@ -126,7 +148,7 @@ export default defineComponent({
|
||||
justify="space-between"
|
||||
>
|
||||
<NFlex align="center">
|
||||
{this.leftIconOptions.map((curr) => (
|
||||
{leftIconOptions.map((curr) => (
|
||||
<TooltipIcon
|
||||
key={curr.name}
|
||||
iconName={curr.name}
|
||||
@ -134,13 +156,22 @@ export default defineComponent({
|
||||
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip
|
||||
}
|
||||
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 align="center">
|
||||
{this.rightTooltipIconOptions.map((curr) => (
|
||||
<NFlex align="center" size={[16, 0]}>
|
||||
{isRenderVNode(
|
||||
<GlobalSearchButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
this.globalSearchShown = true
|
||||
}}
|
||||
/>,
|
||||
)}
|
||||
{rightTooltipIconOptions.map((curr) => (
|
||||
<TooltipIcon
|
||||
key={curr.name}
|
||||
iconName={curr.name}
|
||||
@ -148,14 +179,12 @@ export default defineComponent({
|
||||
isRef(curr.tooltip) ? curr.tooltip.value : curr.tooltip
|
||||
}
|
||||
customClassName={curr.iconClass}
|
||||
onClick={this.toolIconClick.bind(this, curr.name)}
|
||||
onClick={toolIconClick.bind(this, curr.name)}
|
||||
/>
|
||||
))}
|
||||
<NDropdown
|
||||
options={LOCAL_OPTIONS}
|
||||
onSelect={(key: string | number) =>
|
||||
this.updateLocale(String(key))
|
||||
}
|
||||
onSelect={(key: string | number) => updateLocale(String(key))}
|
||||
trigger="click"
|
||||
>
|
||||
<RIcon
|
||||
@ -176,7 +205,7 @@ export default defineComponent({
|
||||
</NFlex>
|
||||
<SettingDrawer
|
||||
v-model:show={this.showSettings}
|
||||
placement={this.getDrawerPlacement}
|
||||
placement={getDrawerPlacement}
|
||||
/>
|
||||
</NLayoutHeader>
|
||||
)
|
||||
|
@ -126,15 +126,7 @@ export const createRightIconOptions = (opts: IconOptionsFC) => {
|
||||
eventKey: 'setting',
|
||||
},
|
||||
]
|
||||
const notTableOrSmallerOptions: IconOptions[] = [
|
||||
{
|
||||
name: 'search',
|
||||
size: 18,
|
||||
tooltip: t('headerTooltip.Search'),
|
||||
eventKey: 'search',
|
||||
},
|
||||
...basicOptions,
|
||||
]
|
||||
const notTableOrSmallerOptions: IconOptions[] = [...basicOptions]
|
||||
const tableOrSmallerOptions: IconOptions[] = [...basicOptions]
|
||||
|
||||
return isTabletOrSmaller!.value
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { DropdownOption } from 'naive-ui'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { ComputedRef, Ref, VNode } from 'vue'
|
||||
|
||||
export interface IconEventMapOptions {
|
||||
[propName: string]: (...args: unknown[]) => unknown
|
||||
@ -21,6 +21,7 @@ export interface IconOptions {
|
||||
eventKey?: string
|
||||
dropdown?: IconDropdownOptions
|
||||
iconClass?: string
|
||||
render?: VNode | JSX.Element
|
||||
}
|
||||
|
||||
export interface IconOptionsFC {
|
||||
|
@ -16,7 +16,9 @@
|
||||
right: -40px;
|
||||
top: -40px;
|
||||
@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 {
|
||||
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;
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
.layout-footer-wrapper {
|
||||
padding: 0 20px 8px 20px;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.ray-template--dark .layout-footer-wrapper {
|
||||
background-color: $layoutFooterBackgroundColorDark;
|
||||
}
|
||||
|
||||
.ray-template--light .layout-footer-wrapper {
|
||||
background-color: $layoutFooterBackgroundColorLight;
|
||||
}
|
||||
|
6
src/layout/default/index.ts
Normal file
6
src/layout/default/index.ts
Normal 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 }
|
@ -13,10 +13,12 @@ import './index.scss'
|
||||
|
||||
import { NLayout, NLayoutContent } from 'naive-ui'
|
||||
import Menu from './components/Menu'
|
||||
import ContentWrapper from '@/layout/default/ContentWrapper'
|
||||
import FooterWrapper from '@/layout/default/FooterWrapper'
|
||||
import HeaderWrapper from './default/HeaderWrapper'
|
||||
import FeatureWrapper from './default/FeatureWrapper'
|
||||
import {
|
||||
ContentWrapper,
|
||||
FooterWrapper,
|
||||
HeaderWrapper,
|
||||
FeatureWrapper,
|
||||
} from './default'
|
||||
|
||||
import { LAYOUT_CONTENT_REF } from '@/app-config'
|
||||
import { layoutHeaderCssVars } from '@/layout/layoutResize'
|
||||
|
@ -125,11 +125,9 @@ export const naiveLocales = (key: string) => {
|
||||
* @remark 未避免出现加载语言错误问题, 故而在 `main.ts` 注册时, 应优先加载 `i18n` 避免出现该问题
|
||||
*/
|
||||
export const getAppDefaultLanguage = () => {
|
||||
const language = getStorage(
|
||||
APP_CATCH_KEY.localeLanguage,
|
||||
'localStorage',
|
||||
SYSTEM_DEFAULT_LOCAL,
|
||||
)
|
||||
const language = getStorage(APP_CATCH_KEY.localeLanguage, 'localStorage', {
|
||||
defaultValue: SYSTEM_DEFAULT_LOCAL,
|
||||
})
|
||||
|
||||
return language as keyof AppCurrentAppMessages
|
||||
}
|
||||
|
@ -23,5 +23,6 @@
|
||||
"SvgIcon": "SVG Icon",
|
||||
"TemplateHooks": "Template Api",
|
||||
"Modal": "Modal",
|
||||
"ContextMenu": "Right Click Menu"
|
||||
"ContextMenu": "Right Click Menu",
|
||||
"CacheDemo": "Cache Utils Demo"
|
||||
}
|
||||
|
@ -23,5 +23,6 @@
|
||||
"SvgIcon": "SVG 图标",
|
||||
"TemplateHooks": "模板内置 Api",
|
||||
"Modal": "模态框",
|
||||
"ContextMenu": "右键菜单"
|
||||
"ContextMenu": "右键菜单",
|
||||
"CacheDemo": "缓存工具函数"
|
||||
}
|
||||
|
@ -44,7 +44,9 @@ export const permissionRouter = (router: Router) => {
|
||||
const catchRoutePath = getStorage(
|
||||
APP_CATCH_KEY.appMenuKey,
|
||||
'sessionStorage',
|
||||
getRootPath.value,
|
||||
{
|
||||
defaultValue: getRootPath.value,
|
||||
},
|
||||
)
|
||||
const { meta, name } = to
|
||||
|
||||
|
18
src/router/modules/demo/cache-demo.ts
Normal file
18
src/router/modules/demo/cache-demo.ts
Normal 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
|
@ -3,12 +3,19 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { Recordable } from '@/types'
|
||||
import type { DefineComponent, VNode } from 'vue'
|
||||
import type { TagProps } from 'naive-ui'
|
||||
|
||||
export type Component<T = any> =
|
||||
| DefineComponent<{}, {}, any>
|
||||
| (() => Promise<typeof import('*.vue')>)
|
||||
| (() => Promise<T>)
|
||||
|
||||
export interface AppMenuExtraOptions {
|
||||
extraLabel?: string
|
||||
extraIcon?: string | VNode
|
||||
extraType?: TagProps['type']
|
||||
}
|
||||
|
||||
export interface AppRouteMeta {
|
||||
i18nKey?: string
|
||||
icon?: string | VNode
|
||||
@ -21,6 +28,7 @@ export interface AppRouteMeta {
|
||||
keepAlive?: boolean
|
||||
sameLevel?: boolean
|
||||
env?: string | string[]
|
||||
extra?: string | AppMenuExtraOptions
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
import { APP_KEEP_ALIVE } from '@/app-config'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
|
||||
import type { KeepAliveStoreState } from './type'
|
||||
import type { AppMenuOption } from '@/types'
|
||||
@ -76,7 +77,7 @@ export const piniaKeepAliveStore = defineStore(
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'piniaKeepAliveStore',
|
||||
key: APP_CATCH_KEY.appPiniaKeepAliveStore,
|
||||
storage: window.sessionStorage,
|
||||
paths: ['keepAliveInclude'],
|
||||
},
|
||||
|
@ -15,12 +15,14 @@ import { APP_MENU_CONFIG, APP_CATCH_KEY } from '@/app-config'
|
||||
import { RIcon } from '@/components'
|
||||
import { getStorage, isValueType } from '@/utils'
|
||||
import { useAppRoot } from '@/hooks'
|
||||
import { NTag } from 'naive-ui'
|
||||
|
||||
import type {
|
||||
AppMenuOption,
|
||||
MenuTagOptions,
|
||||
AppMenuKey,
|
||||
} from '@/types/modules/app'
|
||||
import type { TagProps } from 'naive-ui'
|
||||
|
||||
/**
|
||||
*
|
||||
@ -143,28 +145,72 @@ export const updateDocumentTitle = (option: AppMenuOption) => {
|
||||
document.title = breadcrumbLabel + ' - ' + spliceTitle
|
||||
}
|
||||
|
||||
export const hasMenuIcon = (option: AppMenuOption) => {
|
||||
const { meta } = option
|
||||
export const createMenuIcon = (option: AppMenuOption) => {
|
||||
const {
|
||||
meta: { icon },
|
||||
} = option
|
||||
|
||||
if (!meta.icon) {
|
||||
if (!icon) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isValueType<object>(meta.icon, 'Object')) {
|
||||
return () => meta.icon
|
||||
if (isValueType<object>(icon, 'Object')) {
|
||||
return () => icon
|
||||
}
|
||||
|
||||
const icon = h(
|
||||
const _icon = h(
|
||||
RIcon,
|
||||
{
|
||||
name: meta!.icon as string,
|
||||
name: icon,
|
||||
size: APP_MENU_CONFIG.menuCollapsedIconSize,
|
||||
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 当作默认激活路由菜单 */
|
||||
@ -173,7 +219,9 @@ export const getCatchMenuKey = () => {
|
||||
const cacheMenuKey = getStorage<AppMenuKey>(
|
||||
APP_CATCH_KEY.appMenuKey,
|
||||
'sessionStorage',
|
||||
getRootPath.value,
|
||||
{
|
||||
defaultValue: getRootPath.value,
|
||||
},
|
||||
)
|
||||
|
||||
return cacheMenuKey
|
||||
|
@ -30,8 +30,9 @@ import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot'
|
||||
import {
|
||||
parseAndFindMatchingNodes,
|
||||
updateDocumentTitle,
|
||||
hasMenuIcon,
|
||||
createMenuIcon,
|
||||
getCatchMenuKey,
|
||||
createMenuExtra,
|
||||
} from './helper'
|
||||
import { useI18n } from '@/hooks'
|
||||
import { getAppRawRoutes } from '@/router/appRouteModules'
|
||||
@ -60,13 +61,23 @@ export const piniaMenuStore = defineStore(
|
||||
})
|
||||
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 { meta } = option
|
||||
const { i18nKey, noLocalTitle } = meta
|
||||
|
||||
/** 设置 label, i18nKey 优先级最高 */
|
||||
const label = computed(() =>
|
||||
meta?.i18nKey ? t(`${meta!.i18nKey}`) : meta?.noLocalTitle,
|
||||
)
|
||||
const label = computed(() => (i18nKey ? t(`${i18nKey}`) : noLocalTitle))
|
||||
/**
|
||||
*
|
||||
* 拼装菜单项
|
||||
@ -84,7 +95,8 @@ export const piniaMenuStore = defineStore(
|
||||
} as AppMenuOption
|
||||
/** 合并 icon */
|
||||
const attr: AppMenuOption = Object.assign({}, route, {
|
||||
icon: hasMenuIcon(option),
|
||||
icon: createMenuIcon(option),
|
||||
extra: createMenuExtra(option),
|
||||
})
|
||||
|
||||
if (option.fullPath === getCatchMenuKey()) {
|
||||
@ -368,7 +380,7 @@ export const piniaMenuStore = defineStore(
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'piniaMenuStore',
|
||||
key: APP_CATCH_KEY.appPiniaMenuStore,
|
||||
storage: window.sessionStorage,
|
||||
paths: ['breadcrumbOptions', 'menuKey', 'menuTagOptions'],
|
||||
},
|
||||
|
@ -2,8 +2,9 @@ import { getAppDefaultLanguage } from '@/locales/helper'
|
||||
import { set } from 'lodash-es'
|
||||
import { colorToRgba, setStorage } from '@/utils'
|
||||
import { useI18n, useDayjs } from '@/hooks'
|
||||
import { APP_THEME } 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 { LocalKey } from '@/hooks'
|
||||
@ -21,9 +22,8 @@ export const piniaSettingStore = defineStore(
|
||||
const settingState = reactive<SettingState>({
|
||||
drawerPlacement: 'right',
|
||||
primaryColorOverride: {
|
||||
...APP_THEME.appNaiveUIThemeOverrides,
|
||||
common: {
|
||||
primaryColor: primaryColor, // 主题色
|
||||
primaryColor: primaryColor,
|
||||
primaryColorHover: primaryColor,
|
||||
},
|
||||
},
|
||||
@ -61,22 +61,24 @@ export const piniaSettingStore = defineStore(
|
||||
setStorage(APP_CATCH_KEY.localeLanguage, key, 'localStorage')
|
||||
}
|
||||
|
||||
/** 切换主题色 */
|
||||
/**
|
||||
*
|
||||
* 切换主题色,传递对应颜色即可更新 naive-ui 的主题色
|
||||
*/
|
||||
const changePrimaryColor = (value: string, alpha = 0.3) => {
|
||||
set(
|
||||
settingState,
|
||||
'settingState.primaryColorOverride.common.primaryColorHover',
|
||||
value,
|
||||
)
|
||||
const alphaColor = colorToRgba(value, alpha)
|
||||
const themeOverrides = {
|
||||
primaryColor: value,
|
||||
primaryColorHover: value,
|
||||
}
|
||||
|
||||
settingState.primaryColorOverride.common = themeOverrides
|
||||
|
||||
const body = document.body
|
||||
|
||||
/** 设置主题色变量 */
|
||||
body.style.setProperty('--ray-theme-primary-color', value)
|
||||
body.style.setProperty(
|
||||
'--ray-theme-primary-fade-color',
|
||||
colorToRgba(value, alpha),
|
||||
)
|
||||
body.style.setProperty('--ray-theme-primary-fade-color', alphaColor)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,6 +110,33 @@ export const piniaSettingStore = defineStore(
|
||||
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 {
|
||||
...toRefs(settingState),
|
||||
updateLocale,
|
||||
@ -117,7 +146,7 @@ export const piniaSettingStore = defineStore(
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'piniaSettingStore',
|
||||
key: APP_CATCH_KEY.appPiniaSettingStore,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { removeStorage } from '@/utils'
|
||||
import { APP_CATCH_KEY } from '@/app-config'
|
||||
|
||||
import type {
|
||||
SigningForm,
|
||||
@ -78,7 +79,7 @@ export const piniaSigningStore = defineStore(
|
||||
*/
|
||||
const logout = () => {
|
||||
window.$message.info('账号退出中...')
|
||||
removeStorage('all-sessionStorage')
|
||||
removeStorage('__all_sessionStorage__')
|
||||
|
||||
setTimeout(() => window.location.reload())
|
||||
}
|
||||
@ -91,7 +92,7 @@ export const piniaSigningStore = defineStore(
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'piniaSigningStore',
|
||||
key: APP_CATCH_KEY.appPiniaSigningStore,
|
||||
paths: ['signingCallback'],
|
||||
storage: sessionStorage,
|
||||
},
|
||||
|
@ -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 .n-spin-content {
|
||||
width: 100%;
|
||||
@ -6,8 +6,18 @@
|
||||
}
|
||||
|
||||
// 拓展 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 {
|
||||
border-left: 4px solid var(--ray-theme-primary-color);
|
||||
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;
|
||||
}
|
||||
|
@ -1,3 +1,11 @@
|
||||
$layoutRouterViewContainer: 18px;
|
||||
$layoutHeaderHeight: 64px;
|
||||
$layoutMenuHeight: 46px;
|
||||
// 内容区域背景色(dark)
|
||||
$layoutContentBackgroundColorDark: #101014;
|
||||
// 内容区域背景色(light)
|
||||
$layoutContentBackgroundColorLight: #f7f9f8;
|
||||
// 底部区域背景色(dark)
|
||||
$layoutFooterBackgroundColorDark: rgba(24, 24, 28, 1);
|
||||
// 底部区域背景色(light)
|
||||
$layoutFooterBackgroundColorLight: rgba(255, 255, 255, 1);
|
||||
|
@ -6,3 +6,4 @@ export type * from './modules/component'
|
||||
export type * from './modules/helper'
|
||||
export type * from './modules/utils'
|
||||
export type * from './modules/vue'
|
||||
export { OperatingSystem } from './modules/utils'
|
||||
|
@ -5,9 +5,11 @@ export type StorageLike = 'sessionStorage' | 'localStorage'
|
||||
|
||||
export type RemoveStorageKey =
|
||||
| string
|
||||
| 'all'
|
||||
| 'all-sessionStorage'
|
||||
| 'all-localStorage'
|
||||
| '__all__'
|
||||
| '__all_sessionStorage__'
|
||||
| '__all_localStorage__'
|
||||
|
||||
export type RemoveStorageType = StorageLike
|
||||
|
||||
export type ValidateValueType =
|
||||
| 'BigUint64Array'
|
||||
@ -88,3 +90,18 @@ export type ElementSelector = string | `attr:${string}`
|
||||
export type MaybeArray<T> = T | T[]
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -78,6 +78,13 @@ export type AppConfigExport = Config & UserConfigExport
|
||||
export interface AppTheme {
|
||||
appThemeColors: string[]
|
||||
appPrimaryColor: AppPrimaryColor
|
||||
appNaiveUIThemeOverrides: GlobalThemeOverrides
|
||||
appNaiveUIThemeOverrides: {
|
||||
dark: GlobalThemeOverrides
|
||||
light: GlobalThemeOverrides
|
||||
}
|
||||
echartTheme: string
|
||||
appNaiveUIThemeOverridesCommon: {
|
||||
dark: GlobalThemeOverrides['common']
|
||||
light: GlobalThemeOverrides['common']
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { OperatingSystem } from '@/types'
|
||||
|
||||
import type {
|
||||
ValidateValueType,
|
||||
DownloadAnyFileDataType,
|
||||
@ -341,3 +343,37 @@ export const callWithAsyncErrorHandling = async <
|
||||
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
|
||||
}
|
||||
|
@ -9,77 +9,109 @@
|
||||
* @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 storageType session or local
|
||||
* @param key 需要删除的缓存值key
|
||||
* @param storageType 需要删除的缓存类型
|
||||
* @param options 配置项
|
||||
*
|
||||
* 查找当前缓存中是否含有某个 key
|
||||
* 默认查找 sessionStorage
|
||||
*/
|
||||
function hasStorage(key: string, storageType: StorageLike = 'sessionStorage') {
|
||||
return getStorage(key, storageType) !== null
|
||||
function hasStorage(
|
||||
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 value 需要缓存的值
|
||||
* @param key 需要删除的缓存值key
|
||||
* @param type 需要删除的缓存类型
|
||||
* @param options 配置项
|
||||
*
|
||||
* 设置缓存值,默认设置 sessionStorage
|
||||
*/
|
||||
function setStorage<T = unknown>(
|
||||
key: string,
|
||||
value: T,
|
||||
type: StorageLike = 'sessionStorage',
|
||||
storageType: StorageLike = 'sessionStorage',
|
||||
options?: StorageOptions,
|
||||
) {
|
||||
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
|
||||
}
|
||||
|
||||
const { prefix, prefixKey } = options ?? {}
|
||||
const _prefix = prefix ? prefixKey || APP_CATCH_KEY_PREFIX : ''
|
||||
|
||||
try {
|
||||
const waitCacheValue = JSON.stringify(value)
|
||||
|
||||
type === 'localStorage'
|
||||
? window.localStorage.setItem(key, waitCacheValue)
|
||||
: window.sessionStorage.setItem(key, waitCacheValue)
|
||||
storageType === 'localStorage'
|
||||
? window.localStorage.setItem(_prefix + key, waitCacheValue)
|
||||
: window.sessionStorage.setItem(_prefix + key, waitCacheValue)
|
||||
} 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>(
|
||||
key: string,
|
||||
storageType: StorageLike,
|
||||
defaultValue: T,
|
||||
options?: StorageOptions<T>,
|
||||
): T
|
||||
|
||||
function getStorage<T = unknown>(
|
||||
key: string,
|
||||
storageType?: StorageLike,
|
||||
defaultValue?: T,
|
||||
options?: StorageOptions<T>,
|
||||
): T | null
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key cache
|
||||
* @param storageType session or local
|
||||
* @param defaultValue default value
|
||||
* @param key 需要删除的缓存值key
|
||||
* @param type 需要删除的缓存类型
|
||||
* @param options 配置项
|
||||
*
|
||||
* 获取缓存值
|
||||
* 获取缓存值,默认获取 sessionStorage
|
||||
*/
|
||||
function getStorage<T = unknown>(
|
||||
key: string,
|
||||
storageType: StorageLike = 'sessionStorage',
|
||||
defaultValue?: T,
|
||||
options?: StorageOptions<T>,
|
||||
): T | null {
|
||||
const { prefix, prefixKey, defaultValue } = options ?? {}
|
||||
const _prefix = prefix ? prefixKey || APP_CATCH_KEY_PREFIX : ''
|
||||
|
||||
try {
|
||||
const data =
|
||||
storageType === 'localStorage'
|
||||
? window.localStorage.getItem(key)
|
||||
: window.sessionStorage.getItem(key)
|
||||
? window.localStorage.getItem(_prefix + key)
|
||||
: window.sessionStorage.getItem(_prefix + key)
|
||||
|
||||
if (data === null) {
|
||||
return defaultValue ?? null
|
||||
@ -87,7 +119,10 @@ function getStorage<T = unknown>(
|
||||
|
||||
return JSON.parse(data) as T
|
||||
} 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
|
||||
}
|
||||
@ -96,43 +131,79 @@ function getStorage<T = unknown>(
|
||||
/**
|
||||
*
|
||||
* @param key 需要删除的缓存值key
|
||||
* @param type 需要删除的缓存类型
|
||||
* @param options 配置项
|
||||
*
|
||||
* key:
|
||||
* - all: 删除所有缓存值
|
||||
* - all-sessionStorage: 删除所有 sessionStorage 缓存值
|
||||
* - all-localStorage: 删除所有 localStorage 缓存值
|
||||
* 删除缓存值,默认删除 sessionStorage
|
||||
*
|
||||
* 预保留了 __all__、__all_sessionStorage__、__all_localStorage__ 三个 key
|
||||
* 分别代表清空所有缓存、清空 sessionStorage 缓存、清空 localStorage 缓存
|
||||
*
|
||||
* @example
|
||||
* removeStorage('__all__') // 清空所有缓存
|
||||
* removeStorage('__all_sessionStorage__') // 清空 sessionStorage 缓存
|
||||
* removeStorage('__all_localStorage__') // 清空 localStorage 缓存
|
||||
* removeStorage('signing') // 清空 session 中 signing 缓存字段
|
||||
*/
|
||||
function removeStorage(
|
||||
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) {
|
||||
case 'all':
|
||||
window.window.localStorage.clear()
|
||||
window.sessionStorage.clear()
|
||||
case '__all__':
|
||||
remove(true)
|
||||
|
||||
break
|
||||
|
||||
case 'all-sessionStorage':
|
||||
window.sessionStorage.clear()
|
||||
case '__all_sessionStorage__':
|
||||
remove(false, 'sessionStorage')
|
||||
|
||||
break
|
||||
|
||||
case 'all-localStorage':
|
||||
window.localStorage.clear()
|
||||
case '__all_localStorage__':
|
||||
remove(false, 'localStorage')
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
if (!key) {
|
||||
console.error('Failed to remove stored data: key is empty or undefined')
|
||||
storageType === 'localStorage'
|
||||
? window.localStorage.removeItem(_prefix + key)
|
||||
: window.sessionStorage.removeItem(_prefix + key)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type === 'localStorage'
|
||||
? window.localStorage.removeItem(key)
|
||||
: window.sessionStorage.removeItem(key)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ import './index.scss'
|
||||
|
||||
import {
|
||||
NCard,
|
||||
NLayout,
|
||||
NDescriptions,
|
||||
NDescriptionsItem,
|
||||
NTag,
|
||||
@ -87,7 +86,7 @@ const Dashboard = defineComponent({
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<NLayout class="dashboard-layout layout-full">
|
||||
<NFlex vertical>
|
||||
<NCard>
|
||||
{{
|
||||
header: () => <RIcon name="ray" size="64" />,
|
||||
@ -126,7 +125,7 @@ const Dashboard = defineComponent({
|
||||
<NCard title="友情链接">
|
||||
<RayLink />
|
||||
</NCard>
|
||||
</NLayout>
|
||||
</NFlex>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
97
src/views/demo/cache-demo/index.tsx
Normal file
97
src/views/demo/cache-demo/index.tsx
Normal 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>
|
||||
)
|
||||
},
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user