【vue】vue3学习笔记(二)

接上篇

左侧导航

左侧菜单收缩

  • 上一篇变量声明的d.ts文件前面的文件名需要和variables.scss的文件名一样,也就是叫variables.scss.d.ts,才可以不报错。
  • layout/components/index.vue
<template>
  <div>
    <h8 @click="isCollapse = !isCollapse">展收测试</h8>

    <el-menu
      class="sidebar-container-menu"
      mode="vertical"
      :default-active="activeMenu"
      :background-color="scssVariables.menuBg"
      :text-color="scssVariables.menuText"
      :active-text-color="scssVariables.menuActiveText"
      :collapse="isCollapse"
    >
      <sidebar-item />
    </el-menu>
  </div>
</template>

<script lang="ts">
import {
    
     defineComponent, computed, ref } from 'vue'
import {
    
     useRoute } from 'vue-router'
import variables from '@/styles/variables.scss'
import SidebarItem from './SidebarItem.vue'

export default defineComponent({
    
    
  name: 'Sidebar',
  components: {
    
    
    SidebarItem
  },
  setup() {
    
    
    const route = useRoute()

    const activeMenu = computed(() => {
    
    
      const {
    
     path } = route
      return path
    })
    const scssVariables = computed(() => variables)
    const isCollapse = ref(true)

    return {
    
    
      scssVariables,
      isCollapse,
      activeMenu
    }
  }
})
</script>

  • sidebaritem.vue
<template>
  <div class="sidebar-item-container">
    <el-menu-item index="1">
      <svg-icon class="menu-icon" icon-class="lock"></svg-icon>
      <template #title>
        <span>Dashoard</span>
      </template>
    </el-menu-item>
  </div>
</template>

<script lang="ts">
import {
    
     defineComponent } from 'vue'

export default defineComponent({
    
    
  name: 'SidebarItem'
})
</script>

<style lang="scss">
.sidebar-item-container {
    
    
  .menu-icon {
    
    
    margin-right: 16px;
    margin-left: 5px;
    vertical-align: middle;
  }
}
</style>
  • 在对应位置写出空白组件,并配上路由:
import {
    
     createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Layout from '@/layout/index.vue'

export const asyncRoutes: Array<RouteRecordRaw> = [
  {
    
    
    path: '/documentation',
    component: Layout,
    redirect: '/documentation/index',
    children: [
      {
    
    
        path: 'index',
        name: 'Documentation',
        component: () =>
          import(
            /* webpackChunkName: "documentation" */ '@/views/documentation/index.vue'
          ),
        meta: {
    
    
          title: 'Documentation',
          icon: 'documentation'
        }
      }
    ]
  },
  {
    
    
    path: '/guide',
    component: Layout,
    redirect: '/guide/index',
    children: [
      {
    
    
        path: 'index',
        name: 'Guide',
        component: () =>
          import(/* webpackChunkName: "guide" */ '@/views/guide/index.vue'),
        meta: {
    
    
          title: 'Guide',
          icon: 'guide'
        }
      }
    ]
  },
  {
    
    
    path: '/system',
    component: Layout,
    redirect: '/system/user',
    meta: {
    
    
      title: 'System',
      icon: 'lock'
    },
    children: [
      {
    
    
        path: 'menu',
        component: () =>
          import(/* webpackChunkName: "menu" */ '@/views/system/menu.vue'),
        meta: {
    
    
          title: 'Menu Management',
          icon: 'list'
        }
      },
      {
    
    
        path: 'role',
        component: () =>
          import(/* webpackChunkName: "role" */ '@/views/system/role.vue'),
        meta: {
    
    
          title: 'Role Management',
          icon: 'list'
        }
      },
      {
    
    
        path: 'user',
        component: () =>
          import(/* webpackChunkName: "user" */ '@/views/system/user.vue'),
        meta: {
    
    
          title: 'User Management',
          icon: 'list'
        }
      }
    ]
  }
]

export const constantRoutes: Array<RouteRecordRaw> = [
  {
    
    
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
    
    
        path: 'dashboard',
        name: 'Dashboard',
        component: () =>
          import(
            /* webpackChunkName: "dashboard" */ '@/views/dashboard/index.vue'
          ),
        meta: {
    
    
          title: 'Dashboard'
        }
      }
    ]
  }
]

export const routes = [
  ...constantRoutes,  
  ...asyncRoutes
]

const router = createRouter({
    
    
  history: createWebHashHistory(),
  routes
})

export default router
  • sidebar.scss
