bug fixed: 修复浏览器标题渲染错误问题,以及v4.0.2的一些小细节更新

This commit is contained in:
ray_wuhao 2023-07-02 01:00:59 +08:00
parent f21eeddd7f
commit dbb27902aa
34 changed files with 247 additions and 133 deletions

View File

@ -74,12 +74,22 @@ module.exports = {
'no-ex-assign': 2, // 禁止给 `catch` 语句中的异常参数赋值 'no-ex-assign': 2, // 禁止给 `catch` 语句中的异常参数赋值
'no-extend-native': 2, // 禁止扩展 `native` 对象 'no-extend-native': 2, // 禁止扩展 `native` 对象
'no-extra-bind': 2, // 禁止不必要的函数绑定 'no-extra-bind': 2, // 禁止不必要的函数绑定
'no-extra-boolean-cast': 2, // 禁止不必要的 `bool` 转换 'no-extra-boolean-cast': [
'error',
{
enforceForLogicalOperands: true,
},
], // 禁止不必要的 `bool` 转换
'no-extra-parens': 0, // 禁止非必要的括号 'no-extra-parens': 0, // 禁止非必要的括号
semi: ['error', 'never', { beforeStatementContinuationChars: 'always' }], semi: ['error', 'never', { beforeStatementContinuationChars: 'always' }],
'no-fallthrough': 1, // 禁止 `switch` 穿透 'no-fallthrough': 1, // 禁止 `switch` 穿透
'no-func-assign': 2, // 禁止重复的函数声明 'no-func-assign': 2, // 禁止重复的函数声明
'no-implicit-coercion': 1, // 禁止隐式转换 'no-implicit-coercion': [
'error',
{
allow: ['!!', '~'],
},
], // 禁止隐式转换
'no-implied-eval': 2, // 禁止使用隐式 `eval` 'no-implied-eval': 2, // 禁止使用隐式 `eval`
'no-invalid-regexp': 2, // 禁止无效的正则表达式 'no-invalid-regexp': 2, // 禁止无效的正则表达式
'no-invalid-this': 2, // 禁止无效的 `this` 'no-invalid-this': 2, // 禁止无效的 `this`

View File

@ -6,6 +6,13 @@
- 新增平级路由配置router meta配置项sameLevel 允许你将子路由标记为平级模式,跳转时不会出发菜单、标签页更新,仅会更新面包屑 - 新增平级路由配置router meta配置项sameLevel 允许你将子路由标记为平级模式,跳转时不会出发菜单、标签页更新,仅会更新面包屑
- 修改路由菜单显示、隐藏逻辑,现在仅会针对权限的验证匹配选择是否加入菜单列表中 - 修改路由菜单显示、隐藏逻辑,现在仅会针对权限的验证匹配选择是否加入菜单列表中
- 更新 setupAppMenu 方法触发时机Layout => menu store现在将在 pinia menu store 初始化时触发 App Menu 更新
- 更新了 utils 包中的一些方法,进行了一些重写和重命名
### Fixes
- 修复不能正确渲染浏览器标题问题
- 修复初始化模板菜单函数与菜单更新函数重复执行一些方法的问题
## 4.0.1 ## 4.0.1

View File

