diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..367ab564 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# CHANGE LOG + +## 3.1.1 + +### Fixes + +- 修复国际化语言包模块合并处理不能正常合并问题 +- 修复国际化切换时,面包屑、标签页不能正常切换 + +### Feats + +- 新增面包屑 +- 支持国际化语言包分包管理(但是,依旧是合并到一个文件中,所以需要注意 key 的管理) diff --git a/locales/en-US.json b/locales/en-US.json index 48efddc9..a0dc36f8 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -8,6 +8,7 @@ "scrollReveal": "Scroll Reveal", "Axios": "Axios Request", "Table": "Table", + "MultiMenu": "MultiMenu", "Doc": "Doc", "DocLocal": "Doc (China)" }, diff --git a/locales/system-one/en-US.json b/locales/system-one/en-US.json index 48efddc9..a0dc36f8 100644 --- a/locales/system-one/en-US.json +++ b/locales/system-one/en-US.json @@ -8,6 +8,7 @@ "scrollReveal": "Scroll Reveal", "Axios": "Axios Request", "Table": "Table", + "MultiMenu": "MultiMenu", "Doc": "Doc", "DocLocal": "Doc (China)" }, diff --git a/locales/system-one/zh-CN.json b/locales/system-one/zh-CN.json index 87df69de..645bec79 100644 --- a/locales/system-one/zh-CN.json +++ b/locales/system-one/zh-CN.json @@ -8,6 +8,7 @@ "scrollReveal": "滚动动画", "Axios": "请求", "Table": "表格", + "MultiMenu": "多级菜单", "Doc": "文档", "DocLocal": "文档 (国内地址)" }, diff --git a/locales/system-two/en-US.json b/locales/system-two/en-US.json index 48efddc9..a0dc36f8 100644 --- a/locales/system-two/en-US.json +++ b/locales/system-two/en-US.json @@ -8,6 +8,7 @@ "scrollReveal": "Scroll Reveal", "Axios": "Axios Request", "Table": "Table", + "MultiMenu": "MultiMenu", "Doc": "Doc", "DocLocal": "Doc (China)" }, diff --git a/locales/system-two/zh-CN.json b/locales/system-two/zh-CN.json index 87df69de..645bec79 100644 --- a/locales/system-two/zh-CN.json +++ b/locales/system-two/zh-CN.json @@ -8,6 +8,7 @@ "scrollReveal": "滚动动画", "Axios": "请求", "Table": "表格", + "MultiMenu": "多级菜单", "Doc": "文档", "DocLocal": "文档 (国内地址)" }, diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 87df69de..645bec79 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -8,6 +8,7 @@ "scrollReveal": "滚动动画", "Axios": "请求", "Table": "表格", + "MultiMenu": "多级菜单", "Doc": "文档", "DocLocal": "文档 (国内地址)" }, diff --git a/package.json b/package.json index 0884a817..4845640e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ray-template", "private": true, - "version": "3.1.0", + "version": "3.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/src/language/index.ts b/src/language/index.ts index aa79b956..68bb7fa8 100644 --- a/src/language/index.ts +++ b/src/language/index.ts @@ -28,6 +28,7 @@ import { createI18n } from 'vue-i18n' import { naiveLocales } from './language' import { getCache } from '@use-utils/cache' +import { forIn, merge } from 'lodash-es' export { naiveLocales, localOptions } from './language' @@ -51,10 +52,16 @@ export const getMatchLanguageModule = () => { }) const moduleKeys = Object.keys(modules) - moduleKeys.forEach((curr) => { - const k = curr.match(reg)?.[1] as string - msg[k] = Object.assign({}, JSON.parse(modules[curr])) + moduleKeys.forEach((curr) => { + const k = curr.match(reg)?.[1] as string // 当前语言包类型(zh-CN, en-US...) + const content = JSON.parse(modules[curr]) // 当前语言包内容 + + msg[k] = merge({}, msg[k]) + + forIn(content, (value, ckey) => { + msg[k][ckey] = merge(msg[k][ckey], value) + }) }) } catch (e) { console.error(e) diff --git a/src/layout/components/MenuTag/index.tsx b/src/layout/components/MenuTag/index.tsx index e73bc329..9af2ad24 100644 --- a/src/layout/components/MenuTag/index.tsx +++ b/src/layout/components/MenuTag/index.tsx @@ -19,9 +19,11 @@ const MenuTag = defineComponent({ name: 'MenuTag', setup() { const menuStore = useMenu() - const { menuTagOptions, menuKey } = storeToRefs(menuStore) + const { menuKey } = storeToRefs(menuStore) const { menuModelValueChange, spliceMenTagOptions } = menuStore + const modelMenuTagOptions = computed(() => menuStore.menuTagOptions) + /** * * @param idx 索引 @@ -32,7 +34,7 @@ const MenuTag = defineComponent({ spliceMenTagOptions(idx) if (menuKey.value !== '/dashboard') { - const options = menuTagOptions.value as MenuOption[] + const options = modelMenuTagOptions.value const length = options.length const tag = options[length - 1] @@ -50,7 +52,7 @@ const MenuTag = defineComponent({ } return { - menuTagOptions, + modelMenuTagOptions, menuModelValueChange, handleCloseTag, menuKey, @@ -61,10 +63,10 @@ const MenuTag = defineComponent({ return ( - {this.menuTagOptions.map((curr, idx) => ( + {this.modelMenuTagOptions.map((curr, idx) => ( 1 + curr.key !== '/dashboard' && this.modelMenuTagOptions.length > 1 } onClose={() => this.handleCloseTag(idx)} type={curr.key === this.menuKey ? 'success' : 'info'} diff --git a/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx b/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx new file mode 100644 index 00000000..a121ecde --- /dev/null +++ b/src/layout/components/SiderBar/Components/Breadcrumb/index.tsx @@ -0,0 +1,76 @@ +/** + * + * @author Ray + * + * @date 2023-03-03 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +/** + * + * 顶部面包屑 + * + * 如果下拉菜单条目小于一条, 则不会触发下拉菜单 + * + * 添加 标签, 避免 Runtime directive used on component... 警告 + */ + +import { NDropdown, NBreadcrumb, NBreadcrumbItem } from 'naive-ui' + +import { useMenu } from '@/store' + +import type { DropdownOption } from 'naive-ui' + +const Breadcrumb = defineComponent({ + name: 'Breadcrumb', + setup() { + const menuStore = useMenu() + + const { menuModelValueChange } = menuStore + const modelBreadcrumbOptions = computed(() => menuStore.breadcrumbOptions) + + const handleDropdownSelect = ( + key: string | number, + option: DropdownOption, + ) => { + menuModelValueChange(key, option) + } + + return { + modelBreadcrumbOptions, + handleDropdownSelect, + } + }, + render() { + return ( + + {this.modelBreadcrumbOptions.map((curr) => ( + + 1 ? curr.children : [] + } + onSelect={this.handleDropdownSelect.bind(this)} + > + {{ + default: () => ( + + {curr.label && typeof curr.label === 'function' + ? curr.label() + : curr.breadcrumbLabel} + + ), + }} + + + ))} + + ) + }, +}) + +export default Breadcrumb diff --git a/src/layout/components/SiderBar/Components/SettingDrawer/index.tsx b/src/layout/components/SiderBar/Components/SettingDrawer/index.tsx index 170f57ba..74e7ae37 100644 --- a/src/layout/components/SiderBar/Components/SettingDrawer/index.tsx +++ b/src/layout/components/SiderBar/Components/SettingDrawer/index.tsx @@ -38,8 +38,12 @@ const SettingDrawer = defineComponent({ const settingStore = useSetting() const { changePrimaryColor, changeSwitcher } = settingStore - const { themeValue, primaryColorOverride, menuTagSwitch } = - storeToRefs(settingStore) + const { + themeValue, + primaryColorOverride, + menuTagSwitch, + breadcrumbSwitch, + } = storeToRefs(settingStore) const modelShow = computed({ get: () => props.show, @@ -61,6 +65,7 @@ const SettingDrawer = defineComponent({ primaryColorOverride, menuTagSwitch, changeSwitcher, + breadcrumbSwitch, } }, render() { @@ -135,6 +140,14 @@ const SettingDrawer = defineComponent({ } /> + + + this.changeSwitcher(bool, 'breadcrumbSwitch') + } + /> + diff --git a/src/layout/components/SiderBar/index.tsx b/src/layout/components/SiderBar/index.tsx index 5b15e265..fbe552fc 100644 --- a/src/layout/components/SiderBar/index.tsx +++ b/src/layout/components/SiderBar/index.tsx @@ -15,6 +15,7 @@ import { NLayoutHeader, NSpace, NTooltip, NDropdown, NTag } from 'naive-ui' import RayIcon from '@/components/RayIcon/index' import RayTooltipIcon from '@/components/RayTooltipIcon/index' import SettingDrawer from './components/SettingDrawer/index' +import Breadcrumb from './components/Breadcrumb/index' import { useSetting } from '@/store' import { localOptions } from '@/language/index' @@ -39,7 +40,7 @@ const SiderBar = defineComponent({ const { t } = useI18n() const { updateLocale, changeSwitcher } = settingStore - const modelDrawerPlacement = ref(settingStore.drawerPlacement) + const { drawerPlacement, breadcrumbSwitch } = storeToRefs(settingStore) const showSettings = ref(false) const person = getCache('person') const spaceItemStyle = { @@ -127,12 +128,13 @@ const SiderBar = defineComponent({ rightTooltipIconOptions, t, handleIconClick, - modelDrawerPlacement, showSettings, updateLocale, handlePersonSelect, person, spaceItemStyle, + drawerPlacement, + breadcrumbSwitch, } }, render() { @@ -159,6 +161,7 @@ const SiderBar = defineComponent({ }} ))} + {this.breadcrumbSwitch ? : ''} {this.rightTooltipIconOptions.map((curr) => ( @@ -203,7 +206,7 @@ const SiderBar = defineComponent({ ) diff --git a/src/router/index.ts b/src/router/index.ts index c3f9fb36..ba653965 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -4,10 +4,11 @@ import { constantRoutes } from './routes' import { permissionRouter as _permissionRouter } from './permission' import type { App } from 'vue' +import type { RouteRecordRaw } from 'vue-router' export const router = createRouter({ history: createWebHashHistory(), - routes: constantRoutes, + routes: constantRoutes as unknown as RouteRecordRaw[], scrollBehavior: () => ({ left: 0, top: 0 }), }) diff --git a/src/router/modules/index.ts b/src/router/modules/index.ts index c841bc5e..7a02d94d 100644 --- a/src/router/modules/index.ts +++ b/src/router/modules/index.ts @@ -6,6 +6,7 @@ import scrollReveal from './scroll-reveal' import axios from './axios' import table from './table' import doc from './doc' +import multiMenu from './multi-menu' import docLocal from './doc-local' const routes = [ @@ -15,6 +16,7 @@ const routes = [ axios, scrollReveal, error, + multiMenu, doc, docLocal, reyl, diff --git a/src/router/modules/multi-menu.ts b/src/router/modules/multi-menu.ts new file mode 100644 index 00000000..84ffcce9 --- /dev/null +++ b/src/router/modules/multi-menu.ts @@ -0,0 +1,40 @@ +export default { + path: '/multi-menu', + name: 'multi-menu', + component: () => import('@/views/multi-menu/index'), + meta: { + i18nKey: 'MultiMenu', + icon: 'table', + }, + children: [ + { + path: 'multi-menu-one', + name: 'multi-menu-one', + component: () => import('@/views/multi-menu/views/multi-menu-one/index'), + meta: { + noLocalTitle: '多级菜单-1', + }, + }, + { + path: 'multi-menu-two', + name: 'multi-menu-two', + component: () => import('@/views/multi-menu/views/multi-menu-two/index'), + meta: { + noLocalTitle: '多级菜单-2', + }, + children: [ + { + path: 'sub-menu', + name: 'sub-menu', + component: () => + import( + '@/views/multi-menu/views/multi-menu-two/views/sub-menu/index' + ), + meta: { + noLocalTitle: '多级菜单-2-1', + }, + }, + ], + }, + ], +} diff --git a/src/router/remark.md b/src/router/remark.md new file mode 100644 index 00000000..9c0996f0 --- /dev/null +++ b/src/router/remark.md @@ -0,0 +1,23 @@ +- 类型 + +```ts +interface RouteMeta { + i18nKey: string + icon?: string + windowOpen?: string + role?: string[] + hidden?: boolean + noLocalTitle?: string | number +} +``` + +- 说明 + +``` +i18nKey: i18n 国际化 key, 会优先使用该字段 +icon: icon 图标, 用于 Menu 菜单(依赖 RayIcon 组件实现) +windowOpen: 超链接打开 +role: 权限表 +hidden: 是否显示 +noLocalTitle: 不使用国际化渲染 Menu Titile +``` diff --git a/src/store/index.ts b/src/store/index.ts index bc8b0784..4858b759 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,7 +3,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import type { App } from 'vue' export { useSetting } from './modules/setting' // import { useSetting } from '@/store' 即可使用 -export { useMenu } from './modules/menu' +export { useMenu } from './modules/menu/index' export { useSignin } from './modules/signin' const store = createPinia() diff --git a/src/store/modules/menu.ts b/src/store/modules/menu.ts deleted file mode 100644 index 807d1e02..00000000 --- a/src/store/modules/menu.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { NEllipsis } from 'naive-ui' -import RayIcon from '@/components/RayIcon/index' - -import { getCache, setCache } from '@/utils/cache' -import { validRole } from '@/router/basic' - -import type { MenuOption } from 'naive-ui' -import type { RouteMeta } from 'vue-router' - -export const useMenu = defineStore('menu', () => { - const router = useRouter() - const route = useRoute() - const { t } = useI18n() - - const cacheMenuKey = - getCache('menuKey') === 'no' ? '/dashboard' : getCache('menuKey') - - const menuState = reactive({ - menuKey: cacheMenuKey as string | null, // 当前菜单 `key` - options: [] as IMenuOptions[], // 菜单列表 - collapsed: false, // 是否折叠菜单 - menuTagOptions: [] as TagMenuOptions[], - }) - - const handleMenuTagOptions = (item: IMenuOptions) => { - if (item.path !== menuState.menuKey) { - const tag = menuState.menuTagOptions.find( - (curr) => curr.path === item.path, - ) - - if (!tag) { - menuState.menuTagOptions.push(item) - } - } - } - - /** - * - * @param key 菜单更新后的 `key` - * @param item 菜单当前 `item` - * - * 修改 `menu key` 后的回调函数 - */ - const menuModelValueChange = (key: string, item: MenuOption) => { - const meta = item.meta as RouteMeta - - if (meta.windowOpen) { - window.open(meta.windowOpen) - } else { - handleMenuTagOptions(item as unknown as TagMenuOptions) - - menuState.menuKey = key - - router.push(`${item.path}`) - - setCache('menuKey', key) - } - } - - /** - * - * @param path 路由地址 - * - * 监听路由地址变化更新菜单状态 - */ - const updateMenuKeyWhenRouteUpdate = (path: string) => { - const matchMenuItem = (options: MenuOption[]) => { - for (const i of options) { - if (i?.children?.length) { - matchMenuItem(i.children) - } - - if (path === i.path) { - menuModelValueChange(i.path, i) - - break - } - } - } - - matchMenuItem(menuState.options as MenuOption[]) - } - - /** - * - * @remark 初始化菜单列表, 并且按照权限过滤 - * @remark 如果权限发生变动, 则会触发强制弹出页面并且重新登陆 - */ - const setupAppRoutes = () => { - const layout = router.getRoutes().find((route) => route.name === 'layout') - - const resolveRoutes = (routes: IMenuOptions[], index: number) => { - return routes.map((curr) => { - if (curr.children?.length) { - curr.children = resolveRoutes(curr.children, index++) - } - - const { meta } = curr - - const route = { - ...curr, - key: curr.path, - label: () => - h(NEllipsis, null, { - default: () => t(`GlobalMenuOptions.${meta!.i18nKey}`), - }), - } - - const expandIcon = { - icon: () => - h( - RayIcon, - { - name: meta!.icon as string, - size: 20, - }, - {}, - ), - } - - const attr: IMenuOptions = meta?.icon - ? Object.assign({}, route, expandIcon) - : route - - if (curr.path === cacheMenuKey) { - menuState.menuTagOptions.push(attr) - } - - attr.show = validRole(curr) - - return attr - }) - } - - menuState.options = resolveRoutes(layout?.children as IMenuOptions[], 0) - } - - /** - * - * @param collapsed 折叠菜单开关 - */ - const collapsedMenu = (collapsed: boolean) => - (menuState.collapsed = collapsed) - - const spliceMenTagOptions = (idx: number) => - menuState.menuTagOptions.splice(idx, 1) - - watch( - () => route.fullPath, - (newData) => { - updateMenuKeyWhenRouteUpdate(newData) - }, - { - immediate: true, - }, - ) - - return { - ...toRefs(menuState), - menuModelValueChange, - setupAppRoutes, - collapsedMenu, - spliceMenTagOptions, - } -}) diff --git a/src/store/modules/menu/helper.ts b/src/store/modules/menu/helper.ts new file mode 100644 index 00000000..542bff84 --- /dev/null +++ b/src/store/modules/menu/helper.ts @@ -0,0 +1,110 @@ +/** + * + * @author Ray + * + * @date 2023-03-03 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +/** 本方法感谢 的支持 */ + +/** + * + * @param node 当前节点 + * @param key 动态字段 + * @param value 匹配值 + * + * @remark 检查是否为所需项 + */ +const check = ( + node: IMenuOptions, + key: string | number, + value: string | number, +) => { + return node[key] === value || node.key === value +} + +/** + * + * @param options 节点数组 + * @param key 动态字段 + * @param value 匹配值 + * + * @remark 匹配所有节点 + */ +const process = ( + options: IMenuOptions, + key: string | number, + value: string | number, +) => { + const temp: IMenuOptions[] = [] + + // 检查当前节点是否匹配值 + if (check(options, key, value)) { + temp.push(options) + + return temp + } + + // 遍历子节点 + if (options.children && options.children.length > 0) { + for (const it of options.children) { + // 子节点递归调用 + const innerTemp = process(it, key, value) + + // 如果子节点匹配到了,则将当前节点加入数组 + if (innerTemp.length > 0) { + temp.push(options, ...innerTemp) + } + } + } + + return temp +} + +/** + * + * @param options 节点数组 + * @param key 动态字段 + * @param value 匹配值 + */ +export const parse = ( + options: IMenuOptions[], + key: string | number, + value: string | number, +) => { + const temp = [] + + for (const it of options) { + const innerTemp = process(it, key, value) + + if (innerTemp.length > 0) { + temp.push(...innerTemp) + } + } + + return temp +} + +/** + * + * @param item menu options + * + * @remark 查找当前菜单项 + */ +export const matchMenuOption = ( + item: IMenuOptions, + key: MenuKey, + menuTagOptions: TagMenuOptions[], +) => { + if (item.path !== key) { + const tag = menuTagOptions.find((curr) => curr.path === item.path) + + if (!tag) { + menuTagOptions.push(item) + } + } +} diff --git a/src/store/modules/menu/index.ts b/src/store/modules/menu/index.ts new file mode 100644 index 00000000..3aa5c62f --- /dev/null +++ b/src/store/modules/menu/index.ts @@ -0,0 +1,218 @@ +/** + * + * @author Ray + * + * @date 2022-11-03 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +/** + * + * 该文件为 menu 菜单 pinia store + * + * 说明: + * - BreadcrumbMenu、TagMenu、Menu 统一管理 + * - BreadcrumbMenu、TagMenu、Menu 属性值重度依赖 vue-router routers, 所以需要按照该项目约定方法进行配置 + */ + +import { NEllipsis } from 'naive-ui' +import RayIcon from '@/components/RayIcon/index' + +import { getCache, setCache } from '@/utils/cache' +import { validRole } from '@/router/basic' +import { parse, matchMenuOption } from './helper' + +import type { MenuOption } from 'naive-ui' +import type { RouteMeta } from 'vue-router' + +export const useMenu = defineStore( + 'menu', + () => { + const router = useRouter() + const route = useRoute() + const { t } = useI18n() + + const cacheMenuKey = + getCache('menuKey') === 'no' ? '/dashboard' : getCache('menuKey') + + const menuState = reactive({ + menuKey: cacheMenuKey as MenuKey, // 当前菜单 `key` + options: [] as IMenuOptions[], // 菜单列表 + collapsed: false, // 是否折叠菜单 + menuTagOptions: [] as TagMenuOptions[], // tag 标签菜单 + breadcrumbOptions: [] as IMenuOptions[], // 面包屑菜单 + }) + + /** + * + * @param key 菜单更新后的 `key` + * @param item 菜单当前 `item` + * + * 修改 `menu key` 后的回调函数 + */ + const menuModelValueChange = (key: string | number, item: MenuOption) => { + const meta = item.meta as RouteMeta + + if (meta.windowOpen) { + window.open(meta.windowOpen) + } else { + // 防止重复点击做重复操作处理 + if (menuState.menuKey !== key) { + matchMenuOption( + item as unknown as TagMenuOptions, + menuState.menuKey, + menuState.menuTagOptions, + ) + + menuState.breadcrumbOptions = parse(menuState.options, 'key', key) // 获取面包屑 + + if (key[0] !== '/') { + const p = menuState.breadcrumbOptions + .map((curr) => curr.key) + .join('/') + + router.push(p) + } else { + router.push(item.path as string) + } + + menuState.menuKey = key + setCache('menuKey', key) + } + } + } + + /** + * + * @param path 路由地址 + * + * 监听路由地址变化更新菜单状态 + */ + const updateMenuKeyWhenRouteUpdate = (path: string) => { + const matchMenuItem = (options: MenuOption[]) => { + for (const i of options) { + if (i?.children?.length) { + matchMenuItem(i.children) + } + + if (path === i.path) { + menuModelValueChange(i.path, i) + + break + } + } + } + + matchMenuItem(menuState.options as MenuOption[]) + } + + /** + * + * @remark 初始化菜单列表, 并且按照权限过滤 + * @remark 如果权限发生变动, 则会触发强制弹出页面并且重新登陆 + */ + const setupAppRoutes = () => { + const layout = router.getRoutes().find((route) => route.name === 'layout') + + const resolveRoutes = (routes: IMenuOptions[], index: number) => { + return routes.map((curr) => { + if (curr.children?.length) { + curr.children = resolveRoutes(curr.children, index++) + } + + const { meta } = curr + const label = computed(() => + meta?.i18nKey + ? t(`GlobalMenuOptions.${meta!.i18nKey}`) + : meta?.noLocalTitle, + ) + + /** 拼装菜单项 */ + const route = { + ...curr, + key: curr.path, + label: () => + h(NEllipsis, null, { + default: () => label.value, + }), + breadcrumbLabel: label.value, + } as IMenuOptions + + /** 是否有 icon */ + const expandIcon = { + icon: () => + h( + RayIcon, + { + name: meta!.icon as string, + size: 20, + }, + {}, + ), + } + + const attr: IMenuOptions = meta?.icon + ? Object.assign({}, route, expandIcon) + : route + + if (curr.path === cacheMenuKey) { + menuState.menuTagOptions.push(attr) + } + + attr.show = validRole(curr) + + return attr + }) + } + + menuState.options = resolveRoutes(layout?.children as IMenuOptions[], 0) + + /** 初始化后渲染面包屑 */ + nextTick(() => { + menuState.breadcrumbOptions = parse( + menuState.options, + 'key', + menuState.menuKey as string, + ) + }) + } + + /** + * + * @param collapsed 折叠菜单开关 + */ + const collapsedMenu = (collapsed: boolean) => + (menuState.collapsed = collapsed) + + const spliceMenTagOptions = (idx: number) => + menuState.menuTagOptions.splice(idx, 1) + + watch( + () => route.fullPath, + (newData) => { + updateMenuKeyWhenRouteUpdate(newData) + }, + { + immediate: true, + }, + ) + + return { + ...toRefs(menuState), + menuModelValueChange, + setupAppRoutes, + collapsedMenu, + spliceMenTagOptions, + } + }, + { + persist: { + key: 'piniaMenuStore', + storage: window.sessionStorage, + paths: ['breadcrumbOptions', 'menuKey'], + }, + }, +) diff --git a/src/store/modules/setting.ts b/src/store/modules/setting.ts index a5ac2572..b7e29fce 100644 --- a/src/store/modules/setting.ts +++ b/src/store/modules/setting.ts @@ -1,4 +1,5 @@ import { naiveLocales, getDefaultNaiveLocal } from '@/language/index' +import { setCache } from '@use-utils/cache' export const useSetting = defineStore( 'setting', @@ -15,6 +16,7 @@ export const useSetting = defineStore( menuTagSwitch: true, // 多标签页开关 naiveLocal: getDefaultNaiveLocal(), // `naive ui` 语言包 spinSwitch: false, // 全屏加载 + breadcrumbSwitch: true, // 面包屑开关 }) const { locale } = useI18n() @@ -22,6 +24,8 @@ export const useSetting = defineStore( // TODO: 修改语言 locale.value = key settingState.naiveLocal = naiveLocales(key) + + setCache('localeLanguage', key, 'localStorage') } const changePrimaryColor = (value: string) => { diff --git a/src/types/store.d.ts b/src/types/store.d.ts index 9e0e1e4b..94eece83 100644 --- a/src/types/store.d.ts +++ b/src/types/store.d.ts @@ -13,7 +13,11 @@ declare global { show?: boolean children?: IMenuOptions[] meta?: RouteMeta + breadcrumbLabel?: string + noLocalTitle?: string | number } declare interface TagMenuOptions extends IMenuOptions {} + + declare type MenuKey = null | string | number } diff --git a/src/views/axios/index.tsx b/src/views/axios/index.tsx index cc651cfd..8705e0bf 100644 --- a/src/views/axios/index.tsx +++ b/src/views/axios/index.tsx @@ -7,6 +7,7 @@ import { NLayoutHeader, NSpace, NInput, + NButton, } from 'naive-ui' import { onAxiosTest } from '@use-api/test' @@ -41,9 +42,13 @@ const Axios = defineComponent({ ] const handleInputCityValue = async (value: string) => { - const cb = await onAxiosTest(value) + try { + const cb = await onAxiosTest(value) - state.weatherData = cb.data as unknown as IUnknownObjectKey[] + state.weatherData = cb.data as unknown as IUnknownObjectKey[] + } catch (e) { + window.$message.error('请求已被取消') + } } onBeforeMount(async () => { @@ -62,20 +67,23 @@ const Axios = defineComponent({ - 基于 axios 封装, 能够自动取消连续请求, 避免重复渲染造成问题. + 基于 axios 封装,能够自动取消连续请求,避免重复渲染造成问题 +

