fix: remove meta. perfix

This commit is contained in:
chansee97 2024-06-06 23:35:55 +08:00
parent bf5445b6e4
commit 39d185b132
20 changed files with 839 additions and 791 deletions

View File

@ -12,7 +12,7 @@ const value = defineModel<LayoutMode>('value', { required: true })
:class="{
'outline outline-2': value === 'leftMenu',
}"
class="grid grid-cols-[20%_1fr] grid-rows-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2)"
class="grid grid-cols-[20%_1fr] grid-rows-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
@click="value = 'leftMenu'"
>
<div class="bg-[var(--primary-color)] row-span-2" />
@ -29,7 +29,7 @@ const value = defineModel<LayoutMode>('value', { required: true })
:class="{
'outline outline-2': value === 'topMenu',
}"
class="grid grid-rows-[30%_1fr] outline-[var(--primary-color)] hover:(outline outline-2)"
class="grid grid-rows-[30%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
@click="value = 'topMenu'"
>
<div class="bg-[var(--primary-color)]" />

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import { useAppStore } from '@/store'
const appStore = useAppStore()
</script>
<template>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<CommonWrapper @click="appStore.showSetting = !appStore.showSetting">
<div>
<icon-park-outline-setting-two />
</div>
</CommonWrapper>
</template>
<span>{{ $t('app.setting') }}</span>
</n-tooltip>
</template>

View File

@ -0,0 +1,143 @@
<script setup lang="ts">
import LayoutSelector from './LayoutSelector.vue'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const { t } = useI18n()
const transitionSelectorOptions = computed(() => {
return [
{
label: t('app.transitionNull'),
value: '',
},
{
label: t('app.transitionFadeSlide'),
value: 'fade-slide',
},
{
label: t('app.transitionFadeBottom'),
value: 'fade-bottom',
},
{
label: t('app.transitionFadeScale'),
value: 'fade-scale',
},
{
label: t('app.transitionZoomFade'),
value: 'zoom-fade',
},
{
label: t('app.transitionZoomOut'),
value: 'zoom-out',
},
{
label: t('app.transitionSoft'),
value: 'fade',
},
]
})
const palette = [
'#ffb8b8',
'#d03050',
'#F0A020',
'#fff200',
'#ffda79',
'#18A058',
'#006266',
'#22a6b3',
'#18dcff',
'#2080F0',
'#c56cf0',
'#be2edd',
'#706fd3',
'#4834d4',
'#130f40',
'#4b4b4b',
]
function resetSetting() {
window.$dialog.warning({
title: t('app.resetSettingTitle'),
content: t('app.resetSettingContent'),
positiveText: t('common.confirm'),
negativeText: t('common.cancel'),
onPositiveClick: () => {
appStore.resetAlltheme()
window.$message.success(t('app.resetSettingMeaasge'))
},
})
}
</script>
<template>
<n-drawer v-model:show="appStore.showSetting" :width="360">
<n-drawer-content :title="t('app.systemSetting')" closable>
<n-space vertical>
<n-divider>{{ $t('app.layoutSetting') }}</n-divider>
<LayoutSelector v-model:value="appStore.layoutMode" />
<n-divider>{{ $t('app.themeSetting') }}</n-divider>
<n-space justify="space-between">
{{ $t('app.colorWeak') }}
<n-switch :value="appStore.colorWeak" @update:value="appStore.toggleColorWeak" />
</n-space>
<n-space justify="space-between">
{{ $t('app.blackAndWhite') }}
<n-switch :value="appStore.grayMode" @update:value="appStore.toggleGrayMode" />
</n-space>
<n-space align="center" justify="space-between">
{{ $t('app.themeColor') }}
<n-color-picker
v-model:value="appStore.primaryColor" class="w-10em" :swatches="palette"
@update:value="appStore.setPrimaryColor"
/>
</n-space>
<n-space align="center" justify="space-between">
{{ $t('app.pageTransition') }}
<n-select
v-model:value="appStore.transitionAnimation" class="w-10em"
:options="transitionSelectorOptions" @update:value="appStore.reloadPage"
/>
</n-space>
<n-divider>{{ $t('app.interfaceDisplay') }}</n-divider>
<n-space justify="space-between">
{{ $t('app.logoDisplay') }}
<n-switch v-model:value="appStore.showLogo" />
</n-space>
<n-space justify="space-between">
{{ $t('app.topProgress') }}
<n-switch v-model:value="appStore.showProgress" />
</n-space>
<n-space justify="space-between">
{{ $t('app.multitab') }}
<n-switch v-model:value="appStore.showTabs" />
</n-space>
<n-space justify="space-between">
{{ $t('app.bottomCopyright') }}
<n-switch v-model:value="appStore.showFooter" />
</n-space>
<n-space justify="space-between">
{{ $t('app.breadcrumb') }}
<n-switch v-model:value="appStore.showBreadcrumb" />
</n-space>
<n-space justify="space-between">
{{ $t('app.BreadcrumbIcon') }}
<n-switch v-model:value="appStore.showBreadcrumbIcon" />
</n-space>
<n-space justify="space-between">
{{ $t('app.watermake') }}
<n-switch v-model:value="appStore.showWatermark" />
</n-space>
</n-space>
<template #footer>
<n-button type="error" @click="resetSetting">
{{ $t('app.reset') }}
</n-button>
</template>
</n-drawer-content>
</n-drawer>
</template>

View File

@ -38,15 +38,15 @@ const options = computed(() => {
return routeStore.rowRoutes.filter((item) => {
const conditions = [
t(`route.${String(item.name)}`, item['meta.title'] || item.name)?.includes(searchValue.value),
t(`route.${String(item.name)}`, item.title || item.name)?.includes(searchValue.value),
item.path?.includes(searchValue.value),
]
return conditions.some(condition => !item['meta.hide'] && condition)
return conditions.some(condition => !item.hide && condition)
}).map((item) => {
return {
label: t(`route.${String(item.name)}`, item['meta.title'] || item.name),
label: t(`route.${String(item.name)}`, item.title || item.name),
value: item.path,
icon: item['meta.icon'],
icon: item.icon,
}
})
})

View File

