欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【vue】vue3学习笔记(二)

程序员文章站 2022-05-17 20:40:29
...

接上篇

左侧导航

左侧菜单收缩

  • 上一篇变量声明的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>
  • 剩下的下次写。
相关标签: VUE