preface

According to the past several hand touch hand series of articles released, as well as fans’ private message feedback, most still hope that the example directly on the code, easy to copy and paste, this is really a bad habit, as the saying goes, their hands to find the problem to solve the problem.

App.vue

Note: Start with app.vue. Due to the NaiveUI framework, several prompt type components (Dialog, Loading Bar, etc.) need to be introduced into the app template and require RouterView sibling or parent to use the component gracefully.

Second: if you want to use in JS elegant, is also a problem, the official document example, only support setup use, how to do this is not scientific ah, don’t worry, there are policies, there are countermeasures, see me slowly.


<template>
  <NConfigProvider>
    <AppProvider>
      <RouterView />
    </AppProvider>
  </NConfigProvider>
</template>
<script lang="ts">
  import { defineComponent } from 'vue';
  import { AppProvider } from '@/components/Application';
  export default defineComponent({
    name: 'App',
    components: { AppProvider },
    setup() {
    }
  })
</script>
Copy the code

The AppProvider component wraps the RouterView. To implement the component nesting problem, the AppProvider component implementation code is as follows:

<template> <n-loading-bar-provider> <n-dialog-provider> <DialogContent /> <n-notification-provider> <n-message-provider>  <MessageContent /> <slot slot="default"></slot> </n-message-provider> </n-notification-provider> </n-dialog-provider> </n-loading-bar-provider> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { NDialogProvider, NNotificationProvider, NMessageProvider, NLoadingBarProvider, } from 'naive-ui'; import { MessageContent } from '@/components/MessageContent'; import { DialogContent } from '@/components/DialogContent'; export default defineComponent({ name: 'Application', components: { NDialogProvider, NNotificationProvider, NMessageProvider, NLoadingBarProvider, MessageContent, DialogContent, }, setup() { return {}; }}); </script>Copy the code

This is equivalent to the RouterView in app.vue, which must be used in order to work properly in setup.

The Layout is introduced

This is the core entry template for the whole background skeleton, which is the page after login, which usually contains, left menu navigation, top, content area, as follows:

Realize the Layout

Here directly with the framework provided, layout component, with folding, dark, fixed positioning, and other functions, relatively speaking is very convenient, change the style is very few can achieve a skeleton.

<template> <NLayout class="layout" :position="fixedMenu" has-sider> <! -- left area --> <NLayoutSider> <! -- logo --> <Logo :collapsed="collapsed" /> <! --> <AsideMenu V-model :collapsed="collapsed" v-model:location="getMenuLocation" /> </NLayoutSider> <! -- Right area --> <NLayout> <! -- header field --> <NLayoutHeader :inverted="getHeaderInverted" :position="fixedHeader"> <PageHeader v-model:collapsed="collapsed" :inverted="inverted" /> </NLayoutHeader> <! -- Page Content area --> <NLayoutContent> <! <TabsView v-if="isMultiTabs" v-model:collapsed="collapsed" /> <! MainView /> </NLayoutContent> </NLayout> </NLayout> </template>Copy the code

The result is as follows:

With the above core skeleton and component split, you have already planned and now you just need to fill in the corresponding component content

AsideMenu components

Menu components are encapsulated, including vertical menus, and horizontal menus, as follows:

