前端路由layout布局处理以及菜单交互(三)

上篇介绍了前端项目部署以及基本依赖的应用,这次主要对于路由以及布局进行模块化处理

一、 创建layout模块

1、新建src/layout/index.vue

javascript 复制代码
<template>
    <el-container class="common-layout">
        <!-- <el-aside class="aside">
            <Aside/>
        </el-aside> -->
        <el-container>
            <el-header class="header">
                <Header/>
            </el-header>
            <el-main class="main">
                <router-view></router-view>
            </el-main>
            <el-footer class="footer">Copyright © 2020 云风网, All rights reserved. 京ICP备20002789号-1</el-footer>
        </el-container>
    </el-container>
</template>

<script setup lang="ts">
// import Aside from './components/aside.vue'
import Header from './components/header.vue'

</script>

<style lang="scss" scoped>
@import '@/assets/styles/mixins.scss';
.common-layout{
    width: 100%;
    height: 100vh;
    .aside{
		height: 100vh;
        width: 200px;
	}
	.header{
        height: 60px;
        padding: 0;
	}
    .main{
        @include scrollBar;
    }
    .footer{
        height: 60px;
        background-color: #343a40;
        color: #fff;
        line-height: 60px;
    }
}
	
</style>

2、新建头部组件src/layout/components/header.vue

javascript 复制代码
<template>
    <el-menu
        :default-active="activeIndex"
        class="header-container"
        mode="horizontal"
        :ellipsis="false"
        @select="handleSelect"
    >
        <el-menu-item index="0">
            <img
                class="logo"
                src="@/assets/logo.svg"
                alt="logo"
            />
        </el-menu-item>
        <navbar-item
          v-for="(route, index) in routerList"
          :key="route.path + index"
          :item="route"
          :base-path="route.path"
          :is-nest="false"
        />
    </el-menu>
</template>
  
<script setup lang="ts">
import NavbarItem from './navbarItem.vue';
import { useRouter} from 'vue-router';
const router = useRouter();

const activeIndex = ref('1')
const routerList = router.getRoutes();
console.log('routerList',routerList);
const handleSelect = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
}
</script>
  
<style lang="scss" scoped>
.el-menu--horizontal > .el-menu-item:nth-child(1) {
  margin-right: auto;
}
.header-container{
  height: 100%;
  padding: 0 50px;
  background-color: #fff;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  .logo{
    width: 40px;
  }
}
</style> 

3、新建菜单组件src/layout/components/navbarItem.vue

javascript 复制代码
<template>
    <div v-if="!item.hidden">
      <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren)">
        <app-link v-if="onlyOneChild.meta.title" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
          <el-menu-item :index="resolvePath(onlyOneChild.path,'')" @click="handleClick(item.path)">
            <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
            <template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
          </el-menu-item>
        </app-link>
      </template>
  
      <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path,'')" teleported>
        <template v-if="item.meta" #title>
          <svg-icon :icon-class="item.meta && item.meta.icon" />
          <span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
        </template>
        <app-link v-for="child in item.children" :key="child.path"  :to="resolvePath(child.path, '')">
          <el-menu-item
            :item="child"
            class="nest-menu"
            @click="handleClick(child.path)"
          >
            <template #title><span class="menu-title">{{ child.meta.title }}</span></template>
          </el-menu-item>
        </app-link>
      </el-sub-menu>
    </div>
  </template>
  
  <script setup lang="ts">
  import { isExternal } from '@/utils/validate.ts'
  import AppLink from './Link.vue'
  import { getNormalPath } from '@/utils/mis'
  
  interface MenuItem {
    path: string;
    meta?: {
      icon?: string;
      title?: string;
      [key: string]: any;
    };
    query?: string;
    children?: MenuItem[];
    hidden?: boolean;
    noShowingChildren?: boolean;
  }
  
  const props = defineProps<{
    item: MenuItem;
    isNest: boolean;
    basePath: string;
  }>();
  const onlyOneChild = ref<MenuItem>({ path: '', meta: {} });
  
  function hasOneShowingChild(children: MenuItem[] = [], parent: MenuItem): boolean {
    const showingChildren = children.filter(item => {
      if (item.hidden) {
        return false;
      } else {
        onlyOneChild.value = item;
        return true;
      }
    });
    if (showingChildren.length === 1) {
      return true;
    }
    if (showingChildren.length === 0) {
      onlyOneChild.value = { ...parent, path: '', noShowingChildren: true, meta: {} };
      return true;
    }
  
    return false;
  }
  
  function handleClick(routePath: string) {
    localStorage.setItem('routePath', routePath);
  }
  
  function resolvePath(routePath: string, routeQuery?: string) {
    if (isExternal(routePath)) {
      return routePath;
    }
    if (isExternal(props.basePath)) {
      return props.basePath;
    }
    if (routeQuery) {
      let query = JSON.parse(routeQuery);
      return { path: getNormalPath(`${props.basePath}/${routePath}`), query };
    }
    return getNormalPath(`${props.basePath}/${routePath}`);
  }
  
  function hasTitle(title: string | undefined): string {
    return title && title.length > 5 ? title : '';
  }
  </script>