@ -3,7 +3,7 @@ import { RouterView } from 'vue-router'
import GlobalSpin from '@/spin/index' import GlobalSpin from '@/spin/index'
import LockScreen from '@/components/AppComponents/AppLockScreen/index' import LockScreen from '@/components/AppComponents/AppLockScreen/index'
import { getCache } from '@/utils/cache' import { getStorage } from '@/utils/cache'
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { useSetting } from '@/store' import { useSetting } from '@/store'
import { addClass, removeClass, addStyle, colorToRgba } from '@/utils/element' import { addClass, removeClass, addStyle, colorToRgba } from '@/utils/element'
@ -24,18 +24,20 @@ const App = defineComponent({
} = __APP_CFG__ // 默认主题色 } = __APP_CFG__ // 默认主题色
const body = document.body const body = document.body
const primaryColorOverride = getCache<SettingState>( const primaryColorOverride = getStorage<SettingState>(
'piniaSettingStore', 'piniaSettingStore',
'localStorage', 'localStorage',
primaryColor,
) )
const _p = get( const _p = get(
primaryColorOverride, primaryColorOverride as SettingState,
'primaryColorOverride.common.primaryColor', 'primaryColorOverride.common.primaryColor',
primaryColor,
) )
const _fp = colorToRgba(_p || primaryColor, 0.3) const _fp = colorToRgba(_p, 0.3)
/** 设置全局主题色 css 变量 */ /** 设置全局主题色 css 变量 */
body.style.setProperty('--ray-theme-primary-color', _p || primaryColor) body.style.setProperty('--ray-theme-primary-color', _p)
body.style.setProperty( body.style.setProperty(
'--ray-theme-primary-fade-color', '--ray-theme-primary-fade-color',
_fp || primaryFadeColor, _fp || primaryFadeColor,

View File

@ -21,7 +21,7 @@
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import { appendRequestHeaders } from '@/axios/helper/axiosCopilot' import { appendRequestHeaders } from '@/axios/helper/axiosCopilot'
import { APP_CATCH_KEY } from '@/appConfig/appConfig' import { APP_CATCH_KEY } from '@/appConfig/appConfig'
import { getCache } from '@/utils/cache' import { getStorage } from '@/utils/cache'
import type { import type {
RequestInterceptorConfig, RequestInterceptorConfig,
@ -40,7 +40,7 @@ const { setImplement } = useAxiosInterceptor()
* request instance , * request instance ,
*/ */
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => { const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
const token = getCache<string>(APP_CATCH_KEY.token) const token = getStorage<string>(APP_CATCH_KEY.token)
if (ins.url) { if (ins.url) {
// TODO: 根据 url 不同是否设置 token // TODO: 根据 url 不同是否设置 token

View File

@ -23,7 +23,7 @@ import { NAvatar, NSpace } from 'naive-ui'
import { avatarProps, spaceProps } from 'naive-ui' import { avatarProps, spaceProps } from 'naive-ui'
import { APP_CATCH_KEY } from '@/appConfig/appConfig' import { APP_CATCH_KEY } from '@/appConfig/appConfig'
import { getCache } from '@/utils/cache' import { getStorage } from '@/utils/cache'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { AvatarProps, SpaceProps } from 'naive-ui' import type { AvatarProps, SpaceProps } from 'naive-ui'
@ -48,7 +48,7 @@ const AppAvatar = defineComponent({
}, },
}, },
setup(props) { setup(props) {
const signin = getCache<SigninCallback>(APP_CATCH_KEY.signin) const signin = getStorage<SigninCallback>(APP_CATCH_KEY.signin)
const cssVars = computed(() => { const cssVars = computed(() => {
const vars = { const vars = {
'--app-avatar-cursor': props.cursor, '--app-avatar-cursor': props.cursor,

View File

@ -9,6 +9,7 @@ import { useVueRouter } from '@/router/helper/useVueRouter'
import type { MenuInst } from 'naive-ui' import type { MenuInst } from 'naive-ui'
import type { NaiveMenuOptions } from '@/types/modules/component' import type { NaiveMenuOptions } from '@/types/modules/component'
import type { AppMenuOption } from '@/types/modules/app'
const LayoutMenu = defineComponent({ const LayoutMenu = defineComponent({
name: 'LayoutMenu', name: 'LayoutMenu',
@ -106,7 +107,9 @@ const LayoutMenu = defineComponent({
collapsed={this.modelCollapsed} collapsed={this.modelCollapsed}
collapsedIconSize={APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE} collapsedIconSize={APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE}
collapsedWidth={APP_MENU_CONFIG.MENU_COLLAPSED_WIDTH} collapsedWidth={APP_MENU_CONFIG.MENU_COLLAPSED_WIDTH}
onUpdateValue={this.changeMenuModelValue.bind(this)} onUpdateValue={(key, op) => {
this.changeMenuModelValue(key, op as unknown as AppMenuOption)
}}
accordion={APP_MENU_CONFIG.MENU_ACCORDION} accordion={APP_MENU_CONFIG.MENU_ACCORDION}
/> />
</NLayoutSider> </NLayoutSider>

View File

@ -33,10 +33,10 @@ import { uuid } from '@/utils/hook'
import { hasClass } from '@/utils/element' import { hasClass } from '@/utils/element'
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot' import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
import { ROOT_ROUTE } from '@/appConfig/appConfig' import { ROOT_ROUTE } from '@/appConfig/appConfig'
import { getElement } from '@use-utils/element' import { getElements } from '@use-utils/element'
import type { MenuOption, ScrollbarInst } from 'naive-ui' import type { MenuOption, ScrollbarInst } from 'naive-ui'
import type { MenuTagOptions } from '@/types/modules/app' import type { MenuTagOptions, AppMenuOption } from '@/types/modules/app'
const MenuTag = defineComponent({ const MenuTag = defineComponent({
name: 'MenuTag', name: 'MenuTag',
@ -247,7 +247,7 @@ const MenuTag = defineComponent({
* *
* @param item * @param item
*/ */
const handleTagClick = (item: MenuOption) => { const handleTagClick = (item: AppMenuOption) => {
changeMenuModelValue(item.key as string, item) changeMenuModelValue(item.key as string, item)
} }
@ -381,7 +381,7 @@ const MenuTag = defineComponent({
/** 动态更新 menu tag 所在位置 */ /** 动态更新 menu tag 所在位置 */
const positionMenuTag = () => { const positionMenuTag = () => {
nextTick().then(() => { nextTick().then(() => {
const tags = getElement<HTMLElement>( const tags = getElements<HTMLElement>(
`attr:${MENU_TAG_DATA}="${menuKey.value}"`, `attr:${MENU_TAG_DATA}="${menuKey.value}"`,
) )

View File

@ -42,12 +42,16 @@ const Breadcrumb = defineComponent({
key: string | number, key: string | number,
option: DropdownOption, option: DropdownOption,
) => { ) => {
changeMenuModelValue(key, option) changeMenuModelValue(key, option as unknown as AppMenuOption)
} }
const handleBreadcrumbItemClick = (option: AppMenuOption) => { const handleBreadcrumbItemClick = (option: AppMenuOption) => {
if (!option.children?.length) { if (!option.children?.length) {
changeMenuModelValue(option.key, option as unknown as MenuOption) const { meta = {} } = option
if (!meta.sameLevel) {
changeMenuModelValue(option.key, option)
}
} }
} }

View File

@ -109,7 +109,7 @@ const GlobalSeach = defineComponent({
} }
} }
const handleSearchItemClick = (option: MenuOption) => { const handleSearchItemClick = (option: AppMenuOption) => {
const meta = option.meta as AppRouteMeta const meta = option.meta as AppRouteMeta
/** 如果配置站外跳转则不会关闭搜索框 */ /** 如果配置站外跳转则不会关闭搜索框 */
@ -118,7 +118,7 @@ const GlobalSeach = defineComponent({
} else { } else {
modelShow.value = false modelShow.value = false
changeMenuModelValue(option.key as string, option) changeMenuModelValue(option.key, option)
} }
} }

View File

@ -18,7 +18,7 @@
import { set } from 'lodash-es' import { set } from 'lodash-es'
import { zhCN, dateZhCN } from 'naive-ui' // 导入 `naive ui` 中文包 import { zhCN, dateZhCN } from 'naive-ui' // 导入 `naive ui` 中文包
import { getCache } from '@use-utils/cache' import { getStorage } from '@use-utils/cache'
import { SYSTEM_DEFAULT_LOCAL } from '@/appConfig/localConfig' import { SYSTEM_DEFAULT_LOCAL } from '@/appConfig/localConfig'
import { APP_CATCH_KEY } from '@/appConfig/appConfig' import { APP_CATCH_KEY } from '@/appConfig/appConfig'
@ -126,10 +126,11 @@ export const naiveLocales = (key: string) => {
* @remak , `main.ts` , `i18n` * @remak , `main.ts` , `i18n`
*/ */
export const getAppDefaultLanguage = () => { export const getAppDefaultLanguage = () => {
const language = getCache<string>( const language = getStorage<string>(
APP_CATCH_KEY.localeLanguage, APP_CATCH_KEY.localeLanguage,
'localStorage', 'localStorage',
SYSTEM_DEFAULT_LOCAL,
) )
return language ? language : SYSTEM_DEFAULT_LOCAL return language || SYSTEM_DEFAULT_LOCAL
} }

View File

@ -115,5 +115,5 @@ hidden: 是否显示
noLocalTitle: 不使用国际化渲染 Menu Titile noLocalTitle: 不使用国际化渲染 Menu Titile
ignoreAutoResetScroll: 该页面内容区域自动初始化滚动条位置 ignoreAutoResetScroll: 该页面内容区域自动初始化滚动条位置
keepAlive: 是否缓存该页面(需要配置 APP_KEEP_ALIVE setupKeepAlive 属性为 true 启用才有效) keepAlive: 是否缓存该页面(需要配置 APP_KEEP_ALIVE setupKeepAlive 属性为 true 启用才有效)
sameLevel: 是否标记该路由为平级模式 sameLevel: 是否标记该路由为平级模式,如果标记为平级模式,会使路由菜单项隐藏。如果在含有子节点处,设置了该属性,会导致子节点全部被隐藏
``` ```

View File

@ -20,11 +20,10 @@
* , , * , ,
*/ */
import { getCache, setCache } from '@/utils/cache' import { getStorage } from '@/utils/cache'
import { useSignin } from '@/store'
import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig' import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig'
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot' import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot' import { validRole } from '@/router/helper/routerCopilot'
import type { import type {
Router, Router,
@ -38,8 +37,12 @@ export const permissionRouter = (router: Router) => {
const { beforeEach } = router const { beforeEach } = router
beforeEach((to, from, next) => { beforeEach((to, from, next) => {
const token = getCache<string>(APP_CATCH_KEY.token) const token = getStorage<string>(APP_CATCH_KEY.token)
const route = getCache<string>('menuKey') || ROOT_ROUTE.path const route = getStorage<string>(
'menuKey',
'sessionStorage',
ROOT_ROUTE.path,
) as string
const { meta } = to const { meta } = to
if (token !== null) { if (token !== null) {

View File

@ -20,7 +20,7 @@ import {
import { useSignin } from '@/store' import { useSignin } from '@/store'
import { useVueRouter } from '@/router/helper/useVueRouter' import { useVueRouter } from '@/router/helper/useVueRouter'
import { ROOT_ROUTE } from '@/appConfig/appConfig' import { ROOT_ROUTE } from '@/appConfig/appConfig'
import { setCache } from '@/utils/cache' import { setStorage } from '@/utils/cache'
import type { Router } from 'vue-router' import type { Router } from 'vue-router'
import type { AppRouteMeta } from '@/router/type' import type { AppRouteMeta } from '@/router/type'
@ -58,18 +58,27 @@ export const validRole = (meta: AppRouteMeta) => {
/** /**
* *
* @remark * @remark
* *
* , hidden role * , hidden sameLevel
* , * sameLevel
* *
* , 使 validRole * , 使 validRole
*/ */
export const validMenuItemShow = (option: AppMenuOption) => { export const validMenuItemShow = (option: AppMenuOption) => {
const { meta = {} } = option const { meta = {} } = option
const { hidden } = meta const { hidden, sameLevel } = meta
return hidden === undefined || hidden === false ? true : false // 如果该路由被标记为平级模式, 则会强制不显示在菜单中
if (sameLevel) {
return false
}
if (!sameLevel && !hidden) {
return true
}
return !hidden ? true : false
} }
/** /**
@ -119,7 +128,7 @@ export const redirectRouterToDashboard = (isReplace = true) => {
const { push, replace } = router const { push, replace } = router
const { path } = ROOT_ROUTE const { path } = ROOT_ROUTE
setCache('menuKey', path) setStorage('menuKey', path)
isReplace ? push(path) : replace(path) isReplace ? push(path) : replace(path)
} }

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,9 +1,8 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'
import { LAYOUT } from '@/router/constant/index'
const multiMenu: AppRouteRecordRaw = { const multiMenu: AppRouteRecordRaw = {
path: '/multi', path: '/multi',
name: 'MultiMenu', name: 'MultiMenu',

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,9 +1,8 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'
import { LAYOUT } from '@/router/constant/index'
const rely: AppRouteRecordRaw = { const rely: AppRouteRecordRaw = {
path: '/rely', path: '/rely',
name: 'RelyAbout', name: 'RelyAbout',

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -1,4 +1,5 @@
import { t } from '@/locales/useI18n' import { t } from '@/locales/useI18n'
import { LAYOUT } from '@/router/constant/index'
import type { AppRouteRecordRaw } from '@/router/type' import type { AppRouteRecordRaw } from '@/router/type'

View File

@ -14,7 +14,7 @@
import { APP_MENU_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig' import { APP_MENU_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig'
import RayIcon from '@/components/RayIcon/index' import RayIcon from '@/components/RayIcon/index'
import { isValueType } from '@/utils/hook' import { isValueType } from '@/utils/hook'
import { getCache, setCache } from '@/utils/cache' import { getStorage, setStorage } from '@/utils/cache'
import type { VNode } from 'vue' import type { VNode } from 'vue'
import type { import type {
@ -174,10 +174,11 @@ export const hasMenuIcon = (option: AppMenuOption) => {
/** 获取缓存的 menu key, 如果未获取到则使用 ROOTROUTE path 当作默认激活路由菜单 */ /** 获取缓存的 menu key, 如果未获取到则使用 ROOTROUTE path 当作默认激活路由菜单 */
export const getCatchMenuKey = () => { export const getCatchMenuKey = () => {
const { path: rootPath } = ROOT_ROUTE const { path: rootPath } = ROOT_ROUTE
const cacheMenuKey = const cacheMenuKey = getStorage<AppMenuKey>(
getCache<AppMenuKey>('menuKey') === null 'menuKey',
? rootPath 'sessionStorage',
: getCache<AppMenuKey>('menuKey') rootPath,
)
return cacheMenuKey return cacheMenuKey
} }

View File

@ -24,8 +24,8 @@
import { NEllipsis } from 'naive-ui' import { NEllipsis } from 'naive-ui'
import { getCache, setCache } from '@/utils/cache' import { setStorage } from '@/utils/cache'
import { validMenuItemShow, validRole } from '@/router/helper/routerCopilot' import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot'
import { import {
parseAndFindMatchingNodes, parseAndFindMatchingNodes,
updateDocumentTitle, updateDocumentTitle,
@ -86,7 +86,10 @@ export const useMenu = defineStore(
* *
* , * ,
*/ */
const setBreadcrumbOptions = (key: string | number, option: MenuOption) => { const setBreadcrumbOptions = (
key: string | number,
option: AppMenuOption,
) => {
const { meta } = option as unknown as AppRouteRecordRaw const { meta } = option as unknown as AppRouteRecordRaw
menuState.breadcrumbOptions = getCompleteRoutePath(menuState.options, key) menuState.breadcrumbOptions = getCompleteRoutePath(menuState.options, key)
@ -124,12 +127,12 @@ export const useMenu = defineStore(
/** 当 url 地址发生变化触发 menuTagOptions 更新 */ /** 当 url 地址发生变化触发 menuTagOptions 更新 */
const setMenuTagOptionsWhenMenuValueChange = ( const setMenuTagOptionsWhenMenuValueChange = (
key: string | number, key: string | number,
option: MenuOption, option: AppMenuOption,
) => { ) => {
const tag = menuState.menuTagOptions.find((curr) => curr.path === key) const tag = menuState.menuTagOptions.find((curr) => curr.path === key)
if (!tag) { if (!tag) {
menuState.menuTagOptions.push(option as unknown as MenuTagOptions) menuState.menuTagOptions.push(option as MenuTagOptions)
} }
} }
@ -141,8 +144,11 @@ export const useMenu = defineStore(
* @remark `menu key` * @remark `menu key`
* @remark , key (router push ) * @remark , key (router push )
*/ */
const changeMenuModelValue = (key: string | number, option: MenuOption) => { const changeMenuModelValue = (
const { meta, path } = option as unknown as AppRouteRecordRaw key: string | number,
option: AppMenuOption,
) => {
const { meta, path } = option
if (meta.windowOpen) { if (meta.windowOpen) {
window.open(meta.windowOpen) window.open(meta.windowOpen)
@ -169,10 +175,10 @@ export const useMenu = defineStore(
/** 检查是否为根路由 */ /** 检查是否为根路由 */
const count = (path.match(new RegExp('/', 'g')) || []).length const count = (path.match(new RegExp('/', 'g')) || []).length
/** 更新浏览器标题 */
updateDocumentTitle(option as unknown as AppMenuOption)
/** 更新缓存队列 */ /** 更新缓存队列 */
setKeepAliveInclude(option as unknown as AppMenuOption) setKeepAliveInclude(option as unknown as AppMenuOption)
/** 更新浏览器标题 */
updateDocumentTitle(option as unknown as AppMenuOption)
if (!meta.sameLevel || (meta.sameLevel && count === 1)) { if (!meta.sameLevel || (meta.sameLevel && count === 1)) {
/** 更新标签菜单 */ /** 更新标签菜单 */
@ -182,7 +188,7 @@ export const useMenu = defineStore(
menuState.menuKey = key menuState.menuKey = key
/** 缓存菜单 key(sessionStorage) */ /** 缓存菜单 key(sessionStorage) */
setCache('menuKey', key) setStorage('menuKey', key)
} else { } else {
setBreadcrumbOptions(menuState.menuKey || '', option) setBreadcrumbOptions(menuState.menuKey || '', option)
} }
@ -196,21 +202,33 @@ export const useMenu = defineStore(
* @remark * @remark
* @remark * @remark
*/ */
const updateMenuKeyWhenRouteUpdate = (path: string) => { const updateMenuKeyWhenRouteUpdate = async (path: string) => {
const appRawRoutes = expandRoutes(getAppRawRoutes()) // 获取 `/` 出现次数(如果为 1 则表示该路径为根路由路径)
const count = (path.match(new RegExp('/', 'g')) || []).length const count = (path.match(new RegExp('/', 'g')) || []).length
const fd = appRawRoutes.find((curr) => curr.path === path)
let combinePath = path let combinePath = path
if (count > 1) { if (count > 1) {
// 如果不是跟路径则取出最后一项字符
const splitPath = path.split('/').filter((curr) => curr) const splitPath = path.split('/').filter((curr) => curr)
combinePath = splitPath[splitPath.length - 1] combinePath = splitPath[splitPath.length - 1]
} }
if (fd) { const findMenuOption = (pathKey: string, options: AppMenuOption[]) => {
changeMenuModelValue(combinePath, fd as unknown as MenuOption) for (const curr of options) {
if (curr.children?.length) {
findMenuOption(pathKey, curr.children)
}
if (pathKey === curr.key) {
changeMenuModelValue(pathKey, curr)
break
}
}
} }
findMenuOption(combinePath, menuState.options)
} }
/** /**
@ -237,8 +255,6 @@ export const useMenu = defineStore(
}), }),
breadcrumbLabel: label.value, breadcrumbLabel: label.value,
/** 检查该菜单项是否展示 */ /** 检查该菜单项是否展示 */
show:
meta.hidden === false || meta.hidden === undefined ? true : false,
} as AppMenuOption } as AppMenuOption
/** 合并 icon */ /** 合并 icon */
const attr: AppMenuOption = Object.assign({}, route, { const attr: AppMenuOption = Object.assign({}, route, {
@ -246,14 +262,12 @@ export const useMenu = defineStore(
}) })
if (option.path === getCatchMenuKey()) { if (option.path === getCatchMenuKey()) {
/** 设置浏览器标题 */ /** 设置标签页(初始化时执行设置一次, 避免含有平级路由模式情况时出现不能正确设置标签页的情况) */
updateDocumentTitle(attr) setMenuTagOptionsWhenMenuValueChange(option.path, attr)
setMenuTagOptionsWhenMenuValueChange(
option.path,
attr as unknown as MenuOption,
)
} }
attr.show = validMenuItemShow(attr)
return attr return attr
} }
@ -320,9 +334,9 @@ export const useMenu = defineStore(
const setupPiniaMenuStore = async () => { const setupPiniaMenuStore = async () => {
if (isSetupAppMenuLock.value) { if (isSetupAppMenuLock.value) {
await setupAppMenu() await setupAppMenu()
isSetupAppMenuLock.value = false
} }
isSetupAppMenuLock.value = false
} }
/** 监听路由变化并且更新路由菜单与菜单标签 */ /** 监听路由变化并且更新路由菜单与菜单标签 */
@ -333,7 +347,7 @@ export const useMenu = defineStore(
const match = newData.match(reg)?.[1] const match = newData.match(reg)?.[1]
await setupPiniaMenuStore() await setupPiniaMenuStore()
updateMenuKeyWhenRouteUpdate(match || '') await updateMenuKeyWhenRouteUpdate(match || '')
}, },
{ {
immediate: true, immediate: true,

View File

@ -1,5 +1,5 @@
import { getAppDefaultLanguage } from '@/locales/helper' import { getAppDefaultLanguage } from '@/locales/helper'
import { setCache } from '@use-utils/cache' import { setStorage } from '@use-utils/cache'
import { set } from 'lodash-es' import { set } from 'lodash-es'
import { addClass, removeClass, colorToRgba } from '@/utils/element' import { addClass, removeClass, colorToRgba } from '@/utils/element'
import { useI18n } from '@/locales/useI18n' import { useI18n } from '@/locales/useI18n'
@ -46,7 +46,7 @@ export const useSetting = defineStore(
settingState.localeLanguage = key settingState.localeLanguage = key
setCache('localeLanguage', key, 'localStorage') setStorage('localeLanguage', key, 'localStorage')
} }
/** 切换主题色 */ /** 切换主题色 */

View File

@ -20,7 +20,7 @@
*/ */
import { isEmpty } from 'lodash-es' import { isEmpty } from 'lodash-es'
import { removeCache } from '@/utils/cache' import { removeStorage } from '@/utils/cache'
import type { import type {
SigninForm, SigninForm,
@ -79,7 +79,7 @@ export const useSignin = defineStore(
*/ */
const logout = () => { const logout = () => {
window.$message.info('账号退出中...') window.$message.info('账号退出中...')
removeCache('all-sessionStorage') removeStorage('all-sessionStorage')
setTimeout(() => window.location.reload()) setTimeout(() => window.location.reload())
} }

View File

@ -42,3 +42,9 @@ export type CipherParams = CryptoJS.lib.CipherParams
export type AnyFunc = (...args: any[]) => any export type AnyFunc = (...args: any[]) => any
export type AnyVoidFunc = (...args: any[]) => void export type AnyVoidFunc = (...args: any[]) => void
export type PartialCSSStyleDeclaration = Partial<
Record<keyof CSSStyleDeclaration, string>
>
export type ElementSelector = string | `attr:${string}`

View File

@ -18,16 +18,26 @@ import type { CacheType } from '@/types/modules/utils'
* @param key key * @param key key
* @param value * @param value
*/ */
export const setCache = <T = unknown>( export const setStorage = <T = unknown>(
key: string, key: string,
value: T, value: T,
type: CacheType = 'sessionStorage', type: CacheType = 'sessionStorage',
) => { ) => {
const waitCacheValue = JSON.stringify(value) if (!key) {
console.error('Failed to set stored data: key is empty or undefined')
type === 'localStorage' return
? window.localStorage.setItem(key, waitCacheValue) }
: window.sessionStorage.setItem(key, waitCacheValue)
try {
const waitCacheValue = JSON.stringify(value)
type === 'localStorage'
? window.localStorage.setItem(key, waitCacheValue)
: window.sessionStorage.setItem(key, waitCacheValue)
} catch (error) {
console.error(`Failed to set stored data for key '${key}'`, error)
}
} }
/** /**
@ -35,16 +45,27 @@ export const setCache = <T = unknown>(
* @param key key * @param key key
* @returns * @returns
*/ */
export const getCache = <T>( export const getStorage = <T>(
key: string, key: string,
type: CacheType = 'sessionStorage', storageType: CacheType = 'sessionStorage',
defaultValue?: T,
): T | null => { ): T | null => {
const data = try {
type === 'localStorage' const data =
? window.localStorage.getItem(key) storageType === 'localStorage'
: window.sessionStorage.getItem(key) ? window.localStorage.getItem(key)
: window.sessionStorage.getItem(key)
return Object.is(data, null) ? null : JSON.parse(data as string) if (data === null) {
return defaultValue ?? null
}
return JSON.parse(data) as T
} catch (error) {
console.error(`Failed to get stored data for key '${key}'`, error)
return defaultValue ?? null
}
} }
/** /**
@ -56,7 +77,7 @@ export const getCache = <T>(
* - all-sessionStorage: 删除所有 sessionStorage * - all-sessionStorage: 删除所有 sessionStorage
* - all-localStorage: 删除所有 localStorage * - all-localStorage: 删除所有 localStorage
*/ */
export const removeCache = ( export const removeStorage = (
key: string | 'all' | 'all-sessionStorage' | 'all-localStorage', key: string | 'all' | 'all-sessionStorage' | 'all-localStorage',
type: CacheType = 'sessionStorage', type: CacheType = 'sessionStorage',
) => { ) => {
@ -78,6 +99,12 @@ export const removeCache = (
break break
default: default:
if (!key) {
console.error('Failed to remove stored data: key is empty or undefined')
return
}
type === 'localStorage' type === 'localStorage'
? window.localStorage.removeItem(key) ? window.localStorage.removeItem(key)
: window.sessionStorage.removeItem(key) : window.sessionStorage.removeItem(key)

View File

@ -1,7 +1,11 @@
import { isValueType } from '@use-utils/hook' import { isValueType } from '@use-utils/hook'
import { APP_REGEX } from '@/appConfig/regConfig' import { APP_REGEX } from '@/appConfig/regConfig'
import type { EventListenerOrEventListenerObject } from '@/types/modules/utils' import type {
EventListenerOrEventListenerObject,
PartialCSSStyleDeclaration,
ElementSelector,
} from '@/types/modules/utils'
/** /**
* *
@ -116,6 +120,8 @@ export const hasClass = (element: HTMLElement, className: string) => {
* @param el Target element dom * @param el Target element dom
* @param styles (, ) * @param styles (, )
* *
*
* @example
* style of string * style of string
* ``` * ```
* const styles = 'width: 100px; height: 100px; background: red;' * const styles = 'width: 100px; height: 100px; background: red;'
@ -132,27 +138,37 @@ export const hasClass = (element: HTMLElement, className: string) => {
* addStyle(styles) * addStyle(styles)
* ``` * ```
*/ */
export const addStyle = <K extends keyof CSSStyleDeclaration & string>( export const addStyle = (
el: HTMLElement, el: HTMLElement,
styles: K | Partial<CSSStyleDeclaration>, styles: PartialCSSStyleDeclaration | string,
) => { ) => {
if (el) { if (!el) {
if (isValueType<object>(styles, 'Object')) { return
Object.keys(styles).forEach((item) => {
el.style[item] = styles[item]
})
} else if (isValueType<string>(styles, 'String')) {
const _styles = styles
_styles.split(';').forEach((item) => {
const [_k, _v] = item.split(':')
if (_k && _v) {
el.style[_k.trim()] = _v.trim()
}
})
}
} }
let styleObj: PartialCSSStyleDeclaration
if (isValueType<string>(styles, 'String')) {
styleObj = styles.split(';').reduce((pre, curr) => {
const [key, value] = curr.split(':').map((s) => s.trim())
if (key && value) {
pre[key] = value
}
return pre
}, {} as PartialCSSStyleDeclaration)
} else {
styleObj = styles
}
Object.keys(styleObj).forEach((key) => {
const value = styleObj[key]
if (key in el.style) {
el.style[key] = value
}
})
} }
/** /**
@ -160,15 +176,17 @@ export const addStyle = <K extends keyof CSSStyleDeclaration & string>(
* @param el Target element dom * @param el Target element dom
* @param styles * @param styles
*/ */
export const removeStyle = <K extends keyof CSSStyleDeclaration & string>( export const removeStyle = (
el: HTMLElement, el: HTMLElement,
styles: K[], styles: (keyof CSSStyleDeclaration & string)[],
) => { ) => {
if (el) { if (!el) {
styles.forEach((curr) => { return
el.style.removeProperty(curr)
})
} }
styles.forEach((curr) => {
el.style.removeProperty(curr)
})
} }
/** /**
@ -222,33 +240,33 @@ export const colorToRgba = (color: string, alpha = 1) => {
* : * :
* *
* class: * class:
* const el = getElement('.demo') * const el = getElements('.demo')
* id: * id:
* const el = getElement('#demo') * const el = getElements('#demo')
* attribute: * attribute:
* const el = getElement('attr:type=button') * const el = getElements('attr:type=button')
* *
* const el = getElement('attr:type') * const el = getElements('attr:type')
*/ */
export const getElement = <T extends Element>(element: string) => { export const getElements = <T extends Element = Element>(
if (!element) { selector: ElementSelector,
) => {
if (!selector) {
return null return null
} }
let queryParam: string const queryParam = selector.startsWith('attr:')
? `[${selector.replace('attr:', '')}]`
if (element.startsWith('attr:')) { : selector
queryParam = '[' + element.replace('attr:', '') + ']'
} else {
queryParam = element
}
try { try {
const el = Array.from(document.querySelectorAll<T>(queryParam)) const elements = Array.from(document.querySelectorAll<T>(queryParam))
return el return elements
} catch (e) { } catch (error) {
return [] console.error(`Failed to get elements for selector '${selector}'`, error)
return null
} }
} }

View File

@ -1,6 +1,6 @@
import { NForm, NFormItem, NInput, NButton, NSpace, NDivider } from 'naive-ui' import { NForm, NFormItem, NInput, NButton, NSpace, NDivider } from 'naive-ui'
import { setCache } from '@/utils/cache' import { setStorage } from '@/utils/cache'
import { setSpin } from '@/spin' import { setSpin } from '@/spin'
import { useSignin } from '@/store' import { useSignin } from '@/store'
import { useI18n } from '@/locales/useI18n' import { useI18n } from '@/locales/useI18n'
@ -55,8 +55,8 @@ const Signin = defineComponent({
window.$message.success(`欢迎${signinForm.value.name}登陆~`) window.$message.success(`欢迎${signinForm.value.name}登陆~`)
setCache(APP_CATCH_KEY.token, 'tokenValue') setStorage(APP_CATCH_KEY.token, 'tokenValue')
setCache(APP_CATCH_KEY.signin, res.data) setStorage(APP_CATCH_KEY.signin, res.data)
router.push(path) router.push(path)
}, 2 * 1000) }, 2 * 1000)