Layout layout container
<!-- 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
Configure page routing navigation
Initialize routing directory
Create several other page files (may be added later):
└─ product # 商品相关
├─ attr # 商品规格
│ └─ index.vue
├─ category # 商品分类
│ └─ index.vue
└─ list # 商品列表
└─ index.vue
Configure routing:
// 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
menu navigation
Temporarily write several menu contents statically:
<!-- 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>
Toggle sidebar expand collapse
Store sidebar expanded state:
// 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
Create a Header layout component and write a sidebar control button:
<!-- 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>
Bind the sidebar status, load the Header component, and modify the el-header style:
<!-- 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>
Breadcrumbs
Configure routing headers through routing meta information
// 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: '首页'
}
},
...
]
},
...
]
...
breadcrumb component
<!-- 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>
Load the breadcrumb component
<!-- 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>
Configure routing meta-information TypeScript support , in order to facilitate the creation of custom-created type declaration files in src/types
the directory:
// src\types\vue-router.d.ts
import 'vue-router'
declare module 'vue-router' {
// eslint-disable-next-line no-unused-vars
interface RouteMeta {
title?: string
}
}
other
The page title can be set using nuxt/vue-meta (next branch) .
full screen function
Fullscreen API - Web API interface reference | MDN
Create a fullscreen button component:
<!-- 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>
load component
<!-- 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>
page loading progress bar
Use nprogress to achieve page loading progress bar effect.
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
Note: Starting from Vue Router v4.x, it is not recommended to use it in the navigation guard to
next()
call the next navigation guard, instead usereturn
to control, returnfalse
will cancel the current navigation; return a routing address, it will jump to this route; the default route will call.