前言: 网上Django项目要么太老,要么不全。正好手里有个需求,借这个机会用django3.2加vue3(TS)一步步完整搭建一个图纸管理系统。一方面是记录,另一方面也回馈开源精神。希望和大家一起学习一起进步。本篇为前端相关知识和项目页面构建(最近更新7.7)
二、构建前端页面
1、工具的准备
vs code(Visual Studio Code - Code Editing. Redefined)
node.js(Node.js (nodejs.org))
vs code插件:
(1)、Chinese Language Pack——中文语言包
(2)、禁用vetur,安装Vue Language Features(Volar)
(3)、安装Element UI Snippets
2、创建项目
cmd进入自己的目标文件夹下输入 npm init vite@latest后选择输入项目名称,包,选择vue,选择ts
创建后目录如下:
用vs code打开,并运行npm install更新依赖
随后运行npm run dev测试项目是否运行成功。
运行成功后打开网页为:
3、Vue3项目介绍
项目目录结构介绍
修改访问地址
在vite.config.ts中加入server配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server:{
host:'localhost', // 也可以是192.XX.XXX.X类似的
port:8080, //随便用哪个都可以
open:true
}
})
npm常用指令
npm install XXX (安装某个包)
npm uninstall XXX (卸载某个包)
npm install vuex --save 开发环境中
npm install vuex --save-dev
npm run dev (运行测试环境)
npm run build (打包)
npm install (新项目安装依赖)
简单说下页面如何加载
主要分三个重要的文件
index.html---静态页面
main.ts---自动运行的脚本文件
app.vue---主组件,是页面入口文件
在main.ts中主要就2个
import { createApp } from 'vue' // 导入CreateApp的函数
import './style.css' // 引入App.vue -- 模块的主入口文件!
import App from './App.vue'
createApp(App).mount('#app') // 使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html的id为app的div中
我们可以在components下新建一个Demo.vue
<script lang="ts">
// 浏览器中的script部分 响应用户的操作
</script>
<template>
<!-- 相当于html中的标签,用户呈现页面的内容 -->
</template>
<style scoped>
/* 相当于html中的样式 */
</style>
此外我们可以看到在app.vue中就像引用标签一样<h1...>引用写的helloworld.vue:
一个vue文件就是一个组件,可以被其他的vue引用
我们还可以看到父组件给子组件传递数据
这边可以在Demo.vue中写入一些
<script lang="ts" setup>
// 浏览器中的script部分 响应用户的操作
</script>
<template>
<!-- 相当于html中的标签,用户呈现页面的内容 -->
<h1>Demo中的标签</h1>
</template>
<style scoped>
/* 相当于html中的样式 */
h1{
color: blue;
}
</style>
在App.vue进行引用
我们可以看到Demo中的标签引入到了主页面中
PS:scoped 是 Vue 单文件组件中的一个特殊语法,它的作用是将样式作用域限定在当前组件内部,避免样式污染和命名冲突。
Setup语法大大简化了脚本的代码,现在是不用注册,引入即注册。Setup语法糖还有定义组件props,定义响应式变量、函数、监听、计算属性。。。
4、使用Element Plus
第一步:npm install element-plus --save
我这里安装的是2.1.4版本
在package.json就可以看到安装成功
因为所以的页面都有可能引用,所以要全局设置下
第二步:在main.ts中注册
import { createApp } from 'vue' // 导入CreateApp的函数
import './style.css' // 引入App.vue -- 模块的主入口文件!
import App from './App.vue'
import ElementPlus from 'element-plus' // 引入ElementPlus
import 'element-plus/dist/index.css' // 引入ElementPlus的CSS
createApp(App).use(ElementPlus).mount('#app') // 使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html的id为app的div中
验证
如何验证呢,打开element-plus组件页面
Button 按钮 | Element Plus (gitee.io)
就选择一个按钮组件:
放到demo下
我们可以看见页面有此效果了
稍微加点佐料(提示框)
<script lang="ts" setup>
// 浏览器中的script部分 响应用户的操作
import { ElMessage, ElMessageBox } from 'element-plus'
const queryStudent=()=>{
ElMessageBox.alert('点击成功!', 'Title', {
confirmButtonText: 'OK',
});
};
</script>
<template>
<!-- 相当于html中的标签,用户呈现页面的内容 -->
<h1>Demo中的标签</h1>
<el-row class="mb-4">
<el-button>Default</el-button>
<el-button type="primary" @click="queryStudent">查询</el-button>
<el-button type="success">添加</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
</el-row>
</template>
<style scoped>
/* 相当于html中的样式 */
h1{
color: blue;
}
</style>
5、vue-router
vue-router使用步骤
第一步:npm install vue-router@4.0.14
第二步:新建路由匹配文件(是不是很像django的urls.py)
第三步:在main.ts中注册
import router from './router'
createApp(App).use(router).mount('#app')
在src文件下新建router文件夹,在放上一个加index.ts的脚本文件
例子
// 导入组件
import { createRouter,createWebHistory,RouteRecordRaw } from "vue-router"
// 导入组件
import HelloWorld from '../components/HelloWorld.vue'
import Demo from '../components/Demo.vue'
// 创建路由匹配的数据集合 -- Array
const routes:Array<RouteRecordRaw> = [
{
path:"/",
component:HelloWorld,
},
{
path:"/demo",
component:Demo,
}
]
// 创建一个vue-router的对象
const router = createRouter({
history:createWebHistory(),
routes,
})
// 暴露
export default router
main.ts导入
在app.vue中template中注释,引入
这个时候我们就发现页面地址变化会导致页面切换
PS:createWebHistory路由模式路径不带#号 createWebHashHistory路由模式路径带#号
6、管理系统后台主界面框架搭建
6.1 后台系统主界面的搭建
先在src下新建目录
然后在element-plus选择下布局容器
Container 布局容器 | Element Plus (element-plus.org)
因为我们页面分三部分选择这个。
layout的index.vue写为:
<script lang="ts" setup>
</script>
<template>
<el-container class="layout">
<el-aside class="aside" width="200px">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>
</style>
将router的index.ts修改
// 导入组件
import { createRouter,createWebHistory,RouteRecordRaw } from "vue-router"
// 导入组件
import Index from '../layout/Index.vue'
// 创建路由匹配的数据集合 -- Array
const routes:Array<RouteRecordRaw> = [
{
path:"/",
name:'Index',
component:Index,
},
]
// 创建一个vue-router的对象
const router = createRouter({
history:createWebHistory(),
routes,
})
// 暴露
export default router
app.vue
<script setup lang="ts">
</script>
<template>
<router-view></router-view>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
主页面现在为:
为了显示清晰index.vue修改下
<script lang="ts" setup>
</script>
<template>
<el-container class="layout">
<el-aside class="aside" width="200px">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: antiquewhite;
}
.header{
background-color: aquamarine;
}
.main{
background-color: azure;
}
</style>
对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>
<style>
html,body,#app{
padding: 0px;
margin: 0px;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
发现还是有边框
打开浏览器的F12发现多引入了一个style.css样式,去源项目寻找。果然!
这个时候去main.ts寻找发现果然多引入了
注释还写错了
修改下:
import { createApp } from 'vue' // 导入CreateApp的函数
import App from './App.vue' // 引入App.vue -- 模块的主入口文件!
import ElementPlus from 'element-plus' // 引入ElementPlus
import 'element-plus/dist/index.css' // 引入ElementPlus的CSS
import router from './router' // 导入router文件
createApp(App).use(router).use(ElementPlus).mount('#app') // 使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html的id为app的div中
这样显示就正常了。
搭建侧边栏
选择暗色部分代码:
<el-menu
active-text-color="#ffd04b"
background-color="#545c64"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
@open="handleOpen"
@close="handleClose"
>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>Navigator One</span>
</template>
<el-menu-item-group title="Group One">
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title>item four</template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</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>
导入到menu.vue中
<script lang="ts" setup>
// 引入组件
</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>Navigator One</span>
</template>
<el-menu-item-group title="Group One">
<el-menu-item index="1-1">item one</el-menu-item>
<el-menu-item index="1-2">item two</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="Group Two">
<el-menu-item index="1-3">item three</el-menu-item>
</el-menu-item-group>
<el-sub-menu index="1-4">
<template #title>item four</template>
<el-menu-item index="1-4-1">item one</el-menu-item>
</el-sub-menu>
</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>
</style>
在layout下的index.vue中引用
<script lang="ts" setup>
// 引用Header和Menu的组件
import HeaderVue from './header/Header.vue'
import MenuVue from './menu/Menu.vue'
</script>
<template>
<el-container class="layout">
<el-aside class="aside" width="200px"><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: antiquewhite;
}
.header{
background-color: aquamarine;
}
.main{
background-color: azure;
}
</style>
此时我们就可以看到页面变化
对menu简单修改下
<script lang="ts" setup>
</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>组1</span>
</template>
<el-menu-item index="1-1">功能1</el-menu-item>
<el-menu-item index="1-2">功能2</el-menu-item>
<el-menu-item index="1-3">功能3</el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon><location /></el-icon>
<span>组2</span>
</template>
<el-menu-item index="1-1">功能1</el-menu-item>
<el-menu-item index="1-2">功能2</el-menu-item>
<el-menu-item index="1-3">功能3</el-menu-item>
</el-sub-menu>
</el-menu>
</template>
<style scoped>
.el-menu-item{
display: block;
text-align: center;
}
</style>
结果为:
一般来说菜单是动态生成的,后面也会演示
6.2 添加侧边栏的Logo部分
在menu中新建一个MenuLogo.vue
<script lang="ts" setup>
</script>
<template>
<div class="logo">
<!-- 图片 -->
<img src="../../assets/vue.svg" alt="Logo">
<span class="title">信息管理系统</span>
</div>
</template>
<style scoped>
</style>
将MenuLogo.vue导入到Menu.vue中
<script lang="ts" setup>
// 导入组件
import MenuLogoVue from "./MenuLogo.vue";
</script>
<template>
<!-- Logo区域 -->
<MenuLogoVue></MenuLogoVue>
</template>
可以看到效果如下 logo可以自行选择喜欢的图案
再给menu.vue加点细节
<script lang="ts" setup>
// 导入vue
import { ref,computed } from 'vue';
import MenuItemVue from './MenuItem.vue';
import MenuLogoVue from './MenuLogo.vue';
// 定义一个响应式的变量
// let isCollapse = ref(false);
import { useStore } from 'vuex'
// 获取当前vuex中的store对象
const store = useStore()
// 获取store->state中的collapse值
const isCollapse = computed(() => {
return store.getters['getCollapse']
})
</script>
<template>
<!-- Logo区域 -->
<MenuLogoVue v-if="!isCollapse" class="layout-logo" ></MenuLogoVue>
<!-- 侧边栏导航 -->
<el-menu
active-text-color="#ffd04b"
background-color="#304156"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
:collapse = "isCollapse"
router
>
<MenuItemVue></MenuItemVue>
</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>
MenuLogo.vue加点细节
<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>
把index.vue中的200px改成auto
此外之前为了展示页面的底色也不要了,未来配合再修改个底色
<style scoped>
.layout{
height: 100%;
}
.aside{
background-color: #304156;
}
</style>
修改页面后,效果如何
6.3 动态生成侧边栏菜单
修复个小bug
这样侧边栏会好看很多
侧边栏有可能是从数据库取的,这边说明下动态如何生成
创建MenuItem.vue
<script lang="ts" setup>
</script>
<template>
<el-sub-menu index="1">
<template #title>
<el-icon><location /></el-icon>
<span>组1</span>
</template>
<el-menu-item index="1-1">功能1</el-menu-item>
<el-menu-item index="1-2">功能2</el-menu-item>
<el-menu-item index="1-3">功能3</el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>
<el-icon><location /></el-icon>
<span>组2</span>
</template>
<el-menu-item index="2-1">功能1</el-menu-item>
<el-menu-item index="2-2">功能2</el-menu-item>
<el-menu-item index="2-3">功能3</el-menu-item>
</el-sub-menu>
</template>
<style scoped>
.el-menu-item{
display: block;
text-align: center;
padding-right: 80px;
}
</style>
引入下:
发现效果和之前一样的。进行修改
<script lang="ts" setup>
// 导入基本模块
import {reactive} from "vue";
// 初始化数据
let menuList = reactive([
{
path: "/",
meta: {
title: "首页",
icon: "HomeFilled",
},
},
{
path: "/datasys",
meta: {
title: "数据管理",
icon: "UserFilled",
},
children: [
{
path: "/datasys/DrawingManagementSystem",
meta: {
title: "图纸信息",
icon: "VideoCameraFilled",
},
},
{
path: "/datasys/ProjectSystem",
meta: {
title: "项目管理",
icon: "OfficeBuilding",
},
},
{
path: "/datasys/FileSystem",
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">
<!-- 如果有children并且children不为空,表示是二级菜单数据,先新建el-sub-menu -->
<el-sub-menu v-if="menu.children && menu.children.length > 0" :index="menu.path">
<template #title style="padding-left: 40px">
<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">
<el-icon>
<component class="icons" :is="child.meta.icon"></component>
</el-icon>
<span>{{ child.meta.title }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<!-- 否则是一级菜单,直接新建el-sub-item -->
<el-menu-item v-else :index="menu.path">
<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>
效果为:
6.4 为侧边栏菜单加图标
点击官网找到图标区域
由于我们在上面动态生成中,已经提前加入需要的图标样式
我们只需要全局引入图标就好了
先安装包
对layout的index.vue进行修改
我们可以看到主页面果然出现了图片
但是想一想如果都是这样导入,未免有点繁琐。我们可以批量导入
对main.ts进行修改:
import { createApp } from 'vue' // 导入CreateApp的函数
import App from './App.vue' // 引入App.vue -- 模块的主入口文件!
import ElementPlus from 'element-plus' // 引入ElementPlus
import 'element-plus/dist/index.css' // 引入ElementPlus的CSS
import router from './router' // 导入router文件
import * as Icons from '@element-plus/icons-vue' // 导入所有的icon的图标
// 创建一个app
const app = createApp(App)
app.use(router).use(router).use(ElementPlus).mount('#app') // 使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html的id为app的div中
// 遍历所有的icon,把每个icon图标以组件的方式加载到app中
Object.keys(Icons).forEach((key) =>{
app.component(key, Icons[key as keyof typeof Icons])
})
页面就批量显示出来了
6.5 Header区域的基本布局
先创建三个模块
在Header.vue中导入新建的组件
<script lang="ts" setup>
// 引入组件
import BredCumVue from "./BredCum.vue";
import CollaspeVue from "./Collaspe.vue";
import UserInfoVue from "./UserInfo.vue";
</script>
<template>
<CollaspeVue></CollaspeVue>
<BredCumVue></BredCumVue>
<UserInfoVue></UserInfoVue>
</template>
<style scoped>
</style>
对Collapse.vue进行修改
<script lang="ts" setup>
</script>
<template>
<!-- 使用全局图标 -->
<el-icon>
<component class="icons" is="Fold"></component>
</el-icon>
</template>
<style scoped>
.el-icon{
font-size: 22px;
margin-right: 20px;
}
</style>
对index.vue进行修改(对上边框修改)
<script lang="ts" setup>
// 引用Header和Menu的组件
import HeaderVue from './header/Header.vue'
import MenuVue from './menu/Menu.vue'
</script>
<template>
<el-container class="layout">
<el-aside class="aside" width="auto"><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{
height: 50px;
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
效果为
导入面包屑
BredCum.vue修改为:
<script lang="ts" setup>
</script>
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item>A1</el-breadcrumb-item>
<el-breadcrumb-item>B1</el-breadcrumb-item>
<el-breadcrumb-item>C1</el-breadcrumb-item>
</el-breadcrumb>
</template>
<style scoped>
</style>
对Useinfo.vue进行修改
实际上就是个下拉菜单
<script lang="ts" setup>
</script>
<template>
<el-dropdown>
<span class="el-dropdown-link">
<el-icon style="margin-right:10px;"><user-filled /></el-icon>
<!-- <span>{{username}}</span> -->
<span>管理员</span>
<el-icon class="el-icon--right" style="margin-left:10px;">
<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-menu>
</template>
</el-dropdown>
</template>
<style scoped>
</style>
我们需要把面包屑和缩菜单的放到一个div下 这样更好看点
<script lang="ts" setup>
// 引入组件
import BredCumVue from "./BredCum.vue";
import CollaspeVue from "./Collaspe.vue";
import UserInfoVue from "./UserInfo.vue";
</script>
<template>
<div style="display:flex;align-items:center;">
<CollaspeVue></CollaspeVue>
<BredCumVue></BredCumVue>
</div>
<UserInfoVue></UserInfoVue>
</template>
<style scoped>
</style>
6.6 侧边栏收缩
对Menu.vue进行简单修改,我们就会发现页面发生了变化。
变化:
修改下:
<script lang="ts" setup>
// 导入vue
import { ref } from "vue";
// 导入组件
import MenuItemVue from "./MenuItem.vue";
import MenuLogoVue from "./MenuLogo.vue";
// 定义一个响应式的变量
let isCollapse = ref(true);
</script>
<template>
<!-- Logo区域 -->
<MenuLogoVue class="layout-logo" style="display:none;"></MenuLogoVue>
<!-- 侧边栏导航 -->
<el-menu
active-text-color="#ffd04b"
background-color="#30415"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
:collapse = isCollapse
>
<MenuItemVue></MenuItemVue>
</el-menu>
这个时候发现我们是希望通过Collapse.vue点击后,发生menu.vue的菜单收缩。
可以简单修改下:
<script lang="ts" setup>
// 导入vue
import { ref } from 'vue'
// 导入vue组件
import Components from '../../components/Index.vue';
let isCollapse = ref(false)
// 改变值的isCollapsge值
const changeCollapse=()=>{
// 改变值
isCollapse.value = ! isCollapse.value
console.log(isCollapse.value)
};
</script>
<template>
<!-- 使用全局图标 -->
<el-icon>
<component class="icons" is="Fold" @click="changeCollapse"></component>
</el-icon>
</template>
<style scoped>
.el-icon{
font-size: 22px;
margin-right: 20px;
}
</style>
这个时候我们就需要组件传值
父子组件——直接传值
兄弟组件——vuex
vuex是基于vue框架的一个状态管理库。可以管理复杂应用的数据状态,比如兄弟组件的通信、多层嵌套的组件的传值等等。
vuex有这么几个核心概念——State、Getter、Mutation、Action、Module。
store——store是vuex的核心对象,它记录了整个vue应用的数据状态以及操作数据的方式。
state——就是store操作的数据状态对象。
mutation——提供了一种简单易用的同步的方式改变state的状态,
getter——获取state中数据对象
6.7 vuex安装和store对象的创建、注册
npm install vuex@4.0.2
在src下新建一个store/index.ts
// 导入vuex模块
import { createStore } from "vuex"
// 定义一个存储数据的结构 ——实体类
export interface MyState {
collapse:boolean, // 控制侧边栏的收缩
}
// 创建一个store对象
const store = createStore<MyState>({
// state --- 存储了具体的值
state:{
collapse:false,
},
// mutations --- 修改state中值的函数
mutations:{
setCollapse(state:MyState,collapse:boolean){
state.collapse = collapse;
}
},
// getter --- 获取state值中的函数
getters:{
getCollapse(state:MyState){
return state.collapse;
}
}
})
// 暴露
export default store
这里将 store 对象暴露出来,使得其他组件可以通过 import 导入该对象,以便在其他组件中使用存储在 Vuex store 中的数据。如果没有暴露 store 对象,则其他组件无法访问该对象,也就无法使用其中存储的数据。
把我们简历的遍历全局应用
main.ts
import { createApp } from 'vue' // 导入CreateApp的函数
import App from './App.vue' // 引入App.vue -- 模块的主入口文件!
import ElementPlus from 'element-plus' // 引入ElementPlus
import 'element-plus/dist/index.css' // 引入ElementPlus的CSS
import router from './router' // 导入router文件
import * as Icons from '@element-plus/icons-vue' // 导入所有的icon的图标
import store from './store' // 导入创建的store对象
// 创建一个app
const app = createApp(App)
app.use(store).use(router).use(router).use(ElementPlus).mount('#app') // 使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html使用vue中的CreateApp方法创建一个app.vue的组件,并放置到index.html的id为app的div中
// 遍历所有的icon,把每个icon图标以组件的方式加载到app中
Object.keys(Icons).forEach((key) =>{
app.component(key, Icons[key as keyof typeof Icons])
})
6.8 完成侧边栏收缩的功能
先在menu中把组件引用进来
<script lang="ts" setup>
// 导入vue
import { ref,computed } from "vue";
// 导入组件
import MenuItemVue from "./MenuItem.vue";
import MenuLogoVue from "./MenuLogo.vue";
// 导入usestore的方法
import { useStore } from 'vuex'
// 获取当前vuex中的store对象
const store = useStore()
// 获取store->state中的collapse值
const isCollapse = computed(()=>{
return store.getters['getCollapse']
})
</script>
<template>
<!-- Logo区域 -->
<MenuLogoVue v-if="!isCollapse" class="layout-logo" ></MenuLogoVue>
<!-- 侧边栏导航 -->
<el-menu
active-text-color="#ffd04b"
background-color="#304156"
class="el-menu-vertical-demo"
default-active="2"
text-color="#fff"
:collapse = "isCollapse"
router
>
<MenuItemVue></MenuItemVue>
</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>
head/collaspe.vue
<script lang="ts" setup>
// 导入vue
import { ref,computed } from 'vue'
// 导入vue组件
import Components from '../../components/Index.vue';
// 导入useStore
import {useStore} from 'vuex'
// 获取store对象
const store = useStore();
// 获取collapse值
const isCollapse = computed(()=>{
return store.getters['getCollapse']
})
</script>
<template>
<!-- 使用全局图标 -->
<el-icon>
<component class="icons" is="Fold" @click="changeCollapse"></component>
</el-icon>
</template>
<style scoped>
.el-icon{
font-size: 22px;
margin-right: 20px;
}
</style>
因为我们需要点击后图标还要变换 所以我们需要用三元操作符
表达式?值1:值2
使用三元表达式 全局导入图标就没用了,需要单独导入
<script lang="ts" setup>
// 导入vue
import { ref,computed } from 'vue'
// 导入图标
import {Fold,Expand} from '@element-plus/icons-vue'
// 导入vue组件
import Components from '../../components/Index.vue';
// 导入useStore
import {useStore} from 'vuex'
// 获取store对象
const store = useStore();
// 定义一个响应式的变量
const localCollapse = ref(false);
// 获取collapse值
const isCollapse = computed(()=>{
// 赋值localCollapse
localCollapse.value = store.getters["getCollapse"];
return store.getters['getCollapse'];
})
// 定义函数实现修改
const changeCollapes =()=>{
// 更改localCollapse的值--取反
localCollapse.value = !localCollapse.value;
// 修改Store->state中的collapse值
store.commit('setCollapse',localCollapse.value)
}
</script>
<template>
<!-- 使用全局图标 -->
<el-icon>
<component class="icons" :is="isCollapse ? Expand:Fold" @click="changeCollapes"></component>
</el-icon>
</template>
<style scoped>
.el-icon{
font-size: 22px;
margin-right: 20px;
}
</style>
这样就可以通过点击完成收缩。
6.9 实现侧边栏的导航
首先建立所有的分页vue
那么我们的路由就要绑定这些vue
router/index.ts修改为:
// 导入组件
import { createRouter,createWebHistory,RouteRecordRaw } from "vue-router"
// 导入组件
import Layout from '../layout/Index.vue'
// 创建路由匹配的数据集合 -- Array
const routes : Array<RouteRecordRaw> = [
// 首页 ———> /dashboard/
{
path:'/',
component:Layout,
name:'Layout',
redirect:'/dashboard', //自动跳转
children:[
{
path: '/dashboard',
component: ()=>import('../views/index/Dashboard.vue'),
name:'Dashboard',
meta:{
title:'首页'
},
}
]
},
// 数据管理【图纸管理系统、项目管理系统、资料存储系统】
{
path:'/datasys',
component:Layout,
name:'Datasys',
meta:{
title:'数据管理'
},
children:[
{
path:'/datasys/DrawingManagementSystem',
component:()=>import('../views/datasys/DrawingMS.vue'),
name:'DrawingMS',
meta:{
title:'图纸管理系统'
},
},
{
path:'/datasys/ProjectSystem',
component:()=>import('../views/datasys/ProjectS.vue'),
name:'ProjectS',
meta:{
title:'项目管理系统'
},
},
{
path:'/datasys/FileSystem',
component:()=>import('../views/datasys/FileS.vue'),
name:'FileS',
meta:{
title:'资料存储系统'
},
},
]
},
// 用户角色【登录账号、角色信息、菜单管理、权限管理】
{
path:'/user',
component:Layout,
name:'User',
meta:{
title:'用户角色'
},
children:[
{
path: '/user/account',
component: ()=>import('../views/user/Account.vue'),
name:'Account',
meta:{
title:'登陆账号'
},
},
{
path: '/user/menu',
component: ()=>import('../views/user/Menu.vue'),
name:'Menu',
meta:{
title:'角色信息'
},
},
{
path: '/user/permission',
component: ()=>import('../views/user/Permission.vue'),
name:'Permission',
meta:{
title:'菜单管理'
},
},
{
path: '/user/roles',
component: ()=>import('../views/user/Roles.vue'),
name:'Roles',
meta:{
title:'权限管理'
},
},
]
},
]
// 创建一个vue-router的对象
const router = createRouter({
history:createWebHistory(),
routes,
})
// 暴露
export default router
在menu/index.vue中加main标签加入
<script lang="ts" setup>
// 引用Header和Menu的组件
import HeaderVue from './header/Header.vue'
import MenuVue from './menu/Menu.vue'
</script>
<template>
<el-container class="layout">
<el-aside class="aside" width="auto"><MenuVue></MenuVue></el-aside>
<el-container>
<el-header class="header"><HeaderVue></HeaderVue></el-header>
<el-main class="main"><router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<style scoped>
.layout{
height: 100%;
}
.aside{
background-color: #304156;
}
.header{
height: 50px;
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
这样我们就可以看到
切换效果 并显示我们的vue标签
views/index/Dashboard.vue
<script lang="ts" setup>
</script>
<template>
<div class="container">
<div class="center">
<h1>欢迎使用GVE信息管理系统</h1>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
justify-content: center;
align-items: center;
height: 50vh;
}
.center {
text-align: center;
}
</style>
views/datasys/DrawingMS.vue
<script lang="ts" setup>
</script>
<template>
<h1>DrawingManagementSystem</h1>
</template>
<style scoped>
</style>
6.10 自动生成面包屑
既然我们路由没问题了,那就可以更新面包屑了
GveInfomationSystemFE\src\layout\header\BredCum.vue
<script lang="ts" setup>
// 导入vue
import {Ref,ref,watch} from 'vue'
// 定义useRoute模块
import { useRoute,RouteLocationMatched } from "vue-router";
// 定义一个面包屑的集合
const tabs:Ref<RouteLocationMatched[]> = ref([])
// 获取当前路由的信息
const route = useRoute();
// 定义一个函数getBredCum
const getBredCum = () => {
// 过滤路由匹配的信息 --route.matched --meta,meta.title
let matched = route.matched.filter((item) => item.meta && item.meta.title);
// 获取matched中的第一条
const first = matched[0]
// 判断是不是首页
if(first.path !== '/dashboard'){
matched = [{path:'/dashboard',meta:{'title':'首页'}}as any].concat(matched)
}
// 把拼接好的值给tabs
tabs.value = matched;
};
// 自动调用getBredCum
getBredCum();
// 当请求的url的path变化,自动执行getBredCum
watch(()=>route.path,()=>getBredCum());
</script>
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="item in tabs">
{{item.meta.title}}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<style scoped>
</style>
6.11 图纸信息布局
对DrawingMS.vue进行修改
<script lang="ts" setup>
import { Tree } from 'element-ui';
// 定义树的数据
const treeData = [
{
label: 'Level one 1',
children: [
{
label: 'Level two 1-1',
children: [
{
label: 'Level three 1-1-1',
},
],
},
],
},
{
label: 'Level one 2',
children: [
{
label: 'Level two 2-1',
children: [
{
label: 'Level three 2-1-1',
},
],
},
{
label: 'Level two 2-2',
children: [
{
label: 'Level three 2-2-1',
},
],
},
],
},
];
</script>
<template>
<h1>图纸管理系统页面</h1>
<div class="container">
<div class="tree-container">
<el-tree :data="treeData"></el-tree>
</div>
<h1>表格区域</h1>
</div>
</template>
<style scoped>
.container {
display: flex;
}
.tree-container {
width: 200px;
padding: 10px;
}
</style>
加入搜索部分:
<script lang="ts" setup>
import { Tree } from "element-ui";
// 导入vue
import { ref,reactive } from 'vue'
// 定义存储的集合=====
var Data = reactive({
// 输入的查询条件
q_str:ref(""),
// 存储从后台获取的所有传感器信息
sensorOptions:reactive([
{value:1,label:'角度传感器'},
{value:2,label:'大气压力传感器'},
{value:3,label:'温度传感器'},
]),
// 存储选择院系后的值
sensorSelected:ref(""),
// 存储从后台获取的所有项目
projectOptions:reactive([
{value:1,label:'项目A'},
{value:2,label:'项目B'},
{value:3,label:'项目C'},
]),
projectSelected:ref(""),
// 存储从后台获取的所有资料
dataOptions:reactive([
{value:1,label:'资料A'},
{value:2,label:'资料B'},
{value:3,label:'资料C'},
]),
dataSelected:ref(""),
})
// 定义树的数据
const treeData = [
{
label: "Level one 1",
children: [
{
label: "Level two 1-1",
children: [
{
label: "Level three 1-1-1",
},
],
},
],
},
{
label: "Level one 2",
children: [
{
label: "Level two 2-1",
children: [
{
label: "Level three 2-1-1",
},
],
},
{
label: "Level two 2-2",
children: [
{
label: "Level three 2-2-1",
},
],
},
],
},
];
</script>
<template>
<div class="container">
<div class="sidebar">
<!-- 侧边栏 -->
<div class="tree-container">
<el-tree :data="treeData"></el-tree>
</div>
</div>
<div class="main">
<!-- 查询区域 -->
<el-form :inline="true" class="demo-form-inline" style="display: flex">
<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.sensorSelected" placeholder="请选择传感器" clearable>
<el-option
v-for="item in Data.sensorOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="项目:">
<el-select v-model="Data.projectSelected" placeholder="请选择项目" clearable>
<el-option
v-for="item in Data.projectOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="资料:">
<el-select v-model="Data.dataSelected" placeholder="请选择资料" clearable>
<el-option
v-for="item in Data.dataOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary">
<el-icon><component class="icons" is="Search"></component></el-icon>
<span>查询</span>
</el-button>
<el-button type="primary">
<el-icon><component class="icons" is="List"></component></el-icon>
<span>全部</span>
</el-button>
<el-button type="primary">
<el-icon><component class="icons" is="CirclePlus"></component></el-icon>
<span>添加</span>
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
}
.sidebar {
width: 200px;
padding: 5px;
}
.main {
flex: 1;
padding: 10px;
}
</style>
效果为:
加入表格和分页
<script lang="ts" setup>
import { Tree } from "element-ui";
// 导入vue
import { ref,reactive } from 'vue'
// 导入图标
import {Edit,More,Delete} from '@element-plus/icons-vue'
// 定义存储的集合=====
var Data = reactive({
// =========== 查询区域 =============
// 输入的查询条件
q_str:ref(""),
// 存储从后台获取的所有传感器信息
sensorOptions:reactive([
{value:1,label:'角度传感器'},
{value:2,label:'大气压力传感器'},
{value:3,label:'温度传感器'},
]),
// 存储选择院系后的值
sensorSelected:ref(""),
// 存储从后台获取的所有项目
projectOptions:reactive([
{value:1,label:'项目A'},
{value:2,label:'项目B'},
{value:3,label:'项目C'},
]),
projectSelected:ref(""),
// 存储从后台获取的所有资料
dataOptions:reactive([
{value:1,label:'资料A'},
{value:2,label:'资料B'},
{value:3,label:'资料C'},
]),
dataSelected:ref(""),
// =========== 表格区域 =============
drawing:reactive([
{material_code:'1011',drawing_name:'设计图纸',drawing_spec:'GVE1',drawing_page:'1',drawing_client_id:'D1',
drawing_version:'0001',drawing_remark:'备注',modified:'2023-05-24 15:07:32'},
{material_code:'1011',drawing_name:'设计图纸',drawing_spec:'GVE1',drawing_page:'1',drawing_client_id:'D1',
drawing_version:'0001',drawing_remark:'备注',modified:'2023-05-24 15:07:32'}
]),
// ====================== 分页 =======================
// 当前页
currentPage: ref(1),
// 每页大小
pageSize: ref(15),
// 所有的记录条数
total: ref(0),
});
//分页中修改pagesize
const handleSizeChange=(size:any)=>{
}
//分页中修改pagepage
const handleCurrentChange=(page:any)=>{
}
// 定义树的数据
const treeData = [
{
label: "Level one 1",
children: [
{
label: "Level two 1-1",
children: [
{
label: "Level three 1-1-1",
},
],
},
],
},
{
label: "Level one 2",
children: [
{
label: "Level two 2-1",
children: [
{
label: "Level three 2-1-1",
},
],
},
{
label: "Level two 2-2",
children: [
{
label: "Level three 2-2-1",
},
],
},
],
},
];
</script>
<template>
<div class="container">
<div class="sidebar">
<!-- 侧边栏 -->
<div class="tree-container">
<el-tree :data="treeData"></el-tree>
</div>
</div>
<div class="main">
<!-- 1.查询区域 -->
<el-form :inline="true" class="demo-form-inline" style="display: flex">
<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.sensorSelected" placeholder="请选择传感器" clearable>
<el-option
v-for="item in Data.sensorOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="项目:">
<el-select v-model="Data.projectSelected" placeholder="请选择项目" clearable>
<el-option
v-for="item in Data.projectOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="资料:">
<el-select v-model="Data.dataSelected" placeholder="请选择资料" clearable>
<el-option
v-for="item in Data.dataOptions"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary">
<el-icon><component class="icons" is="Search"></component></el-icon>
<span>查询</span>
</el-button>
<el-button type="primary">
<el-icon><component class="icons" is="List"></component></el-icon>
<span>全部</span>
</el-button>
<el-button type="primary">
<el-icon><component class="icons" is="CirclePlus"></component></el-icon>
<span>添加</span>
</el-button>
</el-form-item>
</el-form>
<!-- 2.表格部分 -->
<el-table :data="Data.drawing" 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="material_code" label="物料编号" width="180" />
<el-table-column prop="drawing_name" label="材料名称" width="160" />
<el-table-column prop="drawing_spec" label="规格/图纸号" width="180" />
<el-table-column prop="drawing_page" label="图纸页数" width="60" />
<el-table-column prop="drawing_client_id" label="客户编号" width="180" />
<el-table-column prop="drawing_version" label="版本号" width="70" />
<el-table-column prop="drawing_remark" label="备注" width="180" />
<el-table-column prop="modified" label="最后上传日期" width="160" />
<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>
<!-- 3.分页 -->
<!-- currentPage4【当前页】 pageSize4【每页大小】 total【记录条数】
handleSizeChange【改变pagesize】
currentPage4【当前页】
-->
<el-pagination style="margin-top:20px;"
background
v-model:current-page="Data.currentPage"
v-model:page-size="Data.pageSize"
:page-sizes="[5, 10, 15, 20]"
layout="total, sizes, prev, pager, next, jumper"
:total="Data.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<style scoped>
.container {
display: flex;
}
.sidebar {
width: 200px;
padding: 5px;
}
.main {
flex: 1;
padding: 10px;
}
</style>
效果为:
我们这时候发现页面插件是英文,一般element plus需要国际化
6.12 Element Plus国际化
直接做全局配置就好
在main.ts下
效果为:
至此前端页面的布局就完成,后面还有前后端数据交互、数据展示等。我们放在下一章编写
2023.10.7补漏:6.8需要加上
<!-- Logo区域 -->
<MenuLogoVue v-if="!isCollapse" class="layout-logo" ></MenuLogoVue>
这样就可以实现logo跳跃