+ 打开控制台 => 网络 => 使用低速3g网络 => + 查看控制台被取消的请求 +

- + + + 搜索 + diff --git a/src/views/multi-menu/index.tsx b/src/views/multi-menu/index.tsx new file mode 100644 index 00000000..6e71aa22 --- /dev/null +++ b/src/views/multi-menu/index.tsx @@ -0,0 +1,24 @@ +/** + * + * @author Ray + * + * @date 2023-03-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import { RouterView } from 'vue-router' + +const MultiMenu = defineComponent({ + name: 'MultiMenu', + setup() { + return {} + }, + render() { + return + }, +}) + +export default MultiMenu diff --git a/src/views/multi-menu/views/multi-menu-one/index.tsx b/src/views/multi-menu/views/multi-menu-one/index.tsx new file mode 100644 index 00000000..bf476cfd --- /dev/null +++ b/src/views/multi-menu/views/multi-menu-one/index.tsx @@ -0,0 +1,22 @@ +/** + * + * @author Ray + * + * @date 2023-03-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +const MultiMenuOne = defineComponent({ + name: 'MultiMenuOne', + setup() { + return {} + }, + render() { + return
多级菜单-1
+ }, +}) + +export default MultiMenuOne diff --git a/src/views/multi-menu/views/multi-menu-two/index.tsx b/src/views/multi-menu/views/multi-menu-two/index.tsx new file mode 100644 index 00000000..1ac76026 --- /dev/null +++ b/src/views/multi-menu/views/multi-menu-two/index.tsx @@ -0,0 +1,24 @@ +/** + * + * @author Ray + * + * @date 2023-03-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import { RouterView } from 'vue-router' + +const MultiMenuTwo = defineComponent({ + name: 'MultiMenuTwo', + setup() { + return {} + }, + render() { + return + }, +}) + +export default MultiMenuTwo diff --git a/src/views/multi-menu/views/multi-menu-two/views/sub-menu/index.tsx b/src/views/multi-menu/views/multi-menu-two/views/sub-menu/index.tsx new file mode 100644 index 00000000..5550f387 --- /dev/null +++ b/src/views/multi-menu/views/multi-menu-two/views/sub-menu/index.tsx @@ -0,0 +1,22 @@ +/** + * + * @author Ray + * + * @date 2023-03-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +const SubMenu = defineComponent({ + name: 'SubMenu', + setup() { + return {} + }, + render() { + return
多级菜单-2-1
+ }, +}) + +export default SubMenu diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index fb4a00a2..144b9cf5 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -17,6 +17,7 @@ declare module 'vue-router' { windowOpen?: string role?: string[] hidden?: boolean + noLocalTitle?: string | number } }