#app {
    
    
  .sidebar-container {
    
    
    height: 100%;
    background-color: $menuBg;

    // menu未收起时样式
    &-menu:not(.el-menu--collapse) {
    
    
      width: $sideBarWidth;
    }
    .el-menu {
    
    
      border: none;
    }
  }
}
  • 此时点击展收测试能正常收放就ok。

左侧菜单路由

  • 把sidebar-item里传一下路由:
<template>
  <div>
    <h6 @click="isCollapse = !isCollapse">展收测试</h6>
    <el-menu
      class="sidebar-container-menu"
      mode="vertical"
      router
      :default-active="activeMenu"
      :background-color="scssVariables.menuBg"
      :text-color="scssVariables.menuText"
      :active-text-color="scssVariables.menuActiveText"
      :collapse="isCollapse"
      :collapse-transition="true"
    >
      <sidebar-item
        v-for="route in menuRoutes"
        :key="route.path"
        :item="route"
        :base-path="route.path"
      />
    </el-menu>
  </div>
</template>

<script lang="ts">
import {
      
       defineComponent, computed, ref } from 'vue'
import {
      
       useRoute } from 'vue-router'
import variables from '@/styles/variables.scss'
import {
      
       routes } from '@/router'
import SidebarItem from './SidebarItem.vue'

export default defineComponent({
      
      
  name: 'Sidebar',
  components: {
      
      
    SidebarItem
  },
  setup() {
      
      
    const route = useRoute()
    const activeMenu = computed(() => {
      
      
      const {
      
       path } = route
      return path
    })
    const scssVariables = computed(() => variables)
    const isCollapse = ref(false)

    const menuRoutes = computed(() => routes)
    console.log(menuRoutes, routes)
    return {
      
      
      scssVariables,
      isCollapse,
      activeMenu,
      menuRoutes
    }
  }
})
</script>

  • 判断下有几个孩子,分别处理,并递归:
<template>
  <div class="sidebar-item-container">
    <template v-if="theOnlyOneChildRoute && !theOnlyOneChildRoute.children">
      <el-menu-item :index="resolvePath(theOnlyOneChildRoute.path)">
        <svg-icon v-if="icon" class="menu-icon" :icon-class="icon"></svg-icon>
        <template #title>
          <span>{
   
   { theOnlyOneChildRoute.meta.title }}</span>
        </template>
      </el-menu-item>
    </template>

    <el-submenu v-else :index="resolvePath(item.path)" popper-append-to-body>
      <template #title>
        <svg-icon
          v-if="item.meta.icon"
          class="menu-icon"
          :icon-class="item.meta.icon"
        ></svg-icon>
        <span class="submenu-title">{
   
   { item.meta.title }}</span>
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
      >
      </sidebar-item>
    </el-submenu>
  </div>
</template>

<script lang="ts">
import {
      
       defineComponent, PropType, computed, toRefs } from 'vue'
import {
      
       RouteRecordRaw } from 'vue-router'
import path from 'path'

export default defineComponent({
      
      
  name: 'SidebarItem',
  props: {
      
      
    item: {
      
      
      type: Object as PropType<RouteRecordRaw>,
      required: true
    },
    basePath: {
      
      
      type: String,
      required: true
    }
  },
  setup(props) {
      
      
    const {
      
       item } = toRefs(props)

    const showingChildNumber = computed(() => {
      
      
      const children = (props.item.children || []).filter(child => {
      
      
        if (child.meta && child.meta.hidden) return false
        return true
      })
      return children.length
    })

    const theOnlyOneChildRoute = computed(() => {
      
      
      if (showingChildNumber.value > 1) {
      
      
        return null
      }

      // 只有一个子路由 还要筛选路由meta里有无hidden属性 hidden:true则过滤出去 不用管
      // 路由meta里我们会配置hidden属性表示不渲染成菜单,比如login 401 404页面是不渲染成菜单的
      if (item.value.children) {
      
      
        for (const child of item.value.children) {
      
      
          if (!child.meta || !child.meta.hidden) {
      
      
            return child
          }
        }
      }

      // showingChildNumber === 0 无可渲染的子路由 (可能有子路由 hidden属性为true)
      // 无可渲染chiildren时 把当前路由item作为仅有的子路由渲染
      return {
      
      
        ...props.item,
        path: '' // resolvePath避免resolve拼接时 拼接重复
      }
    })

    // menu icon
    const icon = computed(() => {
      
      
      // 子路由 如果没有icon就用父路由的
      return (
        theOnlyOneChildRoute.value?.meta?.icon ||
        (props.item.meta && props.item.meta.icon)
      )
    })

    // 利用path.resolve 根据父路径+子路径 解析成正确路径 子路径可能是相对的
    // resolvePath在模板中使用
    const resolvePath = (childPath: string) => {
      
      
      return path.resolve(props.basePath, childPath)
    }

    return {
      
      
      theOnlyOneChildRoute,
      icon,
      resolvePath
    }
  }
})
</script>