4、新建菜单组件src/layout/components/Link.vue

javascript 复制代码
<template>
  <component :is="type" v-bind="linkProps()">
    <slot />
  </component>
</template>

<script setup lang="ts">
import { isExternal } from '@/utils/validate.ts'

const props = defineProps({
  to: {
    type: [String, Object],
    required: true
  }
})

const isExt = computed(() => {
  return isExternal(props.to)
})

const type = computed(() => {
  if (isExt.value) {
    return 'a'
  }
  return 'router-link'
})

function linkProps() {
  if (isExt.value) {
    return {
      href: props.to,
      target: '_blank',
      rel: 'noopener'
    }
  }
  return {
    to: props.to
  }
}
</script>

5、新建方法封装文件src/utils/validate.ts

javascript 复制代码
/**
 * 判断url是否是http或https 
 * @param {string} path
 * @returns {Boolean}
 */
 export function isHttp(url) {
  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}

/**
 * 判断path是否为外链
 * @param {string} path
 * @returns {Boolean}
 */
 export function isExternal(path) {
  return /^(https?:|mailto:|tel:)/.test(path)
}

/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validUsername(str) {
  const valid_map = ['admin', 'editor']
  return valid_map.indexOf(str.trim()) >= 0
}

/**
 * @param {string} url
 * @returns {Boolean}
 */
export function validURL(url) {
  const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
  return reg.test(url)
}

/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validLowerCase(str) {
  const reg = /^[a-z]+$/
  return reg.test(str)
}

/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validUpperCase(str) {
  const reg = /^[A-Z]+$/
  return reg.test(str)
}

/**
 * @param {string} str
 * @returns {Boolean}
 */
export function validAlphabets(str) {
  const reg = /^[A-Za-z]+$/
  return reg.test(str)
}

/**
 * @param {string} email
 * @returns {Boolean}
 */
export function validEmail(email) {
  const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
  return reg.test(email)
}

/**
 * @param {string} str
 * @returns {Boolean}
 */
export function isString(str) {
  if (typeof str === 'string' || str instanceof String) {
    return true
  }
  return false
}

/**
 * @param {Array} arg
 * @returns {Boolean}
 */
export function isArray(arg) {
  if (typeof Array.isArray === 'undefined') {
    return Object.prototype.toString.call(arg) === '[object Array]'
  }
  return Array.isArray(arg)
}

二、修改router/index.ts

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/layout/index.vue'
//公共路由
export const constantRoutes = [
    {
        path: '/',
        component: Layout,
        redirect: '/home',
        hidden: false,
        children: [
            {
                path: 'home',
                name: 'Home',
                meta:{ title: '系统知识',routerNum: 0},
                component: () => import('@/views/Home.vue')
            }
        ]
    },
    {
        path: '/',
        redirect: "/article",
        component: Layout,
        hidden: false,
        children: [
            {
                path: 'article',
                name: 'Article',
                meta:{ title: '插件管理',routerNum: 1},
                component: () => import('@/views/Article.vue')
            }
        ]
    },
    {
        path: '/',
        redirect: "/mix",
        component: Layout,
        hidden: false,
        meta:{ title: '多级菜单',routerNum: 0},
        children: [
            {
                path: 'home',
                name: 'Home',
                meta:{ title: '系统知识',routerNum: 0},
                component: () => import('@/views/Home.vue')
            },
            {
                path: 'article',
                name: 'Article',
                meta:{ title: '插件管理',routerNum: 1},
                component: () => import('@/views/Article.vue')
            }
        ]
    },
    // {
    //     path: '/article/:id',
    //     name: 'Article',
    //     meta:{ title: '插件管理',routerNum: 1},
    //     component: () => import('@/views/Article.vue')
    // }
]
const router = createRouter({
    history: createWebHistory(),
    routes: constantRoutes
})

export default router 
相关推荐
alden_ygq7 分钟前
Shell脚本编程的实用技巧和最佳实践
前端·chrome
有心还是可以做到的嘛16 分钟前
跨层组件通信Vue3【传递数据和方法】
前端·javascript·vue.js
请叫我飞哥@30 分钟前
HTML5 手风琴(Accordion)详解
前端·html·html5
huluang35 分钟前
构建JS全栈开发的CMS系统——从零开始搭建前后端
开发语言·javascript·ecmascript
北极糊的狐37 分钟前
vue2框架配置路由设计打印单
开发语言·前端·javascript
梦想平凡39 分钟前
浅谈棋牌游戏开发流程四:核心业务逻辑(二)——房间匹配与对局流程
java·服务器·前端
火鸟240 分钟前
曲速引擎前端代码生成器 6.6.0 介绍
前端
爱开发V1 小时前
我们公司只有3个人,一个前端,一个后端
前端
进击的雷神1 小时前
SpiderFlow平台v0.5.0内置变量及自定义函数
前端·chrome·spiderflow
前端熊猫1 小时前
video.js视频播放上手
javascript·音视频·video.js