续接Django REST Framework,使用Vite构建Vue3的前端项目
1. 后台管理系统主界面框架搭建
- 后台系统主界面搭建
新建后台管理文件目录
- 完成后台整体布局
TypeScript
// 1.主界面 index.vue
<script setup lang="ts">
</script>
<template>
<el-container class="layout">
<el-aside width="200px" class="aside">Aside</el-aside>
<el-container>
<el-header class="header">Header</el-header>
<el-main class="main">Main</el-main>
</el-container>
</el-container>
</template>
<style scoped>
.layout {
height: 100%;
}
.aside {
background-color: #304156;
}
.header {
background-color: lightblue;
height: 50px;
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
// 2.配置路由
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Index from '../layout/index.vue'
const routes: Array<RouteRecordRaw>= [
{
path: "/",
// name: 'Home',
component: Index,
},
]
// 创建一个 vue-router对象
const router = createRouter({
history: createWebHistory(),
})
// 暴露接口
export default router
3.app.vue引入路由
<script setup lang="ts">
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
- 后台页面满屏显示
TypeScript
1.style.css 注销 #app{}
2.index.html添加样式
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理</title>
</head>
<!--添加样式-->
<style>
html, body,#app{
padding: 0px;
margin: 0px;
height: 100%;
width: 100%;
}
</style>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
- 侧边栏布局实现
TypeScript
// Menu.vue
<script setup lang="ts">
</script>
<template>
<el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo" default-active="2"
text-color="#fff">
<el-sub-menu index="1">
<template #title>
<el-icon>
<location />
</el-icon>
<span>基础数据</span>
</template>
<el-menu-item index="1-1">院系管理</el-menu-item>
<el-menu-item index="1-2">专业管理</el-menu-item>
<el-menu-item index="1-3">学生管理</el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon>
<location />
</el-icon>
<span>学生管理</span>
</template>
<el-menu-item index="2-1">学生信息</el-menu-item>
<el-menu-item index="2-2">学生成绩</el-menu-item>
<el-menu-item index="2-3">学生照片</el-menu-item>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>
<el-icon>
<location />
</el-icon>
<span>用户角色</span>
</template>
<el-menu-item index="3-1">登录账号</el-menu-item>
<el-menu-item index="3-2">用户角色</el-menu-item>
<el-menu-item index="3-3">菜单管理</el-menu-item>
<el-menu-item index="3-4">权限信息</el-menu-item>
</el-sub-menu>
<!-- <el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<span>Navigator Two</span>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon>
<document />
</el-icon>
<span>Navigator Three</span>
</el-menu-item>
<el-menu-item index="4">
<el-icon>
<setting />
</el-icon>
<span>Navigator Four</span>
</el-menu-item> -->
</el-menu>
</template>
<style scoped>
.el-menu-item {
display: block;
text-align: center;
}
</style>
TypeScript
// index.vue 注册
<script setup lang="ts">
// 引入组件
import MenuVue from './menu/Menu.vue';
import HeaderVue from './header/Header.vue';
</script>
<template>
<el-container class="layout">
<el-aside width="200px" class="aside">
<!-- 调用组件 -->
<MenuVue></MenuVue>
</el-aside>
<el-container>
<el-header class="header">
<!-- 调用组件 -->
<HeaderVue></HeaderVue>
</el-header>
<el-main class="main">Main</el-main>
</el-container>
</el-container>
</template>
<style scoped>
.layout {
height: 100%;
}
.aside {
background-color: #304156;
}
.header {
background-color: lightblue;
height: 50px;
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
- 添加侧边栏Logo
TypeScript
// 新建MenuLogo.vue
<script setup lang="ts">
</script>
<template>
<div class="logo">
<!-- 图片 -->
<img src="../../assets/logo_main.png" alt="Logo">
<span class="title">信息管理系统</span>
</div>
</template>
<style scoped>
.logo {
background-color: #2b2f3a;
height: 50px;
border: none;
line-height: 50px;
display: flex;
align-items: center;
padding-left: 15px;
color: #fff;
}
.logo img {
width: 32px;
height: 32px;
margin-right: 12px;
}
.logo span {
font-weight: 600;
line-height: 50px;
font-size: 16px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
</style>
TypeScript
// Menu.vue 绑定并注册 MenuLogo
<script setup lang="ts">
import MenuLogo from './MenuLogo.vue';
</script>
<template>
<!-- 导入Logo组件 -->
<MenuLogo class="layout-logo"></MenuLogo>
<el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo" default-active="2"
text-color="#fff">
<el-sub-menu index="1">
<template #title>
<el-icon>
<location />
</el-icon>
<span>基础数据</span>
</template>
<el-menu-item index="1-1">院系管理</el-menu-item>
<el-menu-item index="1-2">专业管理</el-menu-item>
<el-menu-item index="1-3">学生管理</el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon>
<location />
</el-icon>
<span>学生管理</span>
</template>
<el-menu-item index="2-1">学生信息</el-menu-item>
<el-menu-item index="2-2">学生成绩</el-menu-item>
<el-menu-item index="2-3">学生照片</el-menu-item>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>
<el-icon>
<location />
</el-icon>
<span>用户角色</span>
</template>
<el-menu-item index="3-1">登录账号</el-menu-item>
<el-menu-item index="3-2">用户角色</el-menu-item>
<el-menu-item index="3-3">菜单管理</el-menu-item>
<el-menu-item index="3-4">权限信息</el-menu-item>
</el-sub-menu>
<!-- <el-menu-item index="2">
<el-icon><icon-menu /></el-icon>
<span>Navigator Two</span>
</el-menu-item>
<el-menu-item index="3" disabled>
<el-icon>
<document />
</el-icon>
<span>Navigator Three</span>
</el-menu-item>
<el-menu-item index="4">
<el-icon>
<setting />
</el-icon>
<span>Navigator Four</span>
</el-menu-item> -->
</el-menu>
</template>
<style scoped>
.el-menu-item {
display: block;
text-align: center;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 230px;
min-height: 400px;
}
.el-menu {
border-right: none;
}
.el-menu-item {
color: #f4f4f5 !important;
}
:deep(.el-sub-menu .el-sub-menu__title) {
color: #f4f4f5 !important;
}
/* .el-submenu .is-active .el-submenu__title {
border-bottom-color: #1890ff;
} */
:deep(.el-menu .el-menu-item) {
color: #bfcbd9;
}
/* 菜单点中文字的颜色 */
:deep(.el-menu-item.is-active) {
color: #409eff !important;
}
/* 当前打开菜单的所有子菜单颜色 */
:deep(.is-opened .el-menu-item) {
background-color: #1f2d3d !important;
}
/* 鼠标移动菜单的颜色 */
:deep(.el-menu-item:hover) {
background-color: #001528 !important;
}
/* Logo CSS部分的动画 */
@keyframes logoAnimation {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(1);
}
}
.layout-logo {
animation: logoAnimation 1s ease-out;
}
</style>
- 动态生成侧边栏菜单
TypeScript
// 新建MenuItem.vue
<script setup lang="ts">
// 导入基本模块
import { reactive } from 'vue';
// 初始化构造数据
let menuList = reactive([
{
path: "/",
meta: {
title: "首页",
icon: "HomeFilled",
},
},
{
path: "/baisc",
meta: {
title: "基础数据",
icon: "Setting",
},
children: [
{
path: "/basic/faculty",
meta: {
title: "院系信息",
icon: "Ship",
},
},
{
path: "/basic/major",
meta: {
title: "专业信息",
icon: "ShoppingBag",
},
},
{
path: "/basic/teacher",
meta: {
title: "教师信息",
icon: "ShoppingCartFull",
},
},
],
},
{
path: "/student",
meta: {
title: "学生管理",
icon: "UserFilled",
},
children: [
{
path: "/student/info",
meta: {
title: "学生信息",
icon: "VideoCameraFilled",
},
},
{
path: "/student/exam",
meta: {
title: "考试信息",
icon: "OfficeBuilding",
},
},
{
path: "/student/image",
meta: {
title: "学生照片",
icon: "TakeawayBox",
},
},
],
},
{
path: "/user",
meta: {
title: "用户角色",
icon: "Ticket",
},
children: [
{
path: "/user/account",
meta: {
title: "登录账号",
icon: "Coordinate",
},
},
{
path: "/user/roles",
meta: {
title: "角色信息",
icon: "CreditCard",
},
},
{
path: "/user/menu",
meta: {
title: "菜单管理",
icon: "DeleteLocation",
},
},
{
path: "/user/permission",
meta: {
title: "权限管理",
icon: "Goods",
},
},
],
},
]);
</script>
<template>
<template v-for="menu in menuList">
<!--
1.循环遍历menuList
2.如果children存在并且不为空及长度大于0,则创建一级菜单,反之则创建多级
3. 点击菜单,跳转到对应路径
-->
<el-sub-menu v-if="menu.children && menu.children.length>0" :index="menu.path">
<!-- 展示二级菜单第一层 -->
<template #title>
<span>{{ menu.meta.title }}</span>
</template>
<!-- 展示二级菜单第二层 -->
<!-- <template v-for="child in menu.children">
<el-menu-item :index="child.path">
<span>{{ child.meta.title }}</span>
</el-menu-item>
</template> -->
<template #default>
<el-menu-item v-for="child in menu.children" :key="child.path" :index="child.path">
<span>{{ child.meta.title }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else :index="menu.path" style="text-align: left;">
<span>{{ menu.meta.title }}</span>
</el-menu-item>
</template>
</template>
<style scoped>
.el-menu-item {
display: block;
text-align: center;
padding-right: 80px;
}
</style>
TypeScript
// Menu.vue 注册
<script setup lang="ts">
import MenuLogo from './MenuLogo.vue';
import MenuItem from './MenuItem.vue';
</script>
<template>
<!-- 导入Logo组件 -->
<MenuLogo class="layout-logo"></MenuLogo>
<el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo" default-active="2"
text-color="#fff">
<MenuItem></MenuItem>
</el-menu>
</template>
<style scoped>
.el-menu-item {
display: block;
text-align: center;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 230px;
min-height: 400px;
}
.el-menu {
border-right: none;
}
.el-menu-item {
color: #f4f4f5 !important;
}
:deep(.el-sub-menu .el-sub-menu__title) {
color: #f4f4f5 !important;
}
/* .el-submenu .is-active .el-submenu__title {
border-bottom-color: #1890ff;
} */
:deep(.el-menu .el-menu-item) {
color: #bfcbd9;
}
/* 菜单点中文字的颜色 */
:deep(.el-menu-item.is-active) {
color: #409eff !important;
}
/* 当前打开菜单的所有子菜单颜色 */
:deep(.is-opened .el-menu-item) {
background-color: #1f2d3d !important;
}
/* 鼠标移动菜单的颜色 */
:deep(.el-menu-item:hover) {
background-color: #001528 !important;
}
/* Logo CSS部分的动画 */
@keyframes logoAnimation {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(1);
}
}
.layout-logo {
animation: logoAnimation 1s ease-out;
}
</style>
- 侧边栏菜单添加图标
安装
TypeScript
npm install @element-plus/icons-vue
全局注册
TypeScript
// main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router' // 导入路由文件
import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 导入所以 icon图标
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 创建app
const app= createApp(App)
app.use(router).use(ElementPlus).mount('#app')
// 遍历所有 icon将每个图标以组件方式加载到app中
Object.keys(ElementPlusIconsVue).forEach((key) => {
app.component(key, ElementPlusIconsVue[key as keyof typeof ElementPlusIconsVue]); // 注册成全局组件
})
使用
TypeScript
// MenuItem.vue
<script setup lang="ts">
// 导入基本模块
import { reactive } from 'vue';
// 初始化数据
let menuList = reactive([
{
path: "/",
meta: {
title: "首页",
icon: "HomeFilled",
},
},
{
path: "/baisc",
meta: {
title: "基础数据",
icon: "Setting",
},
children: [
{
path: "/basic/faculty",
meta: {
title: "院系信息",
icon: "Ship",
},
},
{
path: "/basic/major",
meta: {
title: "专业信息",
icon: "ShoppingBag",
},
},
{
path: "/basic/teacher",
meta: {
title: "教师信息",
icon: "ShoppingCartFull",
},
},
],
},
{
path: "/student",
meta: {
title: "学生管理",
icon: "UserFilled",
},
children: [
{
path: "/student/info",
meta: {
title: "学生信息",
icon: "VideoCameraFilled",
},
},
{
path: "/student/exam",
meta: {
title: "考试信息",
icon: "OfficeBuilding",
},
},
{
path: "/student/image",
meta: {
title: "学生照片",
icon: "TakeawayBox",
},
},
],
},
{
path: "/user",
meta: {
title: "用户角色",
icon: "Ticket",
},
children: [
{
path: "/user/account",
meta: {
title: "登录账号",
icon: "Coordinate",
},
},
{
path: "/user/roles",
meta: {
title: "角色信息",
icon: "CreditCard",
},
},
{
path: "/user/menu",
meta: {
title: "菜单管理",
icon: "DeleteLocation",
},
},
{
path: "/user/permission",
meta: {
title: "权限管理",
icon: "Goods",
},
},
],
},
]);
</script>
<template>
<template v-for="menu in menuList">
<!--
1.循环遍历menuList
2.如果children存在并且不为空及长度大于0,则创建一级菜单,反之则创建多级
3. 点击菜单,跳转到对应路径
-->
<el-sub-menu v-if="menu.children && menu.children.length>0" :index="menu.path">
<!-- 展示二级菜单第一层 -->
<template #title>
<el-icon>
<component class="icons" :is="menu.meta.icon"></component>
</el-icon>
<span>{{ menu.meta.title }}</span>
</template>
<!-- 展示二级菜单第二层 -->
<!-- <template v-for="child in menu.children">
<el-menu-item :index="child.path">
<span>{{ child.meta.title }}</span>
</el-menu-item>
</template> -->
<template #default>
<el-menu-item v-for="child in menu.children" :key="child.path" :index="child.path">
<el-icon>
<component class="icons" :is="child.meta.icon"></component>
</el-icon>
<span style="color: aliceblue;">{{ child.meta.title }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else :index="menu.path" style="text-align: left;">
<el-icon>
<component class="icons" :is="menu.meta.icon"></component>
</el-icon>
<span>{{ menu.meta.title }}</span>
</el-menu-item>
</template>
</template>
<style scoped>
.el-menu-item {
display: block;
text-align: center;
padding-right: 80px;
}
</style>
- Header区域基本布局
TypeScript
// 图标组件定义 Collaspe.vue
<script setup lang="ts">
</script>
<template>
<!-- 使用全局定义的折叠图标 -->
<el-icon>
<component class="icons" is="Fold"></component>
</el-icon>
</template>
<style scoped>
.el-icon{
font-style: 22px;
margin-right: 10px;
}
</style>
// 面包屑导航定义 Bredcum.vue
<script setup lang="ts">
</script>
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>基础数据</el-breadcrumb-item>
<el-breadcrumb-item>院系管理</el-breadcrumb-item>
</el-breadcrumb>
</template>
<style scoped>
</style>
// 用户下拉框定义 UserInfo.vue
<script setup lang="ts">
</script>
<template>
<el-dropdown>
<span class="el-dropdown-link">
<el-icon style="margin-right: 5px;"><user-filled /></el-icon>
<span>admin</span>
<el-icon class="el-icon--right" style="margin-left: 5px;">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>用户信息</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item>安全退出</el-dropdown-item>
<!-- <el-dropdown-item disabled>Action 4</el-dropdown-item>
<el-dropdown-item divided>Action 5</el-dropdown-item> -->
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<style scoped>
</style>
TypeScript
// 父组件 Header.vue引入
<script setup lang="ts">
// 导入组件
import UserInfo from './UserInfo.vue';
import Collaspe from './Collaspe.vue';
import Bredcum from './Bredcum.vue';
</script>
<template>
<!-- 靠着横排显示将两组件放在同一个div中 -->
<div style="display: flex; align-items: center;">
<Collaspe></Collaspe>
<Bredcum></Bredcum>
</div>
<UserInfo></UserInfo>
</template>
<style scoped>
</style>
- 侧边栏收缩思路
TypeScript
控制菜单栏收缩---->Menu.vue---><el-menu :collapse=true></el-menu>
操作菜单栏收缩---->Collapse.vue
// 点击按钮实现切换(true/false) Collapse.vue
<script setup lang="ts">
// 导入vue
import {ref} from 'vue'
let isCollapsed = ref(false)
const changeCollapse = ()=>{
isCollapsed.value =!isCollapsed.value
console.log(isCollapsed.value )
}
</script>
<template>
<!-- 使用全局定义的折叠图标 -->
<el-icon>
<component class="icons" is="Fold" @click="changeCollapse"></component>
</el-icon>
</template>
<style scoped>
.el-icon{
font-style: 22px;
margin-right: 10px;
}
</style>
TypeScript
跨组件传值{
父子组件---->直接传值{
// 父组件定义传递的值
// 导入vue
import {ref} from 'vue'
// 定义待传递的值
let name = ref('admin')
// 传递给子组件
<template>
<UserInfo :username='name'></UserInfo>
</template>
// 子组件接收父组件传递过来的值
defineProps<{username: string}>();
// 调用值
{{ username }}
}
兄弟组件---->vuex
}
2. Vuex实现组件间的传值
vuex是基于vue框架的一个状态管理库。可以管理复杂应用的数据状态,比如兄弟组件的通信、多层嵌套的组件的传值等等
store---store是yuex的核心对象,它记录了整个vue应用的数据状态以及操作数据的方式。
state---就是store操作的数据状态对象,
mutation---提供了一种简单易用的同步的方式改变state的状态。
getter---获取state中数据对象
- vuex安装
TypeScript
// 安装最新版 vuex
npm install vuex@next --save
// 新建 store文件夹并在文件夹下新建 index.ts导入vuex模块
import { createStore } from 'vuex'
// 报错:/node_modules/vuex/types/index.d.ts', but this result could not be resolved when respecting package.json "exports".
// 解决报错 node_modules\vuex\package.json在exports字段中添加"types": "./types/index.d.ts"
- store对象创建
TypeScript
// 导入 vuex 模块
import { createStore } from 'vuex'
// 定义实体(存储结构)
export interface MyStore {
// 控制侧边栏收缩
collapse: boolean,
}
// 创建store对象
export const store = createStore<MyStore>({
// 初始状态
state: {
collapse: false, // 侧边栏收缩
},
// mutations 用于更改 Vuex 的 store 中的状态
mutations: {
toggleCollapse(state: MyStore) {
state.collapse =!state.collapse
},
},
// actions 用于提交 mutations,可以包含异步操作
actions: {
toggleCollapse({ commit }) {
commit('toggleCollapse')
},
},
// getters 用于获取状态的某些派生数据
getters: {
// 侧边栏是否收缩
isCollapse(state: MyStore) {
return state.collapse
},
},
})
// 暴露
export default store;
- store对象注册
TypeScript
// 注册到全局变量
import store from './store' // 导入创建的store对象
app.use(store).use(router).use(ElementPlus).mount('#app')
- 侧边栏收缩功能完成
TypeScript
// Menu.vue
// 组件中调用vuex中定义的store
// 导入usestore的方法
import { useStore } from 'vuex'
// 获取当前vuex的store对象
const store = useStore()
// 导入计算
import { computed } from 'vue'
// 获取store->state中的collapse值
const isCollapse = computed(()=>{
return store.getters['isCollapse']
})
// 在标签中实现绑定
<el-menu :collapse = isCollapse></el-menu>
// 解决侧边栏收缩 logo显示问题 侧边栏不是收缩状态时展示
<MenuLogo class="layout-logo" v-if="!isCollapse"></MenuLogo>
// Collaspe.vue
// 导入usestore的方法
import { useStore } from 'vuex'
// 获取当前vuex的store对象
const store = useStore();
// 导入计算
import { computed } from 'vue'
// 获取store->state中的collapse值
const isCollapse = computed(()=>{
return store.getters['isCollapse']
})
// 点击切换收缩事件
const changeCollapse=()=>{
// 切换collapse的值
store.commit('toggleCollapse')
}
// 使用三元表达式判断收缩图标,此时全局定义的icon图标将失效需重新导入
import { Fold,Expand } from '@element-plus/icons-vue';
<component class="icons" :is="isCollapse? Fold: Expand " @click="changeCollapse"></component>
3. Vue-router实现侧边栏导航
- 实现侧边栏导航
TypeScript
// router/index.ts 配置路由
const routes: Array<RouteRecordRaw>= [
// 首页
{
path: "/",
name: 'Layout',
component: Layout,
// 自动跳转
redirect: '/dashboard',
children:[
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/index/Dashboard.vue')
}
]
},
// 基础数据--->院系信息、专业信息、教师信息
{
path: '/basic',
name: 'Basic',
component: Layout,
children: [
{
path: '/basic/faculty',
name: 'Faculty',
component: () => import('../views/basic/Faculty.vue')
},
{
path: '/basic/major',
name: 'Major',
component: () => import('../views/basic/Major.vue')
},
{
path: '/basic/teacher',
name: 'Teacher',
component: () => import('../views/basic/Teacher.vue')
}
]
},
// 学生管理
{
path: '/student',
name:'Student',
component: Layout,
children: [
{
path: '/student/info',
name: 'StudentInfo',
component: () => import('../views/student/Info.vue')
},
{
path: '/student/exam',
name: 'StudentExam',
component: () => import('../views/student/Exam.vue')
},
{
path: '/student/image',
name: 'StudentImage',
component: () => import('../views/student/Image.vue')
}
]
},
// 用户管理
{
path: '/user',
name: 'User',
component: Layout,
children: [
{
path: '/user/account',
name: 'UserAccount',
component: () => import('../views/user/Account.vue')
},
{
path: '/user/roles',
name: 'UserRoles',
component: () => import('../views/user/Roles.vue')
},
{
path: '/user/menu',
name: 'UserMenu',
component: () => import('../views/user/Menu.vue')
},
{
path: '/user/permission',
name: 'UserPermission',
component: () => import('../views/user/Permission.vue')
}
]
}
]
TypeScript
// layout/index.vue引入路由在指定区域显示
<el-main class="main"><router-view></router-view></el-main>
// layout/menu/Menu.vue 配置点击侧边栏切换路由
<el-menu active-text-color="#ffd04b" background-color="#304156" class="el-menu-vertical-demo" default-active="2"
text-color="#fff"
:collapse = isCollapse
router
>
4. 捕获路由信息自动生成面包屑导航
- 自动生成面包屑导航
TypeScript
// 路由添加 meta字段记录标题名
const routes: Array<RouteRecordRaw>= [
// 首页
{
path: "/",
name: 'Layout',
component: Layout,
// 自动跳转
redirect: '/dashboard',
children:[
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/index/Dashboard.vue'),
meta: { title: '首页' }
}
]
},
// 基础数据--->院系信息、专业信息、教师信息
{
path: '/basic',
name: 'Basic',
component: Layout,
meta: { title: '基础数据' },
children: [
{
path: '/basic/faculty',
name: 'Faculty',
component: () => import('../views/basic/Faculty.vue'),
meta: { title: '院系信息' }
},
{
path: '/basic/major',
name: 'Major',
component: () => import('../views/basic/Major.vue'),
meta: { title: '专业信息' }
},
{
path: '/basic/teacher',
name: 'Teacher',
component: () => import('../views/basic/Teacher.vue'),
meta: { title: '教师信息' }
}
]
},
// 学生管理
{
path: '/student',
name:'Student',
component: Layout,
meta: { title: '学生管理' },
children: [
{
path: '/student/info',
name: 'StudentInfo',
component: () => import('../views/student/Info.vue'),
meta: { title: '学生信息' }
},
{
path: '/student/exam',
name: 'StudentExam',
component: () => import('../views/student/Exam.vue'),
meta: { title: '考试信息' }
},
{
path: '/student/image',
name: 'StudentImage',
component: () => import('../views/student/Image.vue'),
meta: { title: '学生照片' }
}
]
},
// 用户管理
{
path: '/user',
name: 'User',
component: Layout,
meta: { title: '用户角色' },
children: [
{
path: '/user/account',
name: 'UserAccount',
component: () => import('../views/user/Account.vue'),
meta: { title: '登录账号' }
},
{
path: '/user/roles',
name: 'UserRoles',
component: () => import('../views/user/Roles.vue'),
meta: { title: '角色信息' }
},
{
path: '/user/menu',
name: 'UserMenu',
component: () => import('../views/user/Menu.vue'),
meta: { title: '菜单管理' }
},
{
path: '/user/permission',
name: 'UserPermission',
component: () => import('../views/user/Permission.vue'),
meta: { title: '权限管理' }
}
]
}
]
TypeScript
// 动态获取面包屑导航标准流程 /layout/header/Bredcum.vue
<script setup lang="ts">
import { Ref, ref, watch } from 'vue'
// 定义 useRouter模块
import { useRoute,RouteLocationMatched} from "vue-router"
// 定义面包屑集合
const tabs: Ref<RouteLocationMatched[]> = ref([])
// 获取路由信息
const route = useRoute();
// 函数定义
const getBredCum=()=>{
// 在路由中筛选过滤路由匹配信息 --- route.metched --meta --meta.title
let matched = route.matched.filter(item=>item.meta && item.meta.title)
// 获取metched中的第一条 判断是否为首页
const first = matched[0]
// 判断是否为首页,为首页时将首页绑定在最前边
if(first.path !== '/dashboard'){
matched = [{path: '/dashboard', meta:{'title':'首页'}} as any].concat(matched)
}
// 将拼接好的值传递给tabs
tabs.value = matched
console.log(matched)
}
getBredCum();
watch(()=>route.path, ()=>getBredCum()) // 路由变化时重新获取面包屑
// watch(()=>route.matched, getBredCum, { immediate: true }) // 路由变化时重新获取面包屑
</script>
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="item in tabs">{{ item.meta.title }}</el-breadcrumb-item>
</el-breadcrumb>
<!-- <el-breadcrumb-item>首页</el-breadcrumb-item>
<el-breadcrumb-item>基础数据</el-breadcrumb-item>
<el-breadcrumb-item>院系管理</el-breadcrumb-item> -->
</template>
<style scoped>
</style>
5. 学生信息页面布局
- 顶部搜索框布局实现
TypeScript
// views/students/info.vue
<script lang="ts" setup>
import { ref, reactive } from "vue"
// 定义存储集合
var Data = reactive({
// 定义输入的查询条件
q_str: ref(""),
// 存储从后台获取的所有院系信息
FacultyOptions: reactive([
{
value:1,
label:'计算机学院'
},
{
value:2,
label:'外语学院'
},
]),
// 存储选择院系后的值
FacultySelected: ref(""),
// 存储从后台获取的所有专业信息
MajorOptions: reactive([
{
value:1,
label:'计算机专业'
},
{
value:2,
label:'外语专业'
},
]),
// 存储选择专业后的值
MajorSelected: ref("")
})
</script>
<template>
<!-- 顶部查询区域 styple="display: flax;"横向显示-->
<el-form :inline="true" class="demo-form-inline">
<el-form-item label="查询条件">
<el-input v-model="Data.q_str" placeholder="请输入查询条件" clearable />
</el-form-item>
<!-- 动态获取院系信息 -->
<el-form-item label="院系">
<el-select v-model="Data.FacultySelected" placeholder="请选择院系">
<el-option
v-for="item in Data.FacultyOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<!-- 动态获取专业信息 -->
<el-form-item label="专业">
<el-select v-model="Data.MajorSelected" placeholder="请选择专业">
<el-option
v-for="item in Data.MajorOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary">
<!-- 引入方法1 -->
<el-icon><component class="icons" is="Search"></component></el-icon>
<span>查询</span></el-button>
<el-button type="primary">
<!-- 引入方法2 -->
<el-icon><Finished /></el-icon>
<span>全部</span></el-button>
<el-button type="primary">
<el-icon><Pointer /></el-icon>
<span>添加</span></el-button>
</el-form-item>
</el-form>
</template>
<style scoped>
.demo-form-inline .el-input {
--el-input-width: 220px;
}
.demo-form-inline .el-select {
--el-select-width: 220px;
}
</style>
- 学生信息详情布局实现
TypeScript
<script lang="ts" setup>
import { ref, reactive } from "vue"
import {More,Edit,Delete} from "@element-plus/icons-vue"
// 定义存储集合
var Data = reactive({
// ===表格区域定义====
students: reactive([
{
sno:'95001',
name:'张武',
gender:'男',
birthday: '2001-10-10',
faculty: '计算机学院',
major:'计算机网络',
mobile: '13514623594',
email: '123@163.com',
address: '郑州市金水区'
},
{
sno:'95001',
name:'张武',
gender:'男',
birthday: '2001-10-10',
faculty: '计算机学院',
major:'计算机网络',
mobile: '13514623594',
email: '123@163.com',
address: '郑州市金水区'
}
]),
});
</script>
<template>
<!-- 2.表格信息部分 -->
<el-table :data="Data.students" stripe border style="width: 100%" :header-cell-style="{ backgroundColor:'#409EFF',color:'#FFF',FontSize:'14px' }">
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column prop="sno" label="学号" align="center" width="80" />
<el-table-column prop="name" label="姓名" align="center" width="80" />
<el-table-column prop="gender" label="性别" align="center" width="80" />
<el-table-column prop="birthday" label="出生日期" align="center" width="180" />
<el-table-column prop="faculty" label="院系" align="center" width="120" />
<el-table-column prop="major" label="专业" align="center" width="120" />
<el-table-column prop="mobile" label="电话" align="center" width="140" />
<el-table-column prop="email" label="Email" align="center" width="180" />
<el-table-column prop="address" label="地址" align="center"/>
<!-- 按钮区域 -->
<el-table-column label="操作" align="center">
<el-button type="primary" :icon="More" circle size="small"/>
<el-button type="warning" :icon="Edit" circle size="small"/>
<el-button type="danger" :icon="Delete" circle size="small"/>
</el-table-column>
</el-table>
</template>
<style scoped>
.demo-form-inline .el-input {
--el-input-width: 220px;
}
.demo-form-inline .el-select {
--el-select-width: 220px;
}
</style>
- 底部分页实现
TypeScript
<script lang="ts" setup>
import { ref, reactive } from "vue"
import {More,Edit,Delete} from "@element-plus/icons-vue"
// 定义存储集合
var Data = reactive({
// =====分页====
// 当前页
currentsPage: ref(1),
// 每页显示的数据量
pageSize: ref(15),
// 总数据量所有记录条数
total: ref(0),
});
// 分页中修改每页的pageSize
const handleSizeChange=()=>{}
// 分页中修改每页的currentsPage
const handleCurrentChange=()=>{}
</script>
<template>
<!-- 3.分页 currentPage4当前页 pageSize4每页大小 total记录条数 handleSizeChange改变每页大小 handleCurrentChange改变当前页 -->
<el-pagination style="margin-top: 20px;"
background
v-model:current-page="Data.currentsPage"
v-model:page-size="Data.pageSize"
:page-sizes="[10, 12, 15, 18,20,25,40,50]"
layout="total, sizes, prev, pager, next, jumper"
:total="Data.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
<style scoped>
.demo-form-inline .el-input {
--el-input-width: 220px;
}
.demo-form-inline .el-select {
--el-select-width: 220px;
}
</style>
- Element Plus中文化
TypeScript
// 全局定义
import zhCn from 'element-plus/es/locale/lang/zh-cn' // 导入中文语言包
// 创建app
const app= createApp(App)
// 添加 locale:zhCn
app.use(store).use(router).use(ElementPlus,{locale:zhCn}).mount('#app')