<style lang="scss">
.sidebar-item-container {
      
      
  .menu-icon {
      
      
    margin-right: 16px;
    margin-left: 5px;
    vertical-align: middle;
  }
}
</style>

  • 能看见正常收缩,以及路由显示即ok

外链路由

  • sidebaritemlink.vue
<template>
  <component :is="type" v-bind="linkProps">
    <slot />
  </component>
</template>

<script lang="ts">
import {
      
       computed, defineComponent } from 'vue'
import {
      
       isExternal } from '@/utils/validate'

// 针对路径是外链 就渲染为a标签 如果是正常路由路径 就渲染为 router-link
// el-menu组件的router属性去掉了  不开启路由模式
export default defineComponent({
      
      
  name: 'SidebarItemLink',
  props: {
      
      
    to: {
      
      
      type: String,
      required: true
    }
  },
  setup(props) {
      
      
    // 判断接收的路径 是不是外链
    const isExt = computed(() => isExternal(props.to))
    const type = computed(() => {
      
      
      if (isExt.value) {
      
      
        return 'a'
      }
      return 'router-link'
    })

    const linkProps = computed(() => {
      
      
      if (isExt.value) {
      
      
        return {
      
      
          // a 标签的一些原生属性
          href: props.to,
          target: '_blank',
          rel: 'noopener'
        }
      }
      // router-link只需一个to props
      return {
      
      
        to: props.to
      }
    })

    return {
      
      
      type,
      linkProps
    }
  }
})
</script>

  • 用了动态标签,学到了。
  • 加入路由:
  {
    
    
    // 外链路由
    path: '/external-link',
    component: Layout,
    children: [
      {
    
    
        path: 'https://www.baidu.com/',
        redirect: '/',
        meta: {
    
    
          title: 'External Link',
          icon: 'link'
        }
      }
    ]
  }
  • 改写resolvepath方法:
   const resolvePath = (childPath: string) => {
    
    
      if (isExternal(childPath)) {
    
    
        return childPath
      }
      return path.resolve(props.basePath, childPath)
    }
  • 套在第一个标签上:
 <template v-if="theOnlyOneChildRoute && !theOnlyOneChildRoute.children">
      <sidebar-item-link
        v-if="theOnlyOneChildRoute.meta"
        :to="resolvePath(theOnlyOneChildRoute.path)"
      >
        <el-menu-item :index="resolvePath(theOnlyOneChildRoute.path)">
          <svg-icon v-if="icon" class="menu-icon" :icon-class="icon"></svg-icon>
          <template #title>
            <span>{
   
   { theOnlyOneChildRoute.meta.title }}</span>
          </template>
        </el-menu-item>
      </sidebar-item-link>
    </template>
  • 如果成功跳转ok。

hidden属性

  • 给onlyOne函数修改下,无children则给个标志:
 const theOnlyOneChildRoute = computed(() => {
    
    
      if (showingChildNumber.value > 1) {
    
    
        return null
      }

      if (item.value.children) {
    
    
        for (const child of item.value.children) {
    
    
          // hidden属性控制路由是否渲染成菜单 像login 401 404等路由都不需要渲染成菜单
          if (!child.meta || !child.meta.hidden) {
    
    
            return child
          }
        }
      }
      return {
    
    
        ...props.item,
        path: '', // resolvePath避免resolve拼接时 拼接重复
        noShowingChildren: true // 无可渲染chiildren
      }
    })

   <template
      v-if="
        theOnlyOneChildRoute &&
          (!theOnlyOneChildRoute.children ||
            theOnlyOneChildRoute.noShowingChildren)
      "
    >
      <sidebar-item-link
        v-if="theOnlyOneChildRoute.meta"
        :to="resolvePath(theOnlyOneChildRoute.path)"
      >
        <el-menu-item :index="resolvePath(theOnlyOneChildRoute.path)">
          <svg-icon v-if="icon" class="menu-icon" :icon-class="icon"></svg-icon>
          <template #title>
            <span>{
   
   { theOnlyOneChildRoute.meta.title }}</span>
          </template>
        </el-menu-item>
      </sidebar-item-link>
    </template>
  • 加个hidden路由,如果隐藏就ok。

路由缓存与动画

  • 添加AppMain.vue:
