【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>
- 剩下的下次写。