<template> <NMenu :options="menus" :inverted="inverted" :mode="mode" :collapsed="collapsed" :collapsed-width="64" :collapsed-icon-size="20" :indent="24" :expanded-keys="openKeys" :value="getSelectedKeys" @update:value="clickMenuItem" @update:expanded-keys="menuExpanded" /> </template> <script lang="ts"> import { defineComponent, ref, onMounted, reactive, computed, watch, toRefs, unref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useAsyncRouteStore } from '@/store/modules/asyncRoute'; import { generatorMenu, generatorMenuMix } from '@/utils'; import { useProjectSettingStore } from '@/store/modules/projectSetting'; import { useProjectSetting } from '@/hooks/setting/useProjectSetting'; Export default defineComponent({name: 'Menu', components: {}, props: {mode: {type: String, default: Collision: {type: String, default: 'left',},}, emits: ['update:collapsed'], setup(props, {emit}) {// currentRoute const currentRoute = useRoute(); const router = useRouter(); const asyncRouteStore = useAsyncRouteStore(); const settingStore = useProjectSettingStore(); const menus = ref<any[]>([]); const selectedKeys = ref<string>(currentRoute.name as string); const headerMenuSelectKey = ref<string>(''); const { getNavMode } = useProjectSetting(); const navMode = getNavMode; Const matched = currentRoute. Matched; const getOpenKeys = matched && matched.length ? matched.map((item) => item.name) : []; const state = reactive({ openKeys: getOpenKeys, }); const inverted = computed(() => { return ['dark', 'header-dark'].includes(settingStore.navTheme); }); const getSelectedKeys = computed(() => { let location = props.location; return location === 'left' || (location === 'header' && unref(navMode) === 'horizontal') ? unref(selectedKeys) : unref(headerMenuSelectKey); }); / watch/listen segmentation menu (() = > settingStore. MenuSetting. MixMenu, () = > {updateMenu (); if (props.collapsed) { emit('update:collapsed', ! props.collapsed); }}); Watch (() => props. Collapsed, (newVal) => {state.openkeys = newVal? [] : getOpenKeys; selectedKeys.value = currentRoute.name as string; }); Watch (() => currentroute. fullPath, () => {updateMenu(); const matched = currentRoute.matched; state.openKeys = matched.map((item) => item.name); const activeMenu: string = (currentRoute.meta? .activeMenu as string) || ''; selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string); }); function updateMenu() { if (! settingStore.menuSetting.mixMenu) { menus.value = generatorMenu(asyncRouteStore.getMenus); } else {/ / mixed menu const firstRouteName: string = (currentRoute. Matched [0]. Name as string) | | '. menus.value = generatorMenuMix(asyncRouteStore.getMenus, firstRouteName, props.location); const activeMenu: string = currentRoute? .matched[0].meta? .activeMenu as string; headerMenuSelectKey.value = (activeMenu ? activeMenu : firstRouteName) || ''; Function clickMenuItem(key: string) {if (/ HTTP (s)? :/.test(key)) { window.open(key); } else { router.push({ name: key }); }} function menuExpanded(openKeys: string[]) {if (! openKeys) return; const latestOpenKey = openKeys.find((key) => state.openKeys.indexOf(key) === -1); const isExistChildren = findChildrenLen(latestOpenKey as string); state.openKeys = isExistChildren ? (latestOpenKey ? [latestOpenKey] : []) : openKeys; } function findChildrenLen(key: string) {if (! key) return false; const subRouteChildren: string[] = []; for (const { children, key } of unref(menus)) { if (children && children.length) { subRouteChildren.push(key as string); } } return subRouteChildren.includes(key); } onMounted(() => { updateMenu(); }); return { ... toRefs(state), inverted, menus, selectedKeys, headerMenuSelectKey, getSelectedKeys, clickMenuItem, menuExpanded, }; }}); </script>Copy the code

Code implementation is relatively simple, the official menu component, will automatically help us recursion to create a submenu, here we only need to throw the data to him, is really great heart.

Here are two core methods:

1. Left common menu

Export function generatorMenu(routerMap: Array<any>) { return filterRouter(routerMap).map((item) => { const isRoot = isRootRouter(item); const info = isRoot ? item.children[0] : item; const currentMenu = { ... info, ... info.meta, label: info.meta? .title, key: info.name, }; // If there is a submenu, If (info.children && info.children. Length > 0) {// Recursion currentMenu.children = generatorMenu(info.children);  } return currentMenu; }); }Copy the code

This is done by routing an array of objects to the format required by the Menu component, and some custom logic can be implemented here

2. Top menu mode – Mixed menu

Export function generatorMenuMix(routerMap: Array<any>, routerName: string, location: string) { const cloneRouterMap = cloneDeep(routerMap); const newRouter = filterRouter(cloneRouterMap); if (location === 'header') { const firstRouter: any[] = []; newRouter.forEach((item) => { const isRoot = isRootRouter(item); const info = isRoot ? item.children[0] : item; info.children = undefined; const currentMenu = { ... info, ... info.meta, label: info.meta? .title, key: info.name, }; firstRouter.push(currentMenu); }); return firstRouter; } else { return getChildrenRouter(newRouter.filter((item) => item.name === routerName)); }} /** * Create submenu ** / export function getChildrenRouter(routerMap: Array<any>) { return filterRouter(routerMap).map((item) => { const isRoot = isRootRouter(item); const info = isRoot ? item.children[0] : item; const currentMenu = { ... info, ... info.meta, label: info.meta? .title, key: info.name, }; // If there is a submenu, If (info.children && info.children. Length > 0) {// Recursion currentMenu.children = getChildrenRouter(info.children); } return currentMenu; }); }Copy the code

Here also provides data support for the Menu component

PageHeader components

This component mainly implements these functions, is relatively simple, and may not be what you want, so don’t mention it

TabsView

Multi – TAB page, the realization of more functions, behind a separate draw together to parse, hand touch hand with you.

MainView

<template> <RouterView> <template #default="{ Component, route }"> <transition name="zoom-fade" mode="out-in" appear> <keep-alive v-if="keepAliveComponents" :include="keepAliveComponents"> <component :is="Component" :key="route.fullPath" /> </keep-alive> <component v-else :is="Component" :key="route.fullPath" /> </transition> </template> </RouterView> </template> <script> import { defineComponent, computed } from 'vue'; import { useAsyncRouteStore } from '@/store/modules/asyncRoute'; export default defineComponent({ name: 'MainView', components: {}, props: { notNeedKey: { type: Boolean, default: false, }, animate: { type: Boolean, default: true, }, }, setup() { const asyncRouteStore = useAsyncRouteStore(); / / need to cache the routing component const keepAliveComponents = computed (() = > asyncRouteStore. KeepAliveComponents); return { keepAliveComponents, }; }}); </script>Copy the code

The main thing here is to do a cache routing, and animation effects, and public layout style

Final finish

The above show only some core code, and ideas, want to see the complete code, you can find the naive-ui-admin official warehouse