<template>
  <div class="app-main">
    <!-- vue3 路由缓存 https://next.router.vuejs.org/guide/migration/index.html#router-view-keep-alive-and-transition -->
    <router-view v-slot="{ Component }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive>
          <component :is="Component" :key="key" />
        </keep-alive>
      </transition>
    </router-view>
  </div>
</template>

<script lang="ts">
import {
    
     computed, defineComponent } from 'vue'
import {
    
     useRoute } from 'vue-router'

export default defineComponent({
    
    
  name: 'AppMain',
  setup() {
    
    
    const route = useRoute()
    const key = computed(() => route.path)
    return {
    
    
      key
    }
  }
})
</script>

<style lang="scss" scoped>
.app-main {
    
    
  /* navbar 50px  */
  min-height: calc(100vh - 50px);
}

.fade-transform-enter-active,
.fade-transform-leave-active {
    
    
  transition: all 0.5s;
}

.fade-transform-enter-from {
    
    
  opacity: 0;
  transform: translateX(-30px);
}

.fade-transform-leave-to {
    
    
  opacity: 0;
  transform: translateX(30px);
}
</style>

  • 在layout/index.vue中导入即可:
  <app-main />
  • 在路由中写个input ,进入写几个字 切换有动画,切回能看见前面刚写的即ok 。

icon支持elemicon

  • 将路由icon变为:
    icon: 'el-icon-platform-eleme'
  • 增加类型type.ts
import {
    
     RouteRecordRaw, RouteMeta } from 'vue-router'

// router的meta类型
type ItemRouterMeta = RouteMeta & {
    
    
  icon: string
  title: string
}

// 菜单menu路由类型
export type MenuItemRouter = RouteRecordRaw & {
    
    
  meta: ItemRouterMeta
}

  • 改写vue-router的meta:
import 'vue-router'

declare module 'vue-router' {
    
    
  interface RouteMeta {
    
    
    title?: string // 路由菜单title
    icon?: string // 路由菜单icon
    hidden?: boolean // 菜单栏不显示
    // 路由是否缓存 没有这个属性或false都会缓存 true不缓存
    noCache?: boolean
    activeMenu?: string // 指定菜单激活
    breadcrumb?: boolean // 该路由是否显示面包屑
    affix?: boolean // 固定显示在tagsView中
    alwaysShow?: boolean // 菜单是否一直显示根路由
  }
}
  • 渲染时判断下即可:
 <sidebar-item-link
        v-if="theOnlyOneChildRoute.meta"
        :to="resolvePath(theOnlyOneChildRoute.path)"
      >
        <el-menu-item :index="resolvePath(theOnlyOneChildRoute.path)">
          <i v-if="icon && icon.includes('el-icon')" :class="icon"></i>
          <svg-icon
            v-else-if="icon"
            class="menu-icon"
            :icon-class="icon"
          ></svg-icon>
          <template #title>
            <span>{
   
   { theOnlyOneChildRoute.meta.title }}</span>
          </template>
        </el-menu-item>
      </sidebar-item-link>
  • 如果能看见饿了么icon则ok。

增加alwaysshow与指定高亮

  • 指定高亮做个判断就行了:
    const activeMenu = computed(() => {
    
    
      const {
    
     path, meta } = route
      if (meta.activeMenu) {
    
    
        return meta.activeMenu
      }
      return path
    })
  • alwaysshow就写个判断,哪个路由有那个,那么本来该省略该路由就不省略:
    // 设置 alwaysShow: true,这样它就会忽略上面定义的规则,一直显示根路由 哪怕只有一个子路由也会显示为嵌套的路由菜单
    const alwaysShowRootMenu = computed(
      () => props.item.meta && props.item.meta.alwaysShow
    )

    // 是否只有一条可渲染路由
    const isRenderSingleRoute = computed(
      () =>
        !alwaysShowRootMenu.value &&
        (!theOnlyOneChildRoute.value?.children || noShowingChildren.value)
    )

   <template v-if="isRenderSingleRoute && theOnlyOneChildRoute">
      <sidebar-item-link
        v-if="theOnlyOneChildRoute.meta"
        :to="resolvePath(theOnlyOneChildRoute.path)"
      >
        <el-menu-item :index="resolvePath(theOnlyOneChildRoute.path)">
          <i v-if="icon && icon.includes('el-icon')" :class="icon"></i>
          <svg-icon
            v-else-if="icon"
            class="menu-icon"
            :icon-class="icon"
          ></svg-icon>
          <template #title>
            <span>{
   
   { theOnlyOneChildRoute.meta.title }}</span>
          </template>
        </el-menu-item>
      </sidebar-item-link>
    </template>
  • 剩下的下次写。

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/117421004