Vue3+Vite+Ts 项目实战 04 搭建 Layout 布局
Layout 布局容器<!-- src\layout\AppLayout.vue --><template><el-container><el-aside width="200px">Aside</el-aside><el-container><el-header>Header</el-header>&
·
Layout 布局容器
<!-- src\layout\AppLayout.vue -->
<template>
<el-container>
<el-aside width="200px">
Aside
</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>
<!-- 子路由出口 -->
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts"></script>
<style scoped lang="scss">
.el-container {
height: 100vh;
}
.el-header {
background-color: #B3C0D1;
}
.el-aside {
width: auto;
background-color: #304156;
}
.el-main {
background-color: #E9EEF3;
}
</style>
// src\styles\common.scss
* {
margin: 0;
padding: 0;
}
// src\router\index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import AppLayout from '@/layout/AppLayout.vue'
const routes:RouteRecordRaw[] = [
{
path: '/',
component: AppLayout,
children: [
{
path: '/',
name: 'home',
component: () => import('@/views/home/index.vue')
}
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue')
}
]
const router = createRouter({
// history: createWebHashHistory(), // hash 路由模式
history: createWebHistory(), // history 路由模式
routes // 路由规则
})
export default router
配置页面路由导航
初始化路由目录
创建其他几个页面文件(后面可能还会增加):
└─ product # 商品相关
├─ attr # 商品规格
│ └─ index.vue
├─ category # 商品分类
│ └─ index.vue
└─ list # 商品列表
└─ index.vue
配置路由:
// src\router\modules\products.ts
import { RouteRecordRaw, RouterView } from 'vue-router'
const routes:RouteRecordRaw = {
path: 'product',
component: RouterView,
children: [
{
path: 'list',
name: 'product_list',
component: () => import('@/views/product/list/index.vue')
},
{
path: 'category',
name: 'product_category',
component: () => import('@/views/product/category/index.vue')
},
{
path: 'attr',
name: 'product_attr',
component: () => import('@/views/product/attr/index.vue')
},
{
path: 'reply',
name: 'product_reply',
component: () => import('@/views/product/reply/index.vue')
}
]
}
export default routes
// src\router\index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import AppLayout from '@/layout/AppLayout.vue'
import productRoutes from './modules/product'
const routes:RouteRecordRaw[] = [
{
path: '/',
component: AppLayout,
children: [
{
path: '/',
name: 'home',
component: () => import('@/views/home/index.vue')
},
productRoutes
]
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue')
}
]
const router = createRouter({
// history: createWebHashHistory(), // hash 路由模式
history: createWebHistory(), // history 路由模式
routes // 路由规则
})
export default router
菜单导航
暂时静态编写几个菜单内容:
<!-- src\layout\components\AppMenu.vue -->
<template>
<el-menu
active-text-color="#ffd04b"
background-color="#304156"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
router
>
<el-menu-item index="/">
<!-- <Menu> 首字母要大写,否则会和浏览器原生的 <menu> 冲突 -->
<el-icon><Menu /></el-icon>
<span>首页</span>
</el-menu-item>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>商品</span>
</template>
<el-menu-item index="/product/list">
<el-icon><Menu /></el-icon>
<span>商品列表</span>
</el-menu-item>
<el-menu-item index="/product/category">
<el-icon><Menu /></el-icon>
<span>商品分类</span>
</el-menu-item>
<el-menu-item index="/product/attr">
<el-icon><Menu /></el-icon>
<span>商品规格</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</template>
<script setup lang="ts"></script>
<style scoped></style>
<!-- src\layout\AppLayout.vue -->
<template>
<el-container>
<el-aside width="200px">
<AppMenu />
</el-aside>
<el-container>
<el-header>Header</el-header>
<el-main>
<!-- 子路由出口 -->
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import AppMenu from './AppMenu/index.vue'
</script>
<style scoped lang="scss">...</style>
切换侧边栏展开收起
存储侧边栏展开状态:
// src\store\index.ts
import { defineStore } from 'pinia'
const useStore = defineStore('main', {
state: () => ({
count: 0,
isCollapse: false
}),
getters: {
doubleCount(state) {
return state.count * 2
}
},
actions: {
increment() {
this.count++
}
}
})
export default useStore
创建 Header 布局组件,编写侧边栏控制按钮:
<!-- src\layout\AppHeader\index.vue -->
<template>
<ToggleSidebar />
<!-- 面包屑 -->
</template>
<script setup lang="ts">
import ToggleSidebar from './ToggleSidebar.vue'
</script>
<style scoped lang="scss" >
i {
font-size: 19px;
cursor: pointer;
}
</style>
<!-- src\layout\AppHeader\ToggleSidebar.vue -->
<template>
<el-icon>
<component
:is="store.isCollapse ? 'expand' : 'fold'"
@click="handleCollapse"
/>
</el-icon>
</template>
<script setup lang="ts">
import useStore from '@/store'
const store = useStore()
// 因为没有其他地方可以修改侧边栏状态
// 所以这里直接修改
const handleCollapse = () => {
store.isCollapse = !store.isCollapse
}
</script>
<style scoped></style>
绑定侧边栏状态,加载 Header 组件,修改 el-header 样式:
<!-- src\layout\AppLayout.vue -->
<template>
<el-container>
<el-aside width="200px">
<AppMenu />
</el-aside>
<el-container>
<el-header>
<AppHeader />
</el-header>
<el-main>
<!-- 子路由出口 -->
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script setup lang="ts">
import AppMenu from './AppMenu/index.vue'
import AppHeader from './AppHeader/index.vue'
</script>
<style scoped lang="scss">
.el-container {
height: 100vh;
}
.el-header {
background-color: #fff;
color: #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.el-aside {
width: auto;
background-color: #304156;
}
.el-main {
background-color: #E9EEF3;
}
</style>
面包屑导航
通过路由元信息配置路由标题
// src\router\modules\products.ts
import { RouteRecordRaw, RouterView } from 'vue-router'
const routes:RouteRecordRaw = {
path: 'product',
component: RouterView,
meta: {
title: '商品'
},
children: [
{
path: 'list',
name: 'product_list',
component: () => import('@/views/product/list/index.vue'),
meta: {
title: '商品列表'
}
},
{
path: 'category',
name: 'product_category',
component: () => import('@/views/product/category/index.vue'),
meta: {
title: '商品分类'
}
},
{
path: 'attr',
name: 'product_attr',
component: () => import('@/views/product/attr/index.vue'),
meta: {
title: '商品规格'
}
}
]
}
export default routes
// src\router\index.ts
...
const routes:RouteRecordRaw[] = [
{
path: '/',
component: AppLayout,
children: [
{
path: '/',
name: 'home',
component: () => import('@/views/home/index.vue'),
meta: {
title: '首页'
}
},
...
]
},
...
]
...
面包屑组件
<!-- src\layout\AppHeader\Breadcrumb.vue -->
<template>
<el-breadcrumb separator-icon="arrow-right">
<el-breadcrumb-item
v-for="item in routes"
:key="item.path"
>
{{ item.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { computed } from 'vue'
// 获取路由,类似 Vue2 的 this.$router
const router = useRouter()
// 获取当前路由的匹配记录
const routes = computed(() => {
return router.currentRoute.value.matched.filter(item => item.meta.title)
})
</script>
<style scoped></style>
加载面包屑组件
<!-- src\layout\AppHeader\index.vue -->
<template>
<el-space size="large">
<ToggleSidebar />
<Breadcrumb />
</el-space>
</template>
<script setup lang="ts">
import ToggleSidebar from './ToggleSidebar.vue'
import Breadcrumb from './Breadcrumb.vue'
</script>
<style scoped lang="scss" >
i {
font-size: 19px;
cursor: pointer;
}
</style>
配置路由元信息 TypeScript 支持,为了方便将自定义创建的类型声明文件放到 src/types
目录下:
// src\types\vue-router.d.ts
import 'vue-router'
declare module 'vue-router' {
// eslint-disable-next-line no-unused-vars
interface RouteMeta {
title?: string
}
}
其他
可以使用 nuxt/vue-meta(next 分支) 设置页面标题。
全屏功能
创建全屏按钮组件:
<!-- src\layout\AppHeader\FullScreen.vue -->
<template>
<el-icon><full-screen @click="toggleFullScreen" /></el-icon>
</template>
<script setup lang="ts">
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
</script>
<style scoped></style>
加载组件
<!-- src\layout\AppHeader\index.vue -->
<template>
<el-space size="large">
<ToggleSidebar />
<Breadcrumb />
</el-space>
<el-space size="large">
<FullScreen />
</el-space>
</template>
<script setup lang="ts">
import ToggleSidebar from './ToggleSidebar.vue'
import Breadcrumb from './Breadcrumb.vue'
import FullScreen from './FullScreen.vue'
</script>
<style scoped lang="scss" >
i {
font-size: 19px;
cursor: pointer;
}
</style>
页面加载进度条
使用 nprogress 实现页面加载进度条效果。
npm i --save nprogress
# TS 类型补充模块
npm i --save-dev @types/nprogress
// src\router\index.ts
...
import nprogress from 'nprogress'
import 'nprogress/nprogress.css'
// 关闭 loading 图标
nprogress.configure({ showSpinner: false })
...
router.beforeEach(() => {
// 开始加载进度条
nprogress.start()
})
router.afterEach(() => {
// 结束加载进度条
nprogress.done()
})
export default router
注意:Vue Router v4.x 开始不建议在导航守卫中使用
next()
来调用下一个导航守卫,转而改用return
来控制,返回false
会取消当前导航;返回一个路由地址,则会跳转到这个路由;默认路由会调用。
更多推荐
已为社区贡献7条内容
所有评论(0)