@ -1,158 +0,0 @@
<script setup lang="ts">
import LayoutSelector from '../common/LayoutSelector.vue'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const { t } = useI18n()
const drawerActive = ref(false)
function openSetting() {
drawerActive.value = !drawerActive.value
}
const transitionSelectorOptions = computed(() => {
return [
{
label: t('app.transitionNull'),
value: '',
},
{
label: t('app.transitionFadeSlide'),
value: 'fade-slide',
},
{
label: t('app.transitionFadeBottom'),
value: 'fade-bottom',
},
{
label: t('app.transitionFadeScale'),
value: 'fade-scale',
},
{
label: t('app.transitionZoomFade'),
value: 'zoom-fade',
},
{
label: t('app.transitionZoomOut'),
value: 'zoom-out',
},
{
label: t('app.transitionSoft'),
value: 'fade',
},
]
})
const palette = [
'#ffb8b8',
'#d03050',
'#F0A020',
'#fff200',
'#ffda79',
'#18A058',
'#006266',
'#22a6b3',
'#18dcff',
'#2080F0',
'#c56cf0',
'#be2edd',
'#706fd3',
'#4834d4',
'#130f40',
'#4b4b4b',
]
function resetSetting() {
window.$dialog.warning({
title: t('app.resetSettingTitle'),
content: t('app.resetSettingContent'),
positiveText: t('common.confirm'),
negativeText: t('common.cancel'),
onPositiveClick: () => {
appStore.resetAlltheme()
window.$message.success(t('app.resetSettingMeaasge'))
},
})
}
</script>
<template>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>
<CommonWrapper @click="openSetting">
<div>
<icon-park-outline-setting-two />
<n-drawer v-model:show="drawerActive" :width="360">
<n-drawer-content :title="t('app.systemSetting')" closable>
<n-space vertical>
<n-divider>{{ $t('app.layoutSetting') }}</n-divider>
<LayoutSelector v-model:value="appStore.layoutMode" />
<n-divider>{{ $t('app.themeSetting') }}</n-divider>
<n-space justify="space-between">
{{ $t('app.colorWeak') }}
<n-switch :value="appStore.colorWeak" @update:value="appStore.toggleColorWeak" />
</n-space>
<n-space justify="space-between">
{{ $t('app.blackAndWhite') }}
<n-switch :value="appStore.grayMode" @update:value="appStore.toggleGrayMode" />
</n-space>
<n-space align="center" justify="space-between">
{{ $t('app.themeColor') }}
<n-color-picker
v-model:value="appStore.primaryColor" class="w-10em" :swatches="palette"
@update:value="appStore.setPrimaryColor"
/>
</n-space>
<n-space align="center" justify="space-between">
{{ $t('app.pageTransition') }}
<n-select
v-model:value="appStore.transitionAnimation" class="w-10em"
:options="transitionSelectorOptions" @update:value="appStore.reloadPage"
/>
</n-space>
<n-divider>{{ $t('app.interfaceDisplay') }}</n-divider>
<n-space justify="space-between">
{{ $t('app.logoDisplay') }}
<n-switch v-model:value="appStore.showLogo" />
</n-space>
<n-space justify="space-between">
{{ $t('app.topProgress') }}
<n-switch v-model:value="appStore.showProgress" />
</n-space>
<n-space justify="space-between">
{{ $t('app.multitab') }}
<n-switch v-model:value="appStore.showTabs" />
</n-space>
<n-space justify="space-between">
{{ $t('app.bottomCopyright') }}
<n-switch v-model:value="appStore.showFooter" />
</n-space>
<n-space justify="space-between">
{{ $t('app.breadcrumb') }}
<n-switch v-model:value="appStore.showBreadcrumb" />
</n-space>
<n-space justify="space-between">
{{ $t('app.BreadcrumbIcon') }}
<n-switch v-model:value="appStore.showBreadcrumbIcon" />
</n-space>
<n-space justify="space-between">
{{ $t('app.watermake') }}
<n-switch v-model:value="appStore.showWatermark" />
</n-space>
</n-space>
<template #footer>
<n-button type="error" @click="resetSetting">
{{ $t('app.reset') }}
</n-button>
</template>
</n-drawer-content>
</n-drawer>
</div>
</CommonWrapper>
</template>
<span>{{ $t('app.setting') }}</span>
</n-tooltip>
</template>

View File

