mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-04-05 19:41:59 +08:00
fix: remove meta.
perfix
This commit is contained in:
parent
bf5445b6e4
commit
39d185b132
@ -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)]" />
|
||||
|
18
src/layouts/components/common/Setting.vue
Normal file
18
src/layouts/components/common/Setting.vue
Normal 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>
|
143
src/layouts/components/common/SettingDrawer.vue
Normal file
143
src/layouts/components/common/SettingDrawer.vue
Normal 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>
|
@ -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,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -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>
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -6,7 +6,6 @@ export const routes: RouteRecordRaw[] = [
|
||||
path: '/',
|
||||
name: 'root',
|
||||
redirect: '/appRoot',
|
||||
// component: () => import('@/layouts/index'),
|
||||
children: [
|
||||
],
|
||||
},
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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全局状态库
|
||||
|
@ -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
142
src/store/router/helper.ts
Normal 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
84
src/store/router/index.ts
Normal 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
|
||||
},
|
||||
},
|
||||
})
|
6
src/typings/route.d.ts
vendored
6
src/typings/route.d.ts
vendored
@ -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
|
||||
|
||||
/**
|
||||
* 挂载到项目上的真实路由结构
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user