diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 64415c2f..1e170e43 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -74,12 +74,22 @@ module.exports = { 'no-ex-assign': 2, // 禁止给 `catch` 语句中的异常参数赋值 'no-extend-native': 2, // 禁止扩展 `native` 对象 'no-extra-bind': 2, // 禁止不必要的函数绑定 - 'no-extra-boolean-cast': 2, // 禁止不必要的 `bool` 转换 + 'no-extra-boolean-cast': [ + 'error', + { + enforceForLogicalOperands: true, + }, + ], // 禁止不必要的 `bool` 转换 'no-extra-parens': 0, // 禁止非必要的括号 semi: ['error', 'never', { beforeStatementContinuationChars: 'always' }], 'no-fallthrough': 1, // 禁止 `switch` 穿透 'no-func-assign': 2, // 禁止重复的函数声明 - 'no-implicit-coercion': 1, // 禁止隐式转换 + 'no-implicit-coercion': [ + 'error', + { + allow: ['!!', '~'], + }, + ], // 禁止隐式转换 'no-implied-eval': 2, // 禁止使用隐式 `eval` 'no-invalid-regexp': 2, // 禁止无效的正则表达式 'no-invalid-this': 2, // 禁止无效的 `this` diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2d8541..6305b8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ - 新增平级路由配置(router meta)配置项,sameLevel 允许你将子路由标记为平级模式,跳转时不会出发菜单、标签页更新,仅会更新面包屑 - 修改路由菜单显示、隐藏逻辑,现在仅会针对权限的验证匹配选择是否加入菜单列表中 +- 更新 setupAppMenu 方法触发时机(Layout => menu store),现在将在 pinia menu store 初始化时触发 App Menu 更新 +- 更新了 utils 包中的一些方法,进行了一些重写和重命名 + +### Fixes + +- 修复不能正确渲染浏览器标题问题 +- 修复初始化模板菜单函数与菜单更新函数重复执行一些方法的问题 ## 4.0.1 diff --git a/src/App.tsx b/src/App.tsx index 174b7db4..a0dd9d3e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import { RouterView } from 'vue-router' import GlobalSpin from '@/spin/index' import LockScreen from '@/components/AppComponents/AppLockScreen/index' -import { getCache } from '@/utils/cache' +import { getStorage } from '@/utils/cache' import { get } from 'lodash-es' import { useSetting } from '@/store' import { addClass, removeClass, addStyle, colorToRgba } from '@/utils/element' @@ -24,18 +24,20 @@ const App = defineComponent({ } = __APP_CFG__ // 默认主题色 const body = document.body - const primaryColorOverride = getCache( + const primaryColorOverride = getStorage( 'piniaSettingStore', 'localStorage', + primaryColor, ) const _p = get( - primaryColorOverride, + primaryColorOverride as SettingState, 'primaryColorOverride.common.primaryColor', + primaryColor, ) - const _fp = colorToRgba(_p || primaryColor, 0.3) + const _fp = colorToRgba(_p, 0.3) /** 设置全局主题色 css 变量 */ - body.style.setProperty('--ray-theme-primary-color', _p || primaryColor) + body.style.setProperty('--ray-theme-primary-color', _p) body.style.setProperty( '--ray-theme-primary-fade-color', _fp || primaryFadeColor, diff --git a/src/axios/inject/requestInject.ts b/src/axios/inject/requestInject.ts index 0cdb700e..f2becd29 100644 --- a/src/axios/inject/requestInject.ts +++ b/src/axios/inject/requestInject.ts @@ -21,7 +21,7 @@ import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' import { appendRequestHeaders } from '@/axios/helper/axiosCopilot' import { APP_CATCH_KEY } from '@/appConfig/appConfig' -import { getCache } from '@/utils/cache' +import { getStorage } from '@/utils/cache' import type { RequestInterceptorConfig, @@ -40,7 +40,7 @@ const { setImplement } = useAxiosInterceptor() * 当然你也可以根据 request instance 来特殊处理, 这里暂时不做演示 */ const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => { - const token = getCache(APP_CATCH_KEY.token) + const token = getStorage(APP_CATCH_KEY.token) if (ins.url) { // TODO: 根据 url 不同是否设置 token diff --git a/src/components/AppComponents/AppAvatar/index.tsx b/src/components/AppComponents/AppAvatar/index.tsx index dd5c5625..777597a4 100644 --- a/src/components/AppComponents/AppAvatar/index.tsx +++ b/src/components/AppComponents/AppAvatar/index.tsx @@ -23,7 +23,7 @@ import { NAvatar, NSpace } from 'naive-ui' import { avatarProps, spaceProps } from 'naive-ui' import { APP_CATCH_KEY } from '@/appConfig/appConfig' -import { getCache } from '@/utils/cache' +import { getStorage } from '@/utils/cache' import type { PropType } from 'vue' import type { AvatarProps, SpaceProps } from 'naive-ui' @@ -48,7 +48,7 @@ const AppAvatar = defineComponent({ }, }, setup(props) { - const signin = getCache(APP_CATCH_KEY.signin) + const signin = getStorage(APP_CATCH_KEY.signin) const cssVars = computed(() => { const vars = { '--app-avatar-cursor': props.cursor, diff --git a/src/layout/components/Menu/index.tsx b/src/layout/components/Menu/index.tsx index 3e46b446..027de23f 100644 --- a/src/layout/components/Menu/index.tsx +++ b/src/layout/components/Menu/index.tsx @@ -9,6 +9,7 @@ import { useVueRouter } from '@/router/helper/useVueRouter' import type { MenuInst } from 'naive-ui' import type { NaiveMenuOptions } from '@/types/modules/component' +import type { AppMenuOption } from '@/types/modules/app' const LayoutMenu = defineComponent({ name: 'LayoutMenu', @@ -106,7 +107,9 @@ const LayoutMenu = defineComponent({ collapsed={this.modelCollapsed} collapsedIconSize={APP_MENU_CONFIG.MENU_COLLAPSED_ICON_SIZE} 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} /> diff --git a/src/layout/components/MenuTag/index.tsx b/src/layout/components/MenuTag/index.tsx index 91971841..3f7fff0c 100644 --- a/src/layout/components/MenuTag/index.tsx +++ b/src/layout/components/MenuTag/index.tsx @@ -33,10 +33,10 @@ import { uuid } from '@/utils/hook' import { hasClass } from '@/utils/element' import { redirectRouterToDashboard } from '@/router/helper/routerCopilot' 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 { MenuTagOptions } from '@/types/modules/app' +import type { MenuTagOptions, AppMenuOption } from '@/types/modules/app' const MenuTag = defineComponent({ name: 'MenuTag', @@ -247,7 +247,7 @@ const MenuTag = defineComponent({ * * @param item 当前菜单值 */ - const handleTagClick = (item: MenuOption) => { + const handleTagClick = (item: AppMenuOption) => { changeMenuModelValue(item.key as string, item) } @@ -381,7 +381,7 @@ const MenuTag = defineComponent({ /** 动态更新 menu tag 所在位置 */ const positionMenuTag = () => { nextTick().then(() => { - const tags = getElement( + const tags = getElements( `attr:${MENU_TAG_DATA}="${menuKey.value}"`, ) diff --git a/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx b/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx index e9eedf4c..00ebbe30 100644 --- a/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx +++ b/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx @@ -42,12 +42,16 @@ const Breadcrumb = defineComponent({ key: string | number, option: DropdownOption, ) => { - changeMenuModelValue(key, option) + changeMenuModelValue(key, option as unknown as AppMenuOption) } const handleBreadcrumbItemClick = (option: AppMenuOption) => { if (!option.children?.length) { - changeMenuModelValue(option.key, option as unknown as MenuOption) + const { meta = {} } = option + + if (!meta.sameLevel) { + changeMenuModelValue(option.key, option) + } } } diff --git a/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx b/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx index 09b317d5..284ddf5a 100644 --- a/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx +++ b/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx @@ -109,7 +109,7 @@ const GlobalSeach = defineComponent({ } } - const handleSearchItemClick = (option: MenuOption) => { + const handleSearchItemClick = (option: AppMenuOption) => { const meta = option.meta as AppRouteMeta /** 如果配置站外跳转则不会关闭搜索框 */ @@ -118,7 +118,7 @@ const GlobalSeach = defineComponent({ } else { modelShow.value = false - changeMenuModelValue(option.key as string, option) + changeMenuModelValue(option.key, option) } } diff --git a/src/locales/helper.ts b/src/locales/helper.ts index 421a87be..dcf44d8f 100644 --- a/src/locales/helper.ts +++ b/src/locales/helper.ts @@ -18,7 +18,7 @@ import { set } from 'lodash-es' 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 { APP_CATCH_KEY } from '@/appConfig/appConfig' @@ -126,10 +126,11 @@ export const naiveLocales = (key: string) => { * @remak 未避免出现加载语言错误问题, 故而在 `main.ts` 注册时, 应优先加载 `i18n` 避免出现该问题 */ export const getAppDefaultLanguage = () => { - const language = getCache( + const language = getStorage( APP_CATCH_KEY.localeLanguage, 'localStorage', + SYSTEM_DEFAULT_LOCAL, ) - return language ? language : SYSTEM_DEFAULT_LOCAL + return language || SYSTEM_DEFAULT_LOCAL } diff --git a/src/router/README.md b/src/router/README.md index b1c66ad7..90cf3399 100644 --- a/src/router/README.md +++ b/src/router/README.md @@ -115,5 +115,5 @@ hidden: 是否显示 noLocalTitle: 不使用国际化渲染 Menu Titile ignoreAutoResetScroll: 该页面内容区域自动初始化滚动条位置 keepAlive: 是否缓存该页面(需要配置 APP_KEEP_ALIVE setupKeepAlive 属性为 true 启用才有效) -sameLevel: 是否标记该路由为平级模式 +sameLevel: 是否标记该路由为平级模式,如果标记为平级模式,会使路由菜单项隐藏。如果在含有子节点处,设置了该属性,会导致子节点全部被隐藏 ``` diff --git a/src/router/helper/permission.ts b/src/router/helper/permission.ts index a002ae46..733998c2 100644 --- a/src/router/helper/permission.ts +++ b/src/router/helper/permission.ts @@ -20,11 +20,10 @@ * 当然, 你可以指定一个超级管理员角色, 默认获取全部路由 */ -import { getCache, setCache } from '@/utils/cache' -import { useSignin } from '@/store' +import { getStorage } from '@/utils/cache' import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig' import { redirectRouterToDashboard } from '@/router/helper/routerCopilot' -import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot' +import { validRole } from '@/router/helper/routerCopilot' import type { Router, @@ -38,8 +37,12 @@ export const permissionRouter = (router: Router) => { const { beforeEach } = router beforeEach((to, from, next) => { - const token = getCache(APP_CATCH_KEY.token) - const route = getCache('menuKey') || ROOT_ROUTE.path + const token = getStorage(APP_CATCH_KEY.token) + const route = getStorage( + 'menuKey', + 'sessionStorage', + ROOT_ROUTE.path, + ) as string const { meta } = to if (token !== null) { diff --git a/src/router/helper/routerCopilot.ts b/src/router/helper/routerCopilot.ts index b109a536..6bbd5d98 100644 --- a/src/router/helper/routerCopilot.ts +++ b/src/router/helper/routerCopilot.ts @@ -20,7 +20,7 @@ import { import { useSignin } from '@/store' import { useVueRouter } from '@/router/helper/useVueRouter' import { ROOT_ROUTE } from '@/appConfig/appConfig' -import { setCache } from '@/utils/cache' +import { setStorage } from '@/utils/cache' import type { Router } from 'vue-router' import type { AppRouteMeta } from '@/router/type' @@ -58,18 +58,27 @@ export const validRole = (meta: AppRouteMeta) => { /** * - * @remark 校验当前路由 + * @remark 校验当前路由是否显示 * - * 该方法进行校验时, 会将 hidden 与 role 一起进行校验 - * 如果有一条不满足校验, 则视为校验失败 + * 该方法进行校验时, 会将 hidden 与 sameLevel 一起进行校验 + * sameLevel 的优先级最高 * * 如果你仅仅是希望校验是否满足权限, 应该使用另一个方法 validRole */ export const validMenuItemShow = (option: AppMenuOption) => { 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 { path } = ROOT_ROUTE - setCache('menuKey', path) + setStorage('menuKey', path) isReplace ? push(path) : replace(path) } diff --git a/src/router/modules/axios.ts b/src/router/modules/axios.ts index 9245ca89..79bd211b 100644 --- a/src/router/modules/axios.ts +++ b/src/router/modules/axios.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/dashboard.ts b/src/router/modules/dashboard.ts index e1055243..bba64493 100644 --- a/src/router/modules/dashboard.ts +++ b/src/router/modules/dashboard.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/directive.ts b/src/router/modules/directive.ts index ffd65bc9..45611ad1 100644 --- a/src/router/modules/directive.ts +++ b/src/router/modules/directive.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/doc-local.ts b/src/router/modules/doc-local.ts index a89c681b..c65ba3c8 100644 --- a/src/router/modules/doc-local.ts +++ b/src/router/modules/doc-local.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/doc.ts b/src/router/modules/doc.ts index 4c6445d6..a4e65910 100644 --- a/src/router/modules/doc.ts +++ b/src/router/modules/doc.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/echart.ts b/src/router/modules/echart.ts index baca12c3..c287115e 100644 --- a/src/router/modules/echart.ts +++ b/src/router/modules/echart.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/error.ts b/src/router/modules/error.ts index 46307f1b..62e0c690 100644 --- a/src/router/modules/error.ts +++ b/src/router/modules/error.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/multi-menu.ts b/src/router/modules/multi-menu.ts index 565b4a91..1e8d0e06 100644 --- a/src/router/modules/multi-menu.ts +++ b/src/router/modules/multi-menu.ts @@ -1,9 +1,8 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' -import { LAYOUT } from '@/router/constant/index' - const multiMenu: AppRouteRecordRaw = { path: '/multi', name: 'MultiMenu', diff --git a/src/router/modules/office.ts b/src/router/modules/office.ts index 45920967..55c2466f 100644 --- a/src/router/modules/office.ts +++ b/src/router/modules/office.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/precision.ts b/src/router/modules/precision.ts index bad283e6..0dba74df 100644 --- a/src/router/modules/precision.ts +++ b/src/router/modules/precision.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/rely.ts b/src/router/modules/rely.ts index dc3d2db7..1cebef37 100644 --- a/src/router/modules/rely.ts +++ b/src/router/modules/rely.ts @@ -1,9 +1,8 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' -import { LAYOUT } from '@/router/constant/index' - const rely: AppRouteRecordRaw = { path: '/rely', name: 'RelyAbout', diff --git a/src/router/modules/scroll-reveal.ts b/src/router/modules/scroll-reveal.ts index 7c724a21..8cf9d085 100644 --- a/src/router/modules/scroll-reveal.ts +++ b/src/router/modules/scroll-reveal.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/router/modules/table.ts b/src/router/modules/table.ts index 809880c9..a25e84b2 100644 --- a/src/router/modules/table.ts +++ b/src/router/modules/table.ts @@ -1,4 +1,5 @@ import { t } from '@/locales/useI18n' +import { LAYOUT } from '@/router/constant/index' import type { AppRouteRecordRaw } from '@/router/type' diff --git a/src/store/modules/menu/helper.ts b/src/store/modules/menu/helper.ts index 3827b57b..c5d400af 100644 --- a/src/store/modules/menu/helper.ts +++ b/src/store/modules/menu/helper.ts @@ -14,7 +14,7 @@ import { APP_MENU_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig' import RayIcon from '@/components/RayIcon/index' import { isValueType } from '@/utils/hook' -import { getCache, setCache } from '@/utils/cache' +import { getStorage, setStorage } from '@/utils/cache' import type { VNode } from 'vue' import type { @@ -174,10 +174,11 @@ export const hasMenuIcon = (option: AppMenuOption) => { /** 获取缓存的 menu key, 如果未获取到则使用 ROOTROUTE path 当作默认激活路由菜单 */ export const getCatchMenuKey = () => { const { path: rootPath } = ROOT_ROUTE - const cacheMenuKey = - getCache('menuKey') === null - ? rootPath - : getCache('menuKey') + const cacheMenuKey = getStorage( + 'menuKey', + 'sessionStorage', + rootPath, + ) return cacheMenuKey } diff --git a/src/store/modules/menu/index.ts b/src/store/modules/menu/index.ts index c11fd790..30b642d9 100644 --- a/src/store/modules/menu/index.ts +++ b/src/store/modules/menu/index.ts @@ -24,8 +24,8 @@ import { NEllipsis } from 'naive-ui' -import { getCache, setCache } from '@/utils/cache' -import { validMenuItemShow, validRole } from '@/router/helper/routerCopilot' +import { setStorage } from '@/utils/cache' +import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot' import { parseAndFindMatchingNodes, 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 menuState.breadcrumbOptions = getCompleteRoutePath(menuState.options, key) @@ -124,12 +127,12 @@ export const useMenu = defineStore( /** 当 url 地址发生变化触发 menuTagOptions 更新 */ const setMenuTagOptionsWhenMenuValueChange = ( key: string | number, - option: MenuOption, + option: AppMenuOption, ) => { const tag = menuState.menuTagOptions.find((curr) => curr.path === key) 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 修改后, 缓存当前选择 key 并且存储标签页与跳转页面(router push 操作) */ - const changeMenuModelValue = (key: string | number, option: MenuOption) => { - const { meta, path } = option as unknown as AppRouteRecordRaw + const changeMenuModelValue = ( + key: string | number, + option: AppMenuOption, + ) => { + const { meta, path } = option if (meta.windowOpen) { window.open(meta.windowOpen) @@ -169,10 +175,10 @@ export const useMenu = defineStore( /** 检查是否为根路由 */ const count = (path.match(new RegExp('/', 'g')) || []).length - /** 更新浏览器标题 */ - updateDocumentTitle(option as unknown as AppMenuOption) /** 更新缓存队列 */ setKeepAliveInclude(option as unknown as AppMenuOption) + /** 更新浏览器标题 */ + updateDocumentTitle(option as unknown as AppMenuOption) if (!meta.sameLevel || (meta.sameLevel && count === 1)) { /** 更新标签菜单 */ @@ -182,7 +188,7 @@ export const useMenu = defineStore( menuState.menuKey = key /** 缓存菜单 key(sessionStorage) */ - setCache('menuKey', key) + setStorage('menuKey', key) } else { setBreadcrumbOptions(menuState.menuKey || '', option) } @@ -196,21 +202,33 @@ export const useMenu = defineStore( * @remark 监听路由地址变化更新菜单状态 * @remark 递归查找匹配项 */ - const updateMenuKeyWhenRouteUpdate = (path: string) => { - const appRawRoutes = expandRoutes(getAppRawRoutes()) + const updateMenuKeyWhenRouteUpdate = async (path: string) => { + // 获取 `/` 出现次数(如果为 1 则表示该路径为根路由路径) const count = (path.match(new RegExp('/', 'g')) || []).length - const fd = appRawRoutes.find((curr) => curr.path === path) let combinePath = path if (count > 1) { + // 如果不是跟路径则取出最后一项字符 const splitPath = path.split('/').filter((curr) => curr) combinePath = splitPath[splitPath.length - 1] } - if (fd) { - changeMenuModelValue(combinePath, fd as unknown as MenuOption) + const findMenuOption = (pathKey: string, options: AppMenuOption[]) => { + 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, /** 检查该菜单项是否展示 */ - show: - meta.hidden === false || meta.hidden === undefined ? true : false, } as AppMenuOption /** 合并 icon */ const attr: AppMenuOption = Object.assign({}, route, { @@ -246,14 +262,12 @@ export const useMenu = defineStore( }) if (option.path === getCatchMenuKey()) { - /** 设置浏览器标题 */ - updateDocumentTitle(attr) - setMenuTagOptionsWhenMenuValueChange( - option.path, - attr as unknown as MenuOption, - ) + /** 设置标签页(初始化时执行设置一次, 避免含有平级路由模式情况时出现不能正确设置标签页的情况) */ + setMenuTagOptionsWhenMenuValueChange(option.path, attr) } + attr.show = validMenuItemShow(attr) + return attr } @@ -320,9 +334,9 @@ export const useMenu = defineStore( const setupPiniaMenuStore = async () => { if (isSetupAppMenuLock.value) { await setupAppMenu() - - isSetupAppMenuLock.value = false } + + isSetupAppMenuLock.value = false } /** 监听路由变化并且更新路由菜单与菜单标签 */ @@ -333,7 +347,7 @@ export const useMenu = defineStore( const match = newData.match(reg)?.[1] await setupPiniaMenuStore() - updateMenuKeyWhenRouteUpdate(match || '') + await updateMenuKeyWhenRouteUpdate(match || '') }, { immediate: true, diff --git a/src/store/modules/setting/index.ts b/src/store/modules/setting/index.ts index b60d3d67..9b362438 100644 --- a/src/store/modules/setting/index.ts +++ b/src/store/modules/setting/index.ts @@ -1,5 +1,5 @@ import { getAppDefaultLanguage } from '@/locales/helper' -import { setCache } from '@use-utils/cache' +import { setStorage } from '@use-utils/cache' import { set } from 'lodash-es' import { addClass, removeClass, colorToRgba } from '@/utils/element' import { useI18n } from '@/locales/useI18n' @@ -46,7 +46,7 @@ export const useSetting = defineStore( settingState.localeLanguage = key - setCache('localeLanguage', key, 'localStorage') + setStorage('localeLanguage', key, 'localStorage') } /** 切换主题色 */ diff --git a/src/store/modules/signin/index.ts b/src/store/modules/signin/index.ts index 202c3373..134646b9 100644 --- a/src/store/modules/signin/index.ts +++ b/src/store/modules/signin/index.ts @@ -20,7 +20,7 @@ */ import { isEmpty } from 'lodash-es' -import { removeCache } from '@/utils/cache' +import { removeStorage } from '@/utils/cache' import type { SigninForm, @@ -79,7 +79,7 @@ export const useSignin = defineStore( */ const logout = () => { window.$message.info('账号退出中...') - removeCache('all-sessionStorage') + removeStorage('all-sessionStorage') setTimeout(() => window.location.reload()) } diff --git a/src/types/modules/utils.ts b/src/types/modules/utils.ts index b39a45ce..e2612915 100644 --- a/src/types/modules/utils.ts +++ b/src/types/modules/utils.ts @@ -42,3 +42,9 @@ export type CipherParams = CryptoJS.lib.CipherParams export type AnyFunc = (...args: any[]) => any export type AnyVoidFunc = (...args: any[]) => void + +export type PartialCSSStyleDeclaration = Partial< + Record +> + +export type ElementSelector = string | `attr:${string}` diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 1bc9094e..93f8a2a3 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -18,16 +18,26 @@ import type { CacheType } from '@/types/modules/utils' * @param key 需要设置的key * @param value 需要缓存的值 */ -export const setCache = ( +export const setStorage = ( key: string, value: T, type: CacheType = 'sessionStorage', ) => { - const waitCacheValue = JSON.stringify(value) + if (!key) { + console.error('Failed to set stored data: key is empty or undefined') - type === 'localStorage' - ? window.localStorage.setItem(key, waitCacheValue) - : window.sessionStorage.setItem(key, waitCacheValue) + return + } + + 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 = ( * @param key 需要获取目标缓存的key * @returns 获取缓存值 */ -export const getCache = ( +export const getStorage = ( key: string, - type: CacheType = 'sessionStorage', + storageType: CacheType = 'sessionStorage', + defaultValue?: T, ): T | null => { - const data = - type === 'localStorage' - ? window.localStorage.getItem(key) - : window.sessionStorage.getItem(key) + try { + const data = + storageType === 'localStorage' + ? 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 = ( * - all-sessionStorage: 删除所有 sessionStorage 缓存值 * - all-localStorage: 删除所有 localStorage 缓存值 */ -export const removeCache = ( +export const removeStorage = ( key: string | 'all' | 'all-sessionStorage' | 'all-localStorage', type: CacheType = 'sessionStorage', ) => { @@ -78,6 +99,12 @@ export const removeCache = ( break default: + if (!key) { + console.error('Failed to remove stored data: key is empty or undefined') + + return + } + type === 'localStorage' ? window.localStorage.removeItem(key) : window.sessionStorage.removeItem(key) diff --git a/src/utils/element.ts b/src/utils/element.ts index 780930e8..4bfb48cb 100644 --- a/src/utils/element.ts +++ b/src/utils/element.ts @@ -1,7 +1,11 @@ import { isValueType } from '@use-utils/hook' 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 styles 所需绑定样式(如果为字符串, 则必须以分号结尾每个行内样式描述) * + * + * @example * style of string * ``` * const styles = 'width: 100px; height: 100px; background: red;' @@ -132,27 +138,37 @@ export const hasClass = (element: HTMLElement, className: string) => { * addStyle(styles) * ``` */ -export const addStyle = ( +export const addStyle = ( el: HTMLElement, - styles: K | Partial, + styles: PartialCSSStyleDeclaration | string, ) => { - if (el) { - if (isValueType(styles, 'Object')) { - Object.keys(styles).forEach((item) => { - el.style[item] = styles[item] - }) - } else if (isValueType(styles, 'String')) { - const _styles = styles - - _styles.split(';').forEach((item) => { - const [_k, _v] = item.split(':') - - if (_k && _v) { - el.style[_k.trim()] = _v.trim() - } - }) - } + if (!el) { + return } + + let styleObj: PartialCSSStyleDeclaration + + if (isValueType(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 = ( * @param el Target element dom * @param styles 所需卸载样式 */ -export const removeStyle = ( +export const removeStyle = ( el: HTMLElement, - styles: K[], + styles: (keyof CSSStyleDeclaration & string)[], ) => { - if (el) { - styles.forEach((curr) => { - el.style.removeProperty(curr) - }) + if (!el) { + return } + + styles.forEach((curr) => { + el.style.removeProperty(curr) + }) } /** @@ -222,33 +240,33 @@ export const colorToRgba = (color: string, alpha = 1) => { * 示例: * * class: - * const el = getElement('.demo') + * const el = getElements('.demo') * id: - * const el = getElement('#demo') + * const el = getElements('#demo') * 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 = (element: string) => { - if (!element) { +export const getElements = ( + selector: ElementSelector, +) => { + if (!selector) { return null } - let queryParam: string - - if (element.startsWith('attr:')) { - queryParam = '[' + element.replace('attr:', '') + ']' - } else { - queryParam = element - } + const queryParam = selector.startsWith('attr:') + ? `[${selector.replace('attr:', '')}]` + : selector try { - const el = Array.from(document.querySelectorAll(queryParam)) + const elements = Array.from(document.querySelectorAll(queryParam)) - return el - } catch (e) { - return [] + return elements + } catch (error) { + console.error(`Failed to get elements for selector '${selector}'`, error) + + return null } } diff --git a/src/views/login/components/Signin/index.tsx b/src/views/login/components/Signin/index.tsx index 0a9f5e15..8abc7615 100644 --- a/src/views/login/components/Signin/index.tsx +++ b/src/views/login/components/Signin/index.tsx @@ -1,6 +1,6 @@ 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 { useSignin } from '@/store' import { useI18n } from '@/locales/useI18n' @@ -55,8 +55,8 @@ const Signin = defineComponent({ window.$message.success(`欢迎${signinForm.value.name}登陆~`) - setCache(APP_CATCH_KEY.token, 'tokenValue') - setCache(APP_CATCH_KEY.signin, res.data) + setStorage(APP_CATCH_KEY.token, 'tokenValue') + setStorage(APP_CATCH_KEY.signin, res.data) router.push(path) }, 2 * 1000)