@ -1,22 +1,18 @@
/* 侧边栏组件 */
import Logo from './sider/Logo.vue'
import Menu from './sider/Menu.vue'
/* 头部栏组件 */
import Breadcrumb from './header/Breadcrumb.vue'
import CollapaseButton from './header/CollapaseButton.vue'
import FullScreen from './header/FullScreen.vue'
import Setting from './header/Setting.vue'
import Notices from './header/Notices.vue'
import UserCenter from './header/UserCenter.vue'
import Search from './header/Search.vue'
/* 标签栏组件 */
import TabBar from './tab/TabBar.vue'
/* 其他组件 */
// 返回顶部
import BackTop from './common/BackTop.vue'
import Setting from './common/Setting.vue'
import SettingDrawer from './common/SettingDrawer.vue'
export {
Breadcrumb,
@ -25,6 +21,7 @@ export {
Logo,
FullScreen,
Setting,
SettingDrawer,
Notices,
UserCenter,
Search,

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import type { MenuInst } from 'naive-ui'
import { useAppStore } from '@/store'
import { useRouteStore } from '@/store/route'
import { useAppStore, useRouteStore } from '@/store'
const route = useRoute()
const appStore = useAppStore()
@ -27,5 +26,3 @@ watch(
:value="routesStore.activeMenu"
/>
</template>
<style scoped></style>

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import leftMenu from './leftMenu.layout.vue'
import topMenu from './topMenu.layout.vue'
import { SettingDrawer } from './components'
import { useAppStore } from '@/store/app'
const appStore = useAppStore()
@ -11,5 +12,6 @@ const layoutMap = {
</script>
<template>
<SettingDrawer />
<component :is="layoutMap[appStore.layoutMode]" />
</template>

View File

@ -6,7 +6,6 @@ export const routes: RouteRecordRaw[] = [
path: '/',
name: 'root',
redirect: '/appRoot',
// component: () => import('@/layouts/index'),
children: [
],
},

View File

@ -1,407 +1,407 @@
export const staticRoutes: AppRoute.RowRoute[] = [
{
'name': 'dashboard',
'path': '/dashboard',
'meta.title': '仪表盘',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:analysis',
'meta.menuType': 'dir',
'componentPath': null,
'id': 1,
'pid': null,
name: 'dashboard',
path: '/dashboard',
title: '仪表盘',
requiresAuth: true,
icon: 'icon-park-outline:analysis',
menuType: 'dir',
componentPath: null,
id: 1,
pid: null,
},
{
'name': 'workbench',
'path': '/dashboard/workbench',
'meta.title': '工作台',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:alarm',
'meta.pinTab': true,
'meta.menuType': 'page',
'componentPath': '/dashboard/workbench/index.vue',
'id': 2,
'pid': 1,
name: 'workbench',
path: '/dashboard/workbench',
title: '工作台',
requiresAuth: true,
icon: 'icon-park-outline:alarm',
pinTab: true,
menuType: 'page',
componentPath: '/dashboard/workbench/index.vue',
id: 2,
pid: 1,
},
{
'name': 'monitor',
'path': '/dashboard/monitor',
'meta.title': '监控页',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:anchor',
'meta.menuType': 'page',
'componentPath': '/dashboard/monitor/index.vue',
'id': 3,
'pid': 1,
name: 'monitor',
path: '/dashboard/monitor',
title: '监控页',
requiresAuth: true,
icon: 'icon-park-outline:anchor',
menuType: 'page',
componentPath: '/dashboard/monitor/index.vue',
id: 3,
pid: 1,
},
{
'name': 'test',
'path': '/test',
'meta.title': '多级菜单演示',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list',
'meta.menuType': 'dir',
'componentPath': null,
'id': 4,
'pid': null,
name: 'test',
path: '/test',
title: '多级菜单演示',
requiresAuth: true,
icon: 'icon-park-outline:list',
menuType: 'dir',
componentPath: null,
id: 4,
pid: null,
},
{
'name': 'test2',
'path': '/test/test2',
'meta.title': '多级菜单子页',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list',
'meta.menuType': 'page',
'componentPath': '/test/test2/index.vue',
'id': 6,
'pid': 4,
name: 'test2',
path: '/test/test2',
title: '多级菜单子页',
requiresAuth: true,
icon: 'icon-park-outline:list',
menuType: 'page',
componentPath: '/test/test2/index.vue',
id: 6,
pid: 4,
},
{
'name': 'test2Detail',
'path': '/test/test2/detail',
'meta.title': '多级菜单的详情页',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list',
'meta.hide': true,
'meta.activeMenu': '/test/test2',
'meta.menuType': 'page',
'componentPath': '/test/test2/detail/index.vue',
'id': 7,
'pid': 4,
name: 'test2Detail',
path: '/test/test2/detail',
title: '多级菜单的详情页',
requiresAuth: true,
icon: 'icon-park-outline:list',
hide: true,
activeMenu: '/test/test2',
menuType: 'page',
componentPath: '/test/test2/detail/index.vue',
id: 7,
pid: 4,
},
{
'name': 'test3',
'path': '/test/test3',
'meta.title': '多级菜单',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list',
'meta.menuType': 'dir',
'componentPath': null,
'id': 8,
'pid': 4,
name: 'test3',
path: '/test/test3',
title: '多级菜单',
requiresAuth: true,
icon: 'icon-park-outline:list',
menuType: 'dir',
componentPath: null,
id: 8,
pid: 4,
},
{
'name': 'test4',
'path': '/test/test3/test4',
'meta.title': '多级菜单3-1',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list',
'componentPath': '/test/test3/test4/index.vue',
'id': 9,
'pid': 8,
name: 'test4',
path: '/test/test3/test4',
title: '多级菜单3-1',
requiresAuth: true,
icon: 'icon-park-outline:list',
componentPath: '/test/test3/test4/index.vue',
id: 9,
pid: 8,
},
{
'name': 'list',
'path': '/list',
'meta.title': '列表页',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list-two',
'meta.menuType': 'dir',
'componentPath': null,
'id': 10,
'pid': null,
name: 'list',
path: '/list',
title: '列表页',
requiresAuth: true,
icon: 'icon-park-outline:list-two',
menuType: 'dir',
componentPath: null,
id: 10,
pid: null,
},
{
'name': 'commonList',
'path': '/list/commonList',
'meta.title': '常用列表',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list-view',
'componentPath': '/list/commonList/index.vue',
'id': 11,
'pid': 10,
name: 'commonList',
path: '/list/commonList',
title: '常用列表',
requiresAuth: true,
icon: 'icon-park-outline:list-view',
componentPath: '/list/commonList/index.vue',
id: 11,
pid: 10,
},
{
'name': 'cardList',
'path': '/list/cardList',
'meta.title': '卡片列表',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:view-grid-list',
'componentPath': '/list/cardList/index.vue',
'id': 12,
'pid': 10,
name: 'cardList',
path: '/list/cardList',
title: '卡片列表',
requiresAuth: true,
icon: 'icon-park-outline:view-grid-list',
componentPath: '/list/cardList/index.vue',
id: 12,
pid: 10,
},
{
'name': 'demo',
'path': '/demo',
'meta.title': '功能示例',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:application-one',
'meta.menuType': 'dir',
'componentPath': null,
'id': 13,
'pid': null,
name: 'demo',
path: '/demo',
title: '功能示例',
requiresAuth: true,
icon: 'icon-park-outline:application-one',
menuType: 'dir',
componentPath: null,
id: 13,
pid: null,
},
{
'name': 'fetch',
'path': '/demo/fetch',
'meta.title': '请求示例',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:international',
'componentPath': '/demo/fetch/index.vue',
'id': 5,
'pid': 13,
name: 'fetch',
path: '/demo/fetch',
title: '请求示例',
requiresAuth: true,
icon: 'icon-park-outline:international',
componentPath: '/demo/fetch/index.vue',
id: 5,
pid: 13,
},
{
'name': 'echarts',
'path': '/demo/echarts',
'meta.title': 'ECharts',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:chart-proportion',
'componentPath': '/demo/echarts/index.vue',
'id': 15,
'pid': 13,
name: 'echarts',
path: '/demo/echarts',
title: 'ECharts',
requiresAuth: true,
icon: 'icon-park-outline:chart-proportion',
componentPath: '/demo/echarts/index.vue',
id: 15,
pid: 13,
},
{
'name': 'map',
'path': '/demo/map',
'meta.title': '地图',
'meta.requiresAuth': true,
'meta.icon': 'carbon:map',
'meta.keepAlive': true,
'componentPath': '/demo/map/index.vue',
'id': 17,
'pid': 13,
name: 'map',
path: '/demo/map',
title: '地图',
requiresAuth: true,
icon: 'carbon:map',
keepAlive: true,
componentPath: '/demo/map/index.vue',
id: 17,
pid: 13,
},
{
'name': 'editor',
'path': '/demo/editor',
'meta.title': '编辑器',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:editor',
'meta.menuType': 'dir',
'componentPath': null,
'id': 18,
'pid': 13,
name: 'editor',
path: '/demo/editor',
title: '编辑器',
requiresAuth: true,
icon: 'icon-park-outline:editor',
menuType: 'dir',
componentPath: null,
id: 18,
pid: 13,
},
{
'name': 'editorMd',
'path': '/demo/editor/md',
'meta.title': 'MarkDown',
'meta.requiresAuth': true,
'meta.icon': 'ri:markdown-line',
'componentPath': '/demo/editor/md/index.vue',
'id': 19,
'pid': 18,
name: 'editorMd',
path: '/demo/editor/md',
title: 'MarkDown',
requiresAuth: true,
icon: 'ri:markdown-line',
componentPath: '/demo/editor/md/index.vue',
id: 19,
pid: 18,
},
{
'name': 'editorRich',
'path': '/demo/editor/rich',
'meta.title': '富文本',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:edit-one',
'componentPath': '/demo/editor/rich/index.vue',
'id': 20,
'pid': 18,
name: 'editorRich',
path: '/demo/editor/rich',
title: '富文本',
requiresAuth: true,
icon: 'icon-park-outline:edit-one',
componentPath: '/demo/editor/rich/index.vue',
id: 20,
pid: 18,
},
{
'name': 'clipboard',
'path': '/demo/clipboard',
'meta.title': '剪贴板',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:clipboard',
'componentPath': '/demo/clipboard/index.vue',
'id': 21,
'pid': 13,
name: 'clipboard',
path: '/demo/clipboard',
title: '剪贴板',
requiresAuth: true,
icon: 'icon-park-outline:clipboard',
componentPath: '/demo/clipboard/index.vue',
id: 21,
pid: 13,
},
{
'name': 'icons',
'path': '/demo/icons',
'meta.title': '图标',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:winking-face-with-open-eyes',
'componentPath': '/demo/icons/index.vue',
'id': 22,
'pid': 13,
name: 'icons',
path: '/demo/icons',
title: '图标',
requiresAuth: true,
icon: 'icon-park-outline:winking-face-with-open-eyes',
componentPath: '/demo/icons/index.vue',
id: 22,
pid: 13,
},
{
'name': 'QRCode',
'path': '/demo/QRCode',
'meta.title': '二维码',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:two-dimensional-code',
'componentPath': '/demo/QRCode/index.vue',
'id': 23,
'pid': 13,
name: 'QRCode',
path: '/demo/QRCode',
title: '二维码',
requiresAuth: true,
icon: 'icon-park-outline:two-dimensional-code',
componentPath: '/demo/QRCode/index.vue',
id: 23,
pid: 13,
},
{
'name': 'docments',
'path': '/docments',
'meta.title': '外链文档',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:file-doc',
'meta.menuType': 'dir',
'componentPath': null,
'id': 24,
'pid': null,
name: 'docments',
path: '/docments',
title: '外链文档',
requiresAuth: true,
icon: 'icon-park-outline:file-doc',
menuType: 'dir',
componentPath: null,
id: 24,
pid: null,
},
{
'name': 'docmentsVue',
'path': '/docments/vue',
'meta.title': 'Vue',
'meta.requiresAuth': true,
'meta.icon': 'logos:vue',
'componentPath': '/docments/vue/index.vue',
'id': 25,
'pid': 24,
name: 'docmentsVue',
path: '/docments/vue',
title: 'Vue',
requiresAuth: true,
icon: 'logos:vue',
componentPath: '/docments/vue/index.vue',
id: 25,
pid: 24,
},
{
'name': 'docmentsVite',
'path': '/docments/vite',
'meta.title': 'Vite',
'meta.requiresAuth': true,
'meta.icon': 'logos:vitejs',
'componentPath': '/docments/vite/index.vue',
'id': 26,
'pid': 24,
name: 'docmentsVite',
path: '/docments/vite',
title: 'Vite',
requiresAuth: true,
icon: 'logos:vitejs',
componentPath: '/docments/vite/index.vue',
id: 26,
pid: 24,
},
{
'name': 'docmentsVueuse',
'path': '/docments/vueuse',
'meta.title': 'VueUse外链',
'meta.requiresAuth': true,
'meta.icon': 'logos:vueuse',
'meta.href': 'https://vueuse.org/guide/',
'componentPath': 'null',
'id': 27,
'pid': 24,
name: 'docmentsVueuse',
path: '/docments/vueuse',
title: 'VueUse外链',
requiresAuth: true,
icon: 'logos:vueuse',
href: 'https://vueuse.org/guide/',
componentPath: 'null',
id: 27,
pid: 24,
},
{
'name': 'permission',
'path': '/permission',
'meta.title': '权限',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:people-safe',
'meta.menuType': 'dir',
'componentPath': null,
'id': 28,
'pid': null,
name: 'permission',
path: '/permission',
title: '权限',
requiresAuth: true,
icon: 'icon-park-outline:people-safe',
menuType: 'dir',
componentPath: null,
id: 28,
pid: null,
},
{
'name': 'permissionDemo',
'path': '/permission/permission',
'meta.title': '权限示例',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:right-user',
'componentPath': '/permission/permission/index.vue',
'id': 29,
'pid': 28,
name: 'permissionDemo',
path: '/permission/permission',
title: '权限示例',
requiresAuth: true,
icon: 'icon-park-outline:right-user',
componentPath: '/permission/permission/index.vue',
id: 29,
pid: 28,
},
{
'name': 'justSuper',
'path': '/permission/justSuper',
'meta.title': 'super可见',
'meta.requiresAuth': true,
'meta.roles': [
name: 'justSuper',
path: '/permission/justSuper',
title: 'super可见',
requiresAuth: true,
roles: [
'super',
],
'meta.icon': 'icon-park-outline:wrong-user',
'componentPath': '/permission/justSuper/index.vue',
'id': 30,
'pid': 28,
icon: 'icon-park-outline:wrong-user',
componentPath: '/permission/justSuper/index.vue',
id: 30,
pid: 28,
},
{
'name': 'error',
'path': '/error',
'meta.title': '异常页',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:error-computer',
'meta.menuType': 'dir',
'componentPath': null,
'id': 31,
'pid': null,
name: 'error',
path: '/error',
title: '异常页',
requiresAuth: true,
icon: 'icon-park-outline:error-computer',
menuType: 'dir',
componentPath: null,
id: 31,
pid: null,
},
{
'name': 'demo403',
'path': '/error/403',
'meta.title': '403',
'meta.requiresAuth': true,
'meta.icon': 'carbon:error',
'meta.order': 3,
'componentPath': '/error/403/index.vue',
'id': 32,
'pid': 31,
name: 'demo403',
path: '/error/403',
title: '403',
requiresAuth: true,
icon: 'carbon:error',
order: 3,
componentPath: '/error/403/index.vue',
id: 32,
pid: 31,
},
{
'name': 'demo404',
'path': '/error/404',
'meta.title': '404',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:error',
'meta.order': 2,
'componentPath': '/error/404/index.vue',
'id': 33,
'pid': 31,
name: 'demo404',
path: '/error/404',
title: '404',
requiresAuth: true,
icon: 'icon-park-outline:error',
order: 2,
componentPath: '/error/404/index.vue',
id: 33,
pid: 31,
},
{
'name': 'demo500',
'path': '/error/500',
'meta.title': '500',
'meta.requiresAuth': true,
'meta.icon': 'carbon:data-error',
'meta.order': 1,
'componentPath': '/error/500/index.vue',
'id': 34,
'pid': 31,
name: 'demo500',
path: '/error/500',
title: '500',
requiresAuth: true,
icon: 'carbon:data-error',
order: 1,
componentPath: '/error/500/index.vue',
id: 34,
pid: 31,
},
{
'name': 'setting',
'path': '/setting',
'meta.title': '系统设置',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:setting',
'meta.menuType': 'dir',
'componentPath': null,
'id': 35,
'pid': null,
name: 'setting',
path: '/setting',
title: '系统设置',
requiresAuth: true,
icon: 'icon-park-outline:setting',
menuType: 'dir',
componentPath: null,
id: 35,
pid: null,
},
{
'name': 'accountSetting',
'path': '/setting/account',
'meta.title': '用户设置',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:every-user',
'componentPath': '/setting/account/index.vue',
'id': 36,
'pid': 35,
name: 'accountSetting',
path: '/setting/account',
title: '用户设置',
requiresAuth: true,
icon: 'icon-park-outline:every-user',
componentPath: '/setting/account/index.vue',
id: 36,
pid: 35,
},
{
'name': 'dictionarySetting',
'path': '/setting/dictionary',
'meta.title': '字典设置',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:book-one',
'componentPath': '/setting/dictionary/index.vue',
'id': 37,
'pid': 35,
name: 'dictionarySetting',
path: '/setting/dictionary',
title: '字典设置',
requiresAuth: true,
icon: 'icon-park-outline:book-one',
componentPath: '/setting/dictionary/index.vue',
id: 37,
pid: 35,
},
{
'name': 'menuSetting',
'path': '/setting/menu',
'meta.title': '菜单设置',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:application-menu',
'componentPath': '/setting/menu/index.vue',
'id': 38,
'pid': 35,
name: 'menuSetting',
path: '/setting/menu',
title: '菜单设置',
requiresAuth: true,
icon: 'icon-park-outline:application-menu',
componentPath: '/setting/menu/index.vue',
id: 38,
pid: 35,
},
{
'name': 'userCenter',
'path': '/userCenter',
'meta.title': '个人中心',
'meta.requiresAuth': true,
'meta.icon': 'carbon:user-avatar-filled-alt',
'componentPath': '/userCenter/index.vue',
'id': 39,
'pid': null,
name: 'userCenter',
path: '/userCenter',
title: '个人中心',
requiresAuth: true,
icon: 'carbon:user-avatar-filled-alt',
componentPath: '/userCenter/index.vue',
id: 39,
pid: null,
},
{
'name': 'about',
'path': '/about',
'meta.title': '关于',
'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:info',
'componentPath': '/about/index.vue',
'id': 40,
'pid': null,
name: 'about',
path: '/about',
title: '关于',
requiresAuth: true,
icon: 'icon-park-outline:info',
componentPath: '/about/index.vue',
id: 40,
pid: null,
},
]

View File

@ -33,6 +33,7 @@ export const useAppStore = defineStore('app-store', {
showBreadcrumb: true,
showBreadcrumbIcon: true,
showWatermark: false,
showSetting: false,
transitionAnimation: 'fade-slide' as TransitionAnimation,
layoutMode: 'leftMenu' as LayoutMode,
}

View File

@ -1,4 +1,4 @@
import { useRouteStore } from './route'
import { useRouteStore } from './router'
import { useTabStore } from './tab'
import { fetchLogin } from '@/service'
import { router } from '@/router'
@ -33,7 +33,7 @@ export const useAuthStore = defineStore('auth-store', {
// 清空标签栏数据
const tabStore = useTabStore()
tabStore.clearAllTabs()
// 重当前存储库
// 重当前存储库
this.$reset()
// 重定向到登录页
if (route.meta.requiresAuth) {
@ -59,14 +59,14 @@ export const useAuthStore = defineStore('auth-store', {
return
// 处理登录信息
await this.handleAfterLogin(data)
await this.handleLoginInfo(data)
}
catch (e) {
}
},
/* 登录后的处理函数 */
async handleAfterLogin(data: Api.Login.Info) {
/* 处理登录返回的数据 */
async handleLoginInfo(data: Api.Login.Info) {
// 将token和userInfo保存下来
local.set('userInfo', data)
local.set('accessToken', data.accessToken)

View File

@ -3,7 +3,7 @@ import piniaPluginPersist from 'pinia-plugin-persist'
export * from './app/index'
export * from './auth'
export * from './route'
export * from './router'
export * from './tab'
// 安装pinia全局状态库

View File

@ -1,207 +0,0 @@
import type { MenuOption } from 'naive-ui'
import { RouterLink } from 'vue-router'
import { h } from 'vue'
import { clone, construct, min } from 'radash'
import type { RouteRecordRaw } from 'vue-router'
import { $t, arrayToTree, local, renderIcon } from '@/utils'
import { router } from '@/router'
import { fetchUserRoutes } from '@/service'
import { staticRoutes } from '@/router/routes.static'
import { usePermission } from '@/hooks'
import Layout from '@/layouts/index.vue'
import { useAuthStore } from '@/store/auth'
interface RoutesStatus {
isInitAuthRoute: boolean
menus: AppRoute.Route[]
rowRoutes: AppRoute.RowRoute[]
activeMenu: string | null
cacheRoutes: string[]
}
export const useRouteStore = defineStore('route-store', {
state: (): RoutesStatus => {
return {
isInitAuthRoute: false,
menus: [],
rowRoutes: [],
activeMenu: null,
cacheRoutes: [],
}
},
actions: {
resetRouteStore() {
this.resetRoutes()
this.$reset()
},
resetRoutes() {
router.removeRoute('appRoot')
},
// set the currently highlighted menu key
setActiveMenu(key: string) {
this.activeMenu = key
},
/* 生成侧边菜单的数据 */
createMenus(userRoutes: AppRoute.RowRoute[]) {
const resultMenus = clone(userRoutes).map(i => construct(i)) as AppRoute.Route[]
// filter menus that do not need to be displayed
const visibleMenus = resultMenus.filter(route => !route.meta.hide)
// generate side menu
this.menus = arrayToTree(this.transformAuthRoutesToMenus(visibleMenus))
},
// render the returned routing table as a sidebar
transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
const { hasPermission } = usePermission()
// Filter out side menus without permission
return userRoutes.filter(i => hasPermission(i.meta.roles))
// Sort the menu according to the order size
.sort((a, b) => {
if (a.meta && a.meta.order && b.meta && b.meta.order)
return a.meta.order - b.meta.order
else if (a.meta && a.meta.order)
return -1
else if (b.meta && b.meta.order)
return 1
else return 0
})
// Convert to side menu data structure
.map((item) => {
const target: MenuOption = {
id: item.id,
pid: item.pid,
label:
(!item.meta.menuType || item.meta.menuType === 'page')
? () =>
h(
RouterLink,
{
to: {
path: item.path,
},
},
{ default: () => $t(`route.${String(item.name)}`, item.meta.title) },
)
: () => $t(`route.${String(item.name)}`, item.meta.title),
key: item.path,
icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
}
return target
})
},
createRoutes(routes: AppRoute.RowRoute[]) {
const { hasPermission } = usePermission()
// Structure the meta field
let resultRouter = clone(routes).map(i => construct(i)) as AppRoute.Route[]
// Route permission filtering
resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles))
// Generate an array of route names that need to be kept alive
this.cacheRoutes = resultRouter.filter((i) => {
return i.meta.keepAlive
})
.map(i => i.name)
// Generate routes, no need to import files for those with redirect
const modules = import.meta.glob('@/views/**/*.vue')
resultRouter = resultRouter.map((item: AppRoute.Route) => {
if (item.componentPath && !item.redirect)
item.component = modules[`/src/views${item.componentPath}`]
return item
})
// Generate route tree
resultRouter = arrayToTree(resultRouter) as AppRoute.Route[]
const appRootRoute: RouteRecordRaw = {
path: '/appRoot',
name: 'appRoot',
redirect: import.meta.env.VITE_HOME_PATH,
component: Layout,
meta: {
title: '',
icon: 'icon-park-outline:home',
},
children: [],
}
// Set the correct redirect path for the route
this.setRedirect(resultRouter)
// Insert the processed route into the root route
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
router.addRoute(appRootRoute)
},
setRedirect(routes: AppRoute.Route[]) {
routes.forEach((route) => {
if (route.children) {
if (!route.redirect) {
// Filter out a collection of child elements that are not hidden
const visibleChilds = route.children.filter(child => !child.meta.hide)
// Redirect page to the path of the first child element by default
let target = visibleChilds[0]
// Filter out pages with the order attribute
const orderChilds = visibleChilds.filter(child => child.meta.order)
if (orderChilds.length > 0)
target = min(orderChilds, i => i.meta.order!) as AppRoute.Route
route.redirect = target.path
}
this.setRedirect(route.children)
}
})
},
async initRouteInfo() {
if (import.meta.env.VITE_AUTH_ROUTE_MODE === 'dynamic') {
const userInfo = local.get('userInfo')
if (!userInfo || !userInfo.id) {
const authStore = useAuthStore()
authStore.resetAuthStore()
return
}
// Get user's route
const { data } = await fetchUserRoutes({
id: userInfo.id,
})
if (!data)
return
return data
}
else {
this.rowRoutes = staticRoutes
return staticRoutes
}
},
async initAuthRoute() {
this.isInitAuthRoute = false
// Initialize route information
const rowRoutes = await this.initRouteInfo()
if (!rowRoutes) {
window.$message.error($t(`app.getRouteError`))
return
}
this.rowRoutes = rowRoutes
// Generate actual route and insert
this.createRoutes(rowRoutes)
// Generate side menu
this.createMenus(rowRoutes)
this.isInitAuthRoute = true
},
},
})

142
src/store/router/helper.ts Normal file
View File

@ -0,0 +1,142 @@
import { clone, min, omit, pick } from 'radash'
import type { RouteRecordRaw } from 'vue-router'
import type { MenuOption } from 'naive-ui'
import { RouterLink } from 'vue-router'
import Layout from '@/layouts/index.vue'
import { usePermission } from '@/hooks'
import { $t, arrayToTree, renderIcon } from '@/utils'
const metaFields: AppRoute.MetaKeys[]
= ['title', 'icon', 'requiresAuth', 'roles', 'keepAlive', 'hide', 'order', 'href', 'activeMenu', 'withoutTab', 'pinTab', 'menuType']
function standardizedRoutes(route: AppRoute.RowRoute[]) {
return clone(route).map((i) => {
const route = omit(i, metaFields)
Reflect.set(route, 'meta', pick(i, metaFields))
return route
}) as AppRoute.Route[]
}
export function createRoutes(routes: AppRoute.RowRoute[]) {
const { hasPermission } = usePermission()
// Structure the meta field
let resultRouter = standardizedRoutes(routes)
// Route permission filtering
resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles))
// Generate routes, no need to import files for those with redirect
const modules = import.meta.glob('@/views/**/*.vue')
resultRouter = resultRouter.map((item: AppRoute.Route) => {
if (item.componentPath && !item.redirect)
item.component = modules[`/src/views${item.componentPath}`]
return item
})
// Generate route tree
resultRouter = arrayToTree(resultRouter) as AppRoute.Route[]
const appRootRoute: RouteRecordRaw = {
path: '/appRoot',
name: 'appRoot',
redirect: import.meta.env.VITE_HOME_PATH,
component: Layout,
meta: {
title: '',
icon: 'icon-park-outline:home',
},
children: [],
}
// Set the correct redirect path for the route
setRedirect(resultRouter)
// Insert the processed route into the root route
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
return appRootRoute
}
// Generate an array of route names that need to be kept alive
export function generateCacheRoutes(routes: AppRoute.RowRoute[]) {
return routes
.filter(i => i.keepAlive)
.map(i => i.name)
}
function setRedirect(routes: AppRoute.Route[]) {
routes.forEach((route) => {
if (route.children) {
if (!route.redirect) {
// Filter out a collection of child elements that are not hidden
const visibleChilds = route.children.filter(child => !child.meta.hide)
// Redirect page to the path of the first child element by default
let target = visibleChilds[0]
// Filter out pages with the order attribute
const orderChilds = visibleChilds.filter(child => child.meta.order)
if (orderChilds.length > 0)
target = min(orderChilds, i => i.meta.order!) as AppRoute.Route
route.redirect = target.path
}
setRedirect(route.children)
}
})
}
/* 生成侧边菜单的数据 */
export function createMenus(userRoutes: AppRoute.RowRoute[]) {
const resultMenus = standardizedRoutes(userRoutes)
// filter menus that do not need to be displayed
const visibleMenus = resultMenus.filter(route => !route.meta.hide)
// generate side menu
return arrayToTree(transformAuthRoutesToMenus(visibleMenus))
}
// render the returned routing table as a sidebar
function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) {
const { hasPermission } = usePermission()
// Filter out side menus without permission
return userRoutes.filter(i => hasPermission(i.meta.roles))
// Sort the menu according to the order size
.sort((a, b) => {
if (a.meta && a.meta.order && b.meta && b.meta.order)
return a.meta.order - b.meta.order
else if (a.meta && a.meta.order)
return -1
else if (b.meta && b.meta.order)
return 1
else return 0
})
// Convert to side menu data structure
.map((item) => {
const target: MenuOption = {
id: item.id,
pid: item.pid,
label:
(!item.meta.menuType || item.meta.menuType === 'page')
? () =>
h(
RouterLink,
{
to: {
path: item.path,
},
},
{ default: () => $t(`route.${String(item.name)}`, item.meta.title) },
)
: () => $t(`route.${String(item.name)}`, item.meta.title),
key: item.path,
icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
}
return target
})
}

84
src/store/router/index.ts Normal file
View File

@ -0,0 +1,84 @@
import { createMenus, createRoutes } from './helper'
import { $t, local } from '@/utils'
import { router } from '@/router'
import { fetchUserRoutes } from '@/service'
import { staticRoutes } from '@/router/routes.static'
import { useAuthStore } from '@/store/auth'
interface RoutesStatus {
isInitAuthRoute: boolean
menus: AppRoute.Route[]
rowRoutes: AppRoute.RowRoute[]
activeMenu: string | null
cacheRoutes: string[]
}
export const useRouteStore = defineStore('route-store', {
state: (): RoutesStatus => {
return {
isInitAuthRoute: false,
menus: [],
rowRoutes: [],
activeMenu: null,
cacheRoutes: [],
}
},
actions: {
resetRouteStore() {
this.resetRoutes()
this.$reset()
},
resetRoutes() {
router.removeRoute('appRoot')
},
// set the currently highlighted menu key
setActiveMenu(key: string) {
this.activeMenu = key
},
async initRouteInfo() {
if (import.meta.env.VITE_AUTH_ROUTE_MODE === 'dynamic') {
const userInfo = local.get('userInfo')
if (!userInfo || !userInfo.id) {
const authStore = useAuthStore()
authStore.resetAuthStore()
return
}
// Get user's route
const { data } = await fetchUserRoutes({
id: userInfo.id,
})
if (!data)
return
return data
}
else {
this.rowRoutes = staticRoutes
return staticRoutes
}
},
async initAuthRoute() {
this.isInitAuthRoute = false
// Initialize route information
const rowRoutes = await this.initRouteInfo()
if (!rowRoutes) {
window.$message.error($t(`app.getRouteError`))
return
}
this.rowRoutes = rowRoutes
// Generate actual route and insert
const routes = createRoutes(rowRoutes)
router.addRoute(routes)
// Generate side menu
this.menus = createMenus(rowRoutes)
this.isInitAuthRoute = true
},
},
})

View File

@ -29,6 +29,8 @@ declare namespace AppRoute {
menuType?: MenuType
}
type MetaKeys = keyof RouteMeta
interface baseRoute {
/** 路由名称(路由唯一标识) */
name: string
@ -45,9 +47,7 @@ declare namespace AppRoute {
}
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
type RowRoute = {
[K in keyof RouteMeta as `meta.${K}`]?: RouteMeta[K]
} & baseRoute
type RowRoute = RouteMeta & baseRoute
/**
*

View File

@ -1,20 +1,50 @@
import { arrayToTree as _arrayToTree } from 'performant-array-to-tree'
import { omit } from 'radash'
// import { arrayToTree as _arrayToTree } from 'performant-array-to-tree'
// import { omit } from 'radash'
export function arrayToTree(data: any) {
const rowTree = _arrayToTree(data, {
parentId: 'pid',
dataField: null,
// export function arrayToTree(data: any) {
// const rowTree = _arrayToTree(data, {
// parentId: 'pid',
// dataField: null,
// })
// const transform = (node: any) => {
// if (node.children.length > 0) {
// return ({
// ...node,
// children: node.children.map(transform),
// })
// }
// return omit(node, ['children'])
// }
// return rowTree.map(transform)
// }
// interface ArrayItem extends Record<PropertyKey, any> {
// id: number
// pid: number | null
// }
// interface TreeItem extends ArrayItem {
// children?: TreeItem[]
// }
export function arrayToTree(arr: any[]) {
const res: any = []
const map = new Map()
arr.forEach((item) => {
map.set(item.id, item)
})
const transform = (node: any) => {
if (node.children.length > 0) {
return ({
...node,
children: node.children.map(transform),
})
arr.forEach((item) => {
const parent = item.pid && map.get(item.pid)
if (parent) {
if (parent?.children)
parent.children.push(item)
else
parent.children = [item]
}
return omit(node, ['children'])
}
return rowTree.map(transform)
else {
res.push(item)
}
})
return res
}

View File

@ -25,22 +25,22 @@ const { bool: modalVisible, setTrue: showModal, setFalse: hiddenModal } = useBoo
const { bool: submitLoading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
const formModel = useDefault<AppRoute.RowRoute>({
'name': '',
'path': '',
'id': -1,
'pid': null,
'meta.title': '',
'meta.icon': '',
'meta.requiresAuth': true,
'meta.roles': [],
'meta.keepAlive': false,
'meta.hide': false,
'meta.order': undefined,
'meta.href': undefined,
'meta.activeMenu': undefined,
'meta.withoutTab': true,
'meta.pinTab': false,
'meta.menuType': 'page',
name: '',
path: '',
id: -1,
pid: null,
title: '',
icon: '',
requiresAuth: true,
roles: [],
keepAlive: false,
hide: false,
order: undefined,
href: undefined,
activeMenu: undefined,
withoutTab: true,
pinTab: false,
menuType: 'page',
})
type ModalType = 'add' | 'view' | 'edit'
@ -130,12 +130,12 @@ function filterDirectory(node: any[]) {
Reflect.deleteProperty(item, 'children')
}
return (item['meta.menuType'] === 'dir')
return (item.menuType === 'dir')
})
}
const rules = {
'name': {
name: {
required: true,
// message: '',
validator(rule: FormItemRule, value: string) {
@ -149,22 +149,22 @@ const rules = {
},
trigger: 'blur',
},
'path': {
path: {
required: true,
message: '请输入菜单路径',
trigger: 'blur',
},
'componentPath': {
componentPath: {
required: true,
message: '请输入组件路径',
trigger: 'blur',
},
'meta.title': {
title: {
required: true,
message: '请输入菜单标题',
trigger: 'blur',
},
'meta.href': {
href: {
validator(rule: FormItemRule, value: string) {
if (!new RegExp(Regex.Url).test(value))
return new Error('请输入正确的URL地址')
@ -204,20 +204,20 @@ async function getRoleList() {
</template>
<n-tree-select
v-model:value="formModel.pid" filterable clearable :options="dirTreeOptions" key-field="id"
label-field="meta.title" children-field="children" placeholder="请选择父级目录"
label-field="title" children-field="children" placeholder="请选择父级目录"
/>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="菜单名称" path="name">
<n-input v-model:value="formModel.name" placeholder="Eg: system" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="标题" path="meta.title">
<n-input v-model:value="formModel['meta.title']" placeholder="Eg: My-System" />
<n-form-item-grid-item :span="1" label="标题" path="title">
<n-input v-model:value="formModel.title" placeholder="Eg: My-System" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="2" label="路由路径" path="path">
<n-input v-model:value="formModel.path" placeholder="Eg: /system/user" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="菜单类型" path="meta.menuType">
<n-radio-group v-model:value="formModel['meta.menuType']" name="radiogroup">
<n-form-item-grid-item :span="1" label="菜单类型" path="menuType">
<n-radio-group v-model:value="formModel.menuType" name="radiogroup">
<n-space>
<n-radio value="dir">
目录
@ -228,64 +228,64 @@ async function getRoleList() {
</n-space>
</n-radio-group>
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="图标" path="meta.icon">
<icon-select v-model:value="formModel['meta.icon']" :disabled="modalType === 'view'" />
<n-form-item-grid-item :span="1" label="图标" path="icon">
<icon-select v-model:value="formModel.icon" :disabled="modalType === 'view'" />
</n-form-item-grid-item>
<n-form-item-grid-item v-if="formModel['meta.menuType'] === 'page'" :span="2" label="组件路径" path="componentPath">
<n-form-item-grid-item v-if="formModel.menuType === 'page'" :span="2" label="组件路径" path="componentPath">
<n-input v-model:value="formModel.componentPath" placeholder="Eg: /system/user/index.vue" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" path="meta.order">
<n-form-item-grid-item :span="1" path="order">
<template #label>
菜单排序
<HelpInfo message="数字越小,同级中越靠前" />
</template>
<n-input-number v-model:value="formModel['meta.order']" />
<n-input-number v-model:value="formModel.order" />
</n-form-item-grid-item>
<n-form-item-grid-item v-if="formModel['meta.menuType'] === 'page'" :span="1" path="meta.href">
<n-form-item-grid-item v-if="formModel.menuType === 'page'" :span="1" path="href">
<template #label>
外链页面
<HelpInfo message="填写后,点击菜单将跳转到该地址,组件路径任意填写" />
</template>
<n-input v-model:value="formModel['meta.href']" placeholder="Eg: https://example.com" />
<n-input v-model:value="formModel.href" placeholder="Eg: https://example.com" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="登录访问" path="meta.requiresAuth">
<n-switch v-model:value="formModel['meta.requiresAuth']" />
<n-form-item-grid-item :span="1" label="登录访问" path="requiresAuth">
<n-switch v-model:value="formModel.requiresAuth" />
</n-form-item-grid-item>
<n-form-item-grid-item
v-if="formModel['meta.menuType'] === 'page'" :span="1" label="页面缓存"
path="meta.keepAlive"
v-if="formModel.menuType === 'page'" :span="1" label="页面缓存"
path="keepAlive"
>
<n-switch v-model:value="formModel['meta.keepAlive']" />
<n-switch v-model:value="formModel.keepAlive" />
</n-form-item-grid-item>
<n-form-item-grid-item
v-if="formModel['meta.menuType'] === 'page'" :span="1" label="标签栏可见"
path="meta.withoutTab"
v-if="formModel.menuType === 'page'" :span="1" label="标签栏可见"
path="withoutTab"
>
<n-switch v-model:value="formModel['meta.withoutTab']" />
<n-switch v-model:value="formModel.withoutTab" />
</n-form-item-grid-item>
<n-form-item-grid-item v-if="formModel['meta.menuType'] === 'page'" :span="1" label="常驻标签栏" path="meta.pinTab">
<n-switch v-model:value="formModel['meta.pinTab']" />
<n-form-item-grid-item v-if="formModel.menuType === 'page'" :span="1" label="常驻标签栏" path="pinTab">
<n-switch v-model:value="formModel.pinTab" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="1" label="侧边菜单隐藏" path="meta.hide">
<n-switch v-model:value="formModel['meta.hide']" />
<n-form-item-grid-item :span="1" label="侧边菜单隐藏" path="hide">
<n-switch v-model:value="formModel.hide" />
</n-form-item-grid-item>
<n-form-item-grid-item
v-if="formModel['meta.menuType'] === 'page' && formModel['meta.hide']" :span="2"
path="meta.activeMenu"
v-if="formModel.menuType === 'page' && formModel.hide" :span="2"
path="activeMenu"
>
<template #label>
高亮菜单
<HelpInfo message="当前路由不在左侧菜单显示,但需要高亮某个菜单" />
</template>
<n-input v-model:value="formModel['meta.activeMenu']" />
<n-input v-model:value="formModel.activeMenu" />
</n-form-item-grid-item>
<n-form-item-grid-item :span="2" path="meta.roles">
<n-form-item-grid-item :span="2" path="roles">
<template #label>
访问角色
<HelpInfo message="不填写则表示所有角色都可以访问" />
</template>
<n-select
v-model:value="formModel['meta.roles']" multiple filterable
v-model:value="formModel.roles" multiple filterable
label-field="role"
value-field="id"
:options="options"

View File

@ -28,16 +28,16 @@ const columns: DataTableColumns<AppRoute.RowRoute> = [
{
title: '图标',
align: 'center',
key: 'meta.icon',
key: 'icon',
width: '6em',
render: (row) => {
return row['meta.icon'] && renderIcon(row['meta.icon'], { size: 20 })()
return row.icon && renderIcon(row.icon, { size: 20 })()
},
},
{
title: '标题',
align: 'center',
key: 'meta.title',
key: 'title',
ellipsis: {
tooltip: true,
},
@ -63,17 +63,17 @@ const columns: DataTableColumns<AppRoute.RowRoute> = [
},
{
title: '排序值',
key: 'meta.order',
key: 'order',
align: 'center',
width: '6em',
},
{
title: '菜单类型',
align: 'center',
key: 'meta.menuType',
key: 'menuType',
width: '6em',
render: (row) => {
const menuType = row['meta.menuType'] || 'page'
const menuType = row.menuType || 'page'
const menuTagType: Record<AppRoute.MenuType, NaiveUI.ThemeColor> = {
dir: 'primary',
page: 'warning',