这段时间以来一直在帮别人写作业,然后在github上翻到了很多别人写的源码,后端部分代码我基本上都可以看的懂。不过前端部分总是感觉很迷糊,毕竟没有系统的学过。因此:以talis为例系统学习一下Vue,ElementPlus等前端的使用
谨以此篇记录我的前端学习过程
古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。
——《晁错论》
talis前端开发 一、 Vue项目入门 1. 新建vue工程化项目 npm create vite
选择vue与JavaScript
构建项目
cd到目录下
npm install
npm run dev
这样就能看到项目的初始页面了
2. 项目文件简介 vite.config.js
:Vue项目的配置信息,如端口号package.json
:项目配置文件,包括项目名,版本号、依赖包、版本等src
源代码文件夹src/assets
静态资源目录,存放图片、字体…src/components
组件目录,存放通用组件src/App.vue
根目录src/main.js
入口文件
3. vue项目的开发访问流程 index.html
默认首页main.js
入口文件。创建vue实例,将其挂载到某个区域4. Vue的API风格 1. 选项式API 选项式APT:可以用包含多个选项的对象来描述组件的逻辑,如: data,methods, mounted等。选项定义的属性都会暴露在函数内部的this
上,它会指向当前的组件实例。
2. 组合式API 组合式API:是Vue3提供的一种基于函数的组件编写方式,通过使用函数来组织和复用组件的逻辑。它提供了一种更灵活、更可组合的方式来编写组件。
组合式API小demo
views/HelloWorld.vue
整体流程:
点击按钮时会调用increment
函数 increment
函数每点击一次就会将值++最终将++后的结果展示在页面上 <script setup > import { ref,onMounted } from 'vue' const msg = ref (0 );function increment ( ){ msg.value ++ } onMounted (()=> { console .log ('Vue mounted' ) }) </script > <template > <button @click ="increment" > {{msg}}</button > </template > <style scoped > </style >
在App.vue中引用
<script setup > import HelloWorld from './views/HelloWorld.vue' </script > <template > <HelloWorld /> </template > <style scoped > </style >
在main.js中注销全局样式引用
import { createApp } from 'vue' import App from './App.vue' createApp (App ).mount ('#app' )
页面显示
setup:是一个标识,告诉Vue需要进行一些处理,让我们可以更简洁的使用组合式AP工。 ref()︰接收一个内部值,返回一个响应式的ref对象,此对象只有一个指向内部值的属性value。 onMounted():在组合式API中的钩子方法,注册一个回调函数,在组件挂载完成后执行。 5. 组合式API小案例 🐱🐱🐱好像摸到一点点小头绪了呢好神奇
首先在views/EmpList.vue
中新建以下代码
<script setup > import { ref ,onMounted} from 'vue' import axios from 'axios' ; const name = ref ('' ); const gender = ref ('' ); const job = ref ('' ); const userList = ref ([]); async function search ( ) { const result =await axios.get (`https://web-server.itheima.net/emps/list?name=${name.value} &gender=${gender.value} &job=${job.value} ` ) userList.value = result.data .data } onMounted (() => { search (); }) </script > <template > <div id ="center" > 姓名: <input type ="text" name ="name" v-model ="name" > 性别: <select name ="gender" v-model ="gender" > <option value ="1" > 男</option > <option value ="2" > 女</option > </select > 职位: <select name ="job" v-model ="job" > <option value ="1" > 班主任</option > <option value ="2" > 讲师</option > <option value ="3" > 其他</option > </select > <input class ="btn" type ="button" value ="查询" @click ="search" > </div > <table > <thead > <tr > <th > 序号</th > <th > 姓名</th > <th > 头像</th > <th > 性别</th > <th > 职位</th > <th > 入职时间</th > <th > 更新时间</th > </tr > <tr v-for ="(user, index) in userList" :key ="user.id" > <td > {{index + 1}}</td > <td > {{user.name}}</td > <td > <img :src ="user.image" > </td > <td > <span v-if ="user.gender == 1" > 男</span > <span v-else-if ="user.gender == 2" > 女</span > <span v-else > 其他</span > </td > <td > <span v-if ="user.job == 1" > 班主任</span > <span v-else-if ="user.job == 2" > 讲师</span > <span v-else-if ="user.job == 3" > 学工主管</span > <span v-else-if ="user.job == 4" > 教研主管</span > <span v-else-if ="user.job == 5" > 咨询师</span > <span v-else > 其他</span > </td > <td > {{user.entrydate}}</td > <td > {{user.updatetime}}</td > </tr > </thead > </table > </template > <style scoped > table ,th ,td { border : 1px solid #000 ; border-collapse : collapse; line-height : 50px ; text-align : center; } #center ,table { width : 60% ; margin : auto; } #center { margin-bottom : 20px ; } img { width : 50px ; } input ,select { width : 17% ; padding : 10px ; margin-right : 30px ; border : 1px solid #ccc ; border-radius : 4px ; } .btn { background-color : #ccc ; } </style >
页面就会显示
6. ElementPlus 组件地址
1. 导入步骤 常见vue项目
npm install element-plus –save 在main.js中引用
import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' createApp ( App ).use (ElementPlus ).mount ( ' #app ' )
2. 表格组件 好像没有啥好说的基本上就是抄抄抄的代码
<script setup > const tableData = [ {date : '2016-05-03' ,name : 'Tom' ,address : 'No. 189, Grove St, Los Angeles' ,}, { date : '2016-05-02' , name : 'Tom' ,address : 'No. 189, Grove St, Los Angeles' , }, { date : '2016-05-04' , name : 'Tom' , address : 'No. 189, Grove St, Los Angeles' ,}, {date : '2016-05-01' ,name : 'Tom' ,address : 'No. 189, Grove St, Los Angeles' ,}, ] </script > <template > <div class ="mb-4" > <el-button > Default</el-button > <el-button type ="primary" > Primary</el-button > <el-button type ="success" > Success</el-button > <el-button type ="info" > Info</el-button > <el-button type ="warning" > Warning</el-button > <el-button type ="danger" > Danger</el-button > </div > <div class ="mb-4" > <el-button plain > Plain</el-button > <el-button type ="primary" plain > Primary</el-button > <el-button type ="success" plain > Success</el-button > <el-button type ="info" plain > Info</el-button > <el-button type ="warning" plain > Warning</el-button > <el-button type ="danger" plain > Danger</el-button > </div > <div class ="mb-4" > <el-button round > Round</el-button > <el-button type ="primary" round > Primary</el-button > <el-button type ="success" round > Success</el-button > <el-button type ="info" round > Info</el-button > <el-button type ="warning" round > Warning</el-button > <el-button type ="danger" round > Danger</el-button > </div > <div class ="mb-4" > <el-button :icon ="Search" circle /> <el-button type ="primary" :icon ="Edit" circle /> <el-button type ="success" :icon ="Check" circle /> <el-button type ="info" :icon ="Message" circle /> <el-button type ="warning" :icon ="Star" circle /> <el-button type ="danger" :icon ="Delete" circle /> </div > <div class ="mb-4" > <el-table :data ="tableData" stripe style ="width: 100%" > <el-table-column prop ="date" label ="日期" width ="180" align ="center" /> <el-table-column prop ="name" label ="姓名" width ="180" align ="center" /> <el-table-column prop ="address" label ="住址" width ="300" align ="center" /> </el-table > </div > </template > <style scoped > .mb-4 { margin-bottom : 10px ; } </style >
3. 分页条组件 <script setup > import { ref } from 'vue' const currentPage4 = ref (1 ) const pageSize4 = ref (10 ) const small = ref (false )const background = ref (false )const disabled = ref (false )const total = ref (40 )const handleSizeChange = (val ) => { console .log (`每页展示:${val} ` ) } const handleCurrentChange = (val ) => { console .log (`当前页码: ${val} ` ) } </script > <template > <div class ="mb-4" > <el-pagination v-model:current-page ="currentPage4" v-model:page-size ="pageSize4" :page-sizes ="[10, 20, 30, 40]" :small ="small" :disabled ="disabled" :background ="background" layout ="total, sizes, prev, pager, next, jumper" :total ="total" @size-change ="handleSizeChange" @current-change ="handleCurrentChange" /> </div > </template > <style scoped > .mb-4 { margin-bottom : 10px ; } </style >
天下苦秦已久矣!
劳资学ElementPlus就是为了解决这个分页条组件英文问题
之前翻人家写的代码一直不知道在哪里改,现在我就知道啦!
呜呜呜终于等到你,还好我没放弃。
4. 对话框组件 就是抄抄抄
<script setup > import { ref,reactive} from 'vue' const tableData = [ { date : '2016-05-03' , name : 'Tom' , address : 'No. 189, Grove St, Los Angeles' }, { date : '2016-05-02' , name : 'Tom' , address : 'No. 189, Grove St, Los Angeles' }, { date : '2016-05-04' , name : 'Tom' , address : 'No. 189, Grove St, Los Angeles' }, { date : '2016-05-01' , name : 'Tom' , address : 'No. 189, Grove St, Los Angeles' }, ] const dialogTableVisible = ref (false )<template> <!-- 对话框 --> <div class ="mb-4" > <el-button plain @click ="dialogTableVisible = true" > 打开对话框</el-button > <el-dialog v-model ="dialogTableVisible" title ="配送地址" width ="800" > <el-table :data ="tableData" > <el-table-column property ="date" label ="日期" width ="150" /> <el-table-column property ="name" label ="姓名" width ="200" /> <el-table-column property ="address" label ="地址" /> </el-table > </el-dialog > </div > </template>
显示效果
5. 表单组件 抄抄抄我抄
<script setup > import { ref, reactive } from 'vue' const formInline = reactive ({ name : '' , gender : '' , birthday : '' , }) const onSubmit = ( ) => { console .log (formInline) } </script > <template > <div class ="mb-4" > <el-form :inline ="true" :model ="formInline" class ="demo-form-inline" > <el-form-item label ="姓名" > <el-input v-model ="formInline.name" placeholder ="你叫啥" clearable /> </el-form-item > <el-form-item label ="性别" > <el-select v-model ="formInline.gender" placeholder ="男/女/沃尔玛购物袋" clearable > <el-option label ="男" value ="1" /> <el-option label ="女" value ="2" /> <el-option label ="沃尔玛购物袋" value ="0" /> </el-select > </el-form-item > <el-form-item label ="出生日期" > <el-date-picker v-model ="formInline.birthday" type ="date" placeholder ="选一个日期" value-format ="YYYY-MM-DD" clearable /> </el-form-item > <el-form-item > <el-button type ="primary" @click ="onSubmit" > 提交</el-button > </el-form-item > </el-form > </div > </template > <style scoped > .mb-4 { margin-bottom : 10px ; } .demo-form-inline .el-input { --el-input-width : 220px ; } .demo-form-inline .el-select { --el-select-width : 220px ; } </style >
最终效果
6. ElementPlus的小案例
我来挑战一下嘿嘿
分析主要是要有表单与表格组件,参考json定义变量名
"code" : 1 , "msg" : "success" , "data" : [ { "id" : 1 , "name" : "谢逊" , "image" : "https://web-framework.oss-cn-hangzhou.aliyuncs.com/2023/4.jpg" , "gender" : 1 , "job" : "1" , "entrydate" : "2023-06-09" , "updatetime" : "2025-05-25T08:11:11" } ,
1.定义表单内容 <script setup > import { ref,reactive } from 'vue' const formInline = reactive ({ name : '' , gender : '' , birthday : '' , }) const onSubmit = ( ) => { console .log (formInline) } </script > <template > <div class ="mb-4" > <el-form :inline ="true" :model ="formInline" class ="demo-form-inline" > <el-form-item label ="姓名" > <el-input v-model ="formInline.name" placeholder ="你叫啥" clearable /> </el-form-item > <el-form-item label ="性别" > <el-select v-model ="formInline.gender" placeholder ="男/女/沃尔玛购物袋" clearable > <el-option label ="男" value ="1" /> <el-option label ="女" value ="2" /> <el-option label ="沃尔玛购物袋" value ="0" /> </el-select > </el-form-item > <el-form-item label ="出生日期" > <el-date-picker v-model ="formInline.birthday" type ="date" placeholder ="选一个日期" value-format ="YYYY-MM-DD" clearable /> </el-form-item > <el-form-item > <el-button type ="primary" @click ="onSubmit" > 提交</el-button > </el-form-item > </el-form > </div > </template > <style scoped > .demo-form-inline .el-input { --el-input-width : 220px ; } .demo-form-inline .el-select { --el-select-width : 220px ; } </style >
但是我的格式好像不太对,会适应屏幕
我去翻翻文档有没有固定的
文档没有但是找到了其他解决方案
嗯嗯很完美
2. 完成表格内容 <script setup > import { ref, onMounted } from 'vue' import axios from 'axios' ; const name = ref ('' ); const gender = ref ('' ); const job = ref ('' ); const userList = ref ([]) async function search ( ) { const result =await axios.get (`https://web-server.itheima.net/emps/list?name=${name.value} &gender=${gender.value} &job=${job.value} ` ) userList.value = result.data .data } function onClean ( ){ name.value = '' gender.value = '' job.value = '' search () } onMounted (() => { search (); }) </script > <template > <div class ="mb-3" > <div class ="mb-4" style ="width: 1500px;" > <el-form :inline ="true" :model ="formInline" class ="demo-form-inline" > <el-form-item label ="姓名" > <el-input v-model ="name" placeholder ="请输入姓名" clearable /> </el-form-item > <el-form-item label ="性别" > <el-select v-model ="gender" placeholder ="请选择" clearable > <el-option label ="男" value ="1" /> <el-option label ="女" value ="2" /> </el-select > </el-form-item > <el-form-item label ="职位" > <el-select v-model ="job" placeholder ="请选择" clearable > <el-option label ="班主任" value ="1" /> <el-option label ="讲师" value ="2" /> <el-option label ="其他" value ="3" /> </el-select > </el-form-item > <el-form-item > <el-button type ="primary" @click ="search" > 查询</el-button > </el-form-item > <el-form-item > <el-button type ="primary" @click ="onClean" > 清空</el-button > </el-form-item > </el-form > <el-table :data ="userList" border style ="width: 1030px;" > <el-table-column prop ="date" label ="ID" width ="100" /> <el-table-column prop ="name" label ="姓名" width ="100" /> <el-table-column prop ="address" label ="头像" width ="100" /> <el-table-column prop ="address" label ="性别" width ="100" /> <el-table-column prop ="address" label ="职位" width ="100" /> <el-table-column prop ="address" label ="入职日期" width ="180" /> <el-table-column prop ="address" label ="更新日期" width ="180" /> </el-table > </div > </div > </template > <style scoped > .demo-form-inline .el-input { --el-input-width : 220px ; } .demo-form-inline .el-select { --el-select-width : 220px ; } .mb-3 { border : 1px solid red; width : 90% ; max-width : 1300px ; position : absolute; left : 50% ; transform : translateX (-50% ); } </style >
完成了表格内容,不过页面显示的很不对劲
目前只有姓名是显示正常的,其他都不太行我在改改
<el-table-column prop ="date" label ="ID" width ="100" /> <el-table-column prop ="name" label ="姓名" width ="100" /> <el-table-column prop ="address" label ="头像" width ="100" /> <el-table-column prop ="address" label ="性别" width ="100" /> <el-table-column prop ="address" label ="职位" width ="100" /> <el-table-column prop ="address" label ="入职日期" width ="180" /> <el-table-column prop ="address" label ="更新日期" width ="180" />
好像发现问题了,我这里没有绑定嘿嘿
3. 修改表格绑定内容 <el-table :data ="userList" border style ="width: 1030px;" > <el-table-column prop ="id" label ="ID" width ="100" /> <el-table-column prop ="name" label ="姓名" width ="100" /> <el-table-column prop ="image" label ="头像" width ="100" /> <el-table-column prop ="gender" label ="性别" width ="100" /> <el-table-column prop ="job" label ="职位" width ="100" /> <el-table-column prop ="entrydate" label ="入职日期" width ="180" /> <el-table-column prop ="updatetime" label ="更新日期" width ="180" /> </el-table >
哇塞哇塞有效果了,不过图片不显示,感觉像是啥属性没有配置的原因,我去翻翻文档
好像发现了,我改改
4. 更改图片属性 啊?啊?啊?
这里去官方文档里找的代码没有成功回显图片,显示失败
查看了老师的代码
<el-table-column prop ="image" label ="头像" width ="100" > <template #default ="scope" > <img :src ="scope.row.image" height ="40px" > </template > </el-table-column >
嗯…咱也不知道为啥反正就实现了
测试功能一切正常
5. 最终效果
<script setup > import { ref, onMounted } from 'vue' import axios from 'axios' ;const name = ref ('' );const gender = ref ('' ); const job = ref ('' );const userList = ref ([])async function search ( ) { const result = await axios.get (`https://web-server.itheima.net/emps/list?name=${name.value} &gender=${gender.value} &job=${job.value} ` ) userList.value = result.data .data } function onClean ( ) { name.value = '' gender.value = '' job.value = '' search () } onMounted (() => { search (); }) </script > <template > <div class ="mb-4" style ="max-width: 1200px; margin: 0 auto; min-height: 100vh; display: flex; flex-direction: column; align-items: center; margin-top: 200px;" > <el-form :inline ="true" :model ="formInline" class ="demo-form-inline" > <el-form-item label ="姓名" > <el-input v-model ="name" placeholder ="请输入姓名" clearable /> </el-form-item > <el-form-item label ="性别" > <el-select v-model ="gender" placeholder ="请选择" clearable > <el-option label ="男" value ="1" /> <el-option label ="女" value ="2" /> </el-select > </el-form-item > <el-form-item label ="职位" > <el-select v-model ="job" placeholder ="请选择" clearable > <el-option label ="班主任" value ="1" /> <el-option label ="讲师" value ="2" /> <el-option label ="其他" value ="3" /> </el-select > </el-form-item > <el-form-item > <el-button type ="primary" @click ="search" > 查询</el-button > </el-form-item > <el-form-item > <el-button type ="primary" @click ="onClean" > 清空</el-button > </el-form-item > </el-form > <el-table :data ="userList" border style ="width: 1030px;" > <el-table-column prop ="id" label ="ID" width ="110" align ="center" /> <el-table-column prop ="name" label ="姓名" width ="110" align ="center" /> <el-table-column label ="头像" width ="140" align ="center" > <template #default ="scope" > <img :src ="scope.row.image" height ="80px" width ="80px" > </template > </el-table-column > <el-table-column label ="性别" width ="110" align ="center" > <template #default ="scope" > {{ scope.row.gender === 1 ? '男' : '女' }} </template > </el-table-column > <el-table-column label ="职位" width ="110" align ="center" > <template #default ="scope" > <span v-if ="scope.row.job == 1" > 班主任</span > <span v-else-if ="scope.row.job == 2" > 讲师</span > <span v-else-if ="scope.row.job == 3" > 学工主管</span > <span v-else-if ="scope.row.job == 4" > 教研主管</span > <span v-else-if ="scope.row.job == 5" > 咨询师</span > <span v-else > 其他</span > </template > </el-table-column > <el-table-column prop ="entrydate" label ="入职日期" width ="220" align ="center" /> <el-table-column prop ="updatetime" label ="更新日期" width ="230" align ="center" /> </el-table > </div > </template > <style scoped > .demo-form-inline .el-input { --el-input-width : 220px ; } .demo-form-inline .el-select { --el-select-width : 220px ; } </style >
使用自定义模板prop标签可以删除
二、tlias侧边栏开发 小知识点 在老师的vue文件中有这样的一个代码
import Layout from "@/views/layout/index.vue" ;
这里的@就是完美Src的目录文件夹,定义在了vite.config.js
文件中
export default defineConfig ({ plugins : [vue ()], resolve : { alias : { '@' : fileURLToPath (new URL ('./src' , import .meta .url )) } } })
老师的页面布局代码使用的是Element的布局,需要在文档查看
1. 左侧侧边栏 1. 左侧菜单基本定义 <el-aside width ="200px" class ="aside" > <el-menu :default-openeds ="['1', '3']" > <el-sub-menu index ="1" > <template #title > <el-icon > <message /> </el-icon > Navigator One </template > <el-menu-item index ="1-1" > Option 1</el-menu-item > <el-menu-item index ="1-2" > Option 2</el-menu-item > </el-sub-menu > </el-menu > </el-aside >
左侧侧边栏大致长这样,需要注意的是
<el-menu :default-openeds ="['1', '3']" >
代表的是默认展开,索引为1和3,开发过程中不需要展开,在这里就给删除了
<script setup > </script > <template > <div class ="common-layout" > <el-container > <el-header class ="header" > <span class ="title" > Tlias智能学习辅助系统</span > <span class ="right_tool" > <a href ="" > <el-icon > <EditPen /> </el-icon > 修改密码 | </a > <a href ="" > <el-icon > <SwitchButton /> </el-icon > 退出登录 </a > </span > </el-header > <el-container > <el-aside width ="200px" class ="aside" > <el-menu > <el-menu-item index ="/index" > <el-icon > <Promotion > </Promotion > </el-icon > 首页 </el-menu-item > <el-sub-menu index ="/manage" > <template #title > <el-icon > <Menu /> </el-icon > 班级学员管理 </template > <el-menu-item index ="/clazz" > <el-icon > <HomeFilled /> </el-icon > 班级管理</el-menu-item > <el-menu-item index ="/stu" > <el-icon > <UserFilled /> </el-icon > 学员管理</el-menu-item > </el-sub-menu > <el-sub-menu index ="/system" > <template #title > <el-icon > <Tools /> </el-icon > 系统信息管理 </template > <el-menu-item index ="/dept" > <el-icon > <HelpFilled /> </el-icon > 部门管理</el-menu-item > <el-menu-item index ="/emp" > <el-icon > <Avatar /> </el-icon > 员工管理</el-menu-item > </el-sub-menu > <el-sub-menu index ="/report" > <template #title > <el-icon > <Histogram /> </el-icon > 数据统计管理 </template > <el-menu-item index ="/empReport" > <el-icon > <InfoFilled /> </el-icon > 员工信息统计</el-menu-item > <el-menu-item index ="/stuReport" > <el-icon > <Share /> </el-icon > 学员信息统计</el-menu-item > <el-menu-item index ="/log" > <el-icon > <Document /> </el-icon > 日志信息统计</el-menu-item > </el-sub-menu > </el-menu > </el-aside > <el-main > 右侧核心展示区域 </el-main > </el-container > </el-container > </div > </template > <style scoped > .header { background-image : linear-gradient (to right, #00547d , #007fa4 , #00aaa0 , #00d072 , #a8eb12 ); } .title { color : white; font-size : 40px ; font-family : 楷体; line-height : 60px ; font-weight : bolder; } .right_tool { float : right; line-height : 60px ; } a { color : white; text-decoration : none; } .aside { width : 220px ; border-right : 1px solid #ccc ; height : 730px ; } </style >
定义完之后就长这样基本上就是抄代码
2. 动态菜单(Router路由) Router 路由知识点 router组成
Router实例:路由实例,基于createRouter函数创建,维护了应用的路由信息。
eg: /stu: /views/stu/index.vue 当配置stu时会访问的路径
<router-link>
:路由链接组件,浏览器会解析成<a>
。
eg: <router-link to='/emp'>
员工管理</router-link>
会被解析为超链接标签,点击会跳转(当点击员工管理会跳转emp页面。然后这个标签会去查找我们的实例中定义的/emp的组件路径)
<router-view>
:动态视图组件,用来渲染展示与路由路径对应的组件。
eg: <router-view></router-view>
你想在哪里展示组件就在那里加这个标
1. 开启router <el-menu router>
自需要在菜单栏上添加一个router既可以跳转了,好神奇
2.配置路由index import { createRouter, createWebHistory } from 'vue-router' import IndexView from '@/views/index/index.vue' import clazzView from '@/views/clazz/index.vue' import DeptView from '@/views/dept/index.vue' import EmpView from '@/views/emp/index.vue' import LogView from '@/views/log/index.vue' import stuView from '@/views/stu/index.vue' import EmpReportView from '@/views/report/emp/index.vue' import stuReportView from '@/views/report/stu/index.vue' import LayoutView from '@/views/layout/index.vue' import LoginView from '@/views/login/index.vue' const router = createRouter ({ history : createWebHistory (import .meta .env .BASE_URL ), routes : [ { path : '/index' , name : 'index' , component : IndexView }, { path : '/clazz' , name : 'clazz' , component : clazzView }, { path : '/stu' , name : 'stu' , component : stuView }, { path : '/dept' , name : 'dept' , component : DeptView }, { path : '/emp' , name : 'emp' , component : EmpView }, { path : '/log' , name : 'log' , component : LogView }, { path : '/empReport' , name : 'empReport' , component : EmpReportView }, { path : '/stuReport' , name : 'stuReport' , component : stuReportView }, { path : '/login' , name : 'login' , component : LoginView } ] }) export default router
导入路由然后配置路径
3. 在需要展示的地方导入视图 // 右侧核心区 <el-main > <router-view > </router-view > </el-main >
4.页面展示
神奇!
3. 嵌套路由 案例引出:当我们在页面访问登录功能时会在右侧盒子中显示登陆页面。这肯定是不符合要求的,推测要在登录页面展示这个路由。
原因:我们在app.vue中引入了默认页面layout。当页面打开时默认显示layout,猜测修改:使用index包裹layout(卧槽卧槽卧槽)
老师说不能把这个页面写死,也就是说不要在app.vue中固定页面(猜错了嘿嘿)
1. 动态路由组件 在app.vue中填写
<router-view></router-view>
当匹配到哪个组件就需要在页面中展示哪个组件
咦~现在当我打开页面时变成空白了,也就是说没有指定默认访问页面
推测修改:可能要在里面添加Layout组件
2. 路由嵌套
也就是说要在大路由中嵌套layout
{path :'/' , name :'' , component : LayoutView , redirect :'/index' , children :[ {path :'index' ,name :'index' ,component :IndexView }, {path :'clazz' ,name :'clazz' ,component :clazzView}, {path :'dept' ,name :'dept' ,component :DeptView }, {path :'emp' ,name :'emp' ,component :EmpView }, {path :'log' ,name :'log' ,component :LogView }, {path :'stuReport' ,name :'stuReport' ,component :stuView}, {path :'empReport' ,name :'empReport' ,component :stuReportView}, ] }, { path : '/login' , name : 'login' , component : LoginView } ] })
当我们访问根路径时,默认访问的是LayoutView布局的页面,也就是我们最开始的页面。然后就能愉快的访问里面的页面啦。当我们访问login时也能正常显示 路由中配置了redirect
,也就是说当我们访问layout的布局内容是,会重定向到index页面 不过感觉这里应该会加拦截器相关的内容🍔(看到大汉堡突然想吃汉堡了😋)
三、部门管理-查询 1. 页面分析
呜呼呜呼呜呼呜呼
部门管理页面首先上面有一个部门管理标题 一个新增按钮(点击新增会有提示框) 4列表格 编辑,删除按钮 2. 页面布局开发 标题
直接使用的h1标签
新增部门按钮,使用Ele的按钮组件
这里不需要div吗,感觉也太紧凑了吧
为按钮设置一个div然后设置外边距
<div class ="container" > <el-button type ="primary" > +新增部门</el-button > </div >
.container { margin : 20px 0px ; }
这样就好看哆啦
新增表格组件
拷贝表格组件代码
设置type
属性为index即可从1开始自增
设置操作
当我们使用表格标签时,默认显示的不能满足我们的需求,我们这就需要自定义标签(默认展示的是文字,我们现在要展示按钮)
完整布局代码
<script setup > import { ref } from "vue" ;const tableData = ref ([ { id : 1 , name : "学工部" , createTime : "2022-09-01T23:06:29" , updateTime : "2022-09-01T23:06:29" , }, ]); </script > <template > <h1 > 部门管理</h1 > <div class ="container" > <el-button type ="primary" > +新增部门</el-button > </div > <div class ="container" > <el-table :data ="tableData" border style ="width: 100%" > <el-table-column type ="index" label ="序号" width ="100" align ="center" /> <el-table-column prop ="name" label ="部门名称" width ="260" align ="center" /> <el-table-column prop ="updateTime" label ="最后操作时间" width ="300" align ="center" /> <el-table-column label ="操作" align ="center" > <template #default ="scope" > <el-button type ="primary" size ="small" > <el-icon > <Edit /> </el-icon > 编辑 </el-button > <el-button type ="danger" size ="small" > <el-icon > <Delete /> </el-icon > 删除 </el-button > </template > </el-table-column > </el-table > </div > </template > <style scoped > .container { margin : 20px 0px ; } </style >
太漂亮啦嘿嘿
3. 列表查询-加载数据 根据需求,需要在打开页面之后,需要自动加载全部部门数据,展示在表格中。
使用ApiFox创建mock模拟数据 当我们前端在开发中,假设后端还没开发完毕,这时候我们想要动态查询数据可以使用ApiFox提供的mock模拟后端生成的数据
打开ApiFox(没有就下载)
新建get请求接口,导入响应回来的接口数据(参考接口文档)
然后点击mock的云端在里面进行开启
我忘记截屏了,不过里面有一个停用按钮,我把开启就可以了(我选的是响应式优先)
具体请观看老师的视频:
在ApiFox中定义完接口还需要回到前端进行引用。这里要用到的技术是axios
完整代码
<script setup > import { ref ,onMounted} from "vue" ;import axios from "axios" ;onMounted (() => { search (); }); const search =async ( ) => { const result = await axios.get ("https://m1.apifoxmock.com/m1/6421010-6118327-default/depts" ); if (result.data .code == 1 ) { deptList.value = result.data .data ; } else { this .$message .error (result.data .message ); } }; const deptList = ref ([]);</script > <template > <h1 > 部门管理</h1 > <div class ="container" > <el-button type ="primary" > +新增部门</el-button > </div > <div class ="container" > <el-table :data ="deptList" border style ="width: 100%" > <el-table-column type ="index" label ="序号" width ="100" align ="center" /> <el-table-column prop ="name" label ="部门名称" width ="260" align ="center" /> <el-table-column prop ="updateTime" label ="最后操作时间" width ="300" align ="center" /> <el-table-column label ="操作" align ="center" > <template #default ="scope" > <el-button type ="primary" size ="small" > <el-icon > <Edit /> </el-icon > 编辑 </el-button > <el-button type ="danger" size ="small" > <el-icon > <Delete /> </el-icon > 删除 </el-button > </template > </el-table-column > </el-table > </div > </template > <style scoped > .container { margin : 20px 0px ; } </style >
页面显示(我的分辨率好像有问题,感觉页面显示的太小了)
这里使用axios来进行异步请求,使用钩子函数来触发。
神奇!
4. 程序优化 当前程序问题:URL是写死的不方便维护
1. 代码分析 改进方案1
将异步请求提取到一个工具类中,这样就可以根据这个工具类来获取信息了
太短了的话后端打断点会报错。
改进方案2
将与服务器进行异步请求交互的逻辑封装在一个单独的api中
懂了懂了,全都懂了。我终于可以看懂别人的前端代码了
2. 具体代码 src/api/dept.js
import request from "@/utils/request" ;export const queryAllApi = ( ) => { return request.get ('/depts' ); };
src/utils/request.js
import axios from 'axios' ;const request = axios.create ({ baseURL : 'https://m1.apifoxmock.com/m1/6421010-6118327-default' , timeout : 600000 }); request.interceptors .response .use ( (response ) => { return response.data ; }, (error ) => { return Promise .reject (error); } ); export default request;
src/views/dept/index.vue
<script setup > import { ref ,onMounted} from "vue" ;import { queryAllApi} from "@/api/dept" ;onMounted (() => { search (); }); const search =async ( ) => { const result = await queryAllApi (); deptList.value = result.data ; }; const deptList = ref ([]);</script >
页面正常显示数据,注意是在src目录下新建包,不是在views里面
3. 反向代理 卧槽这几集全是干货啊,好爽好爽好好爽
经过上面的优化基础功能实现了,不过还没有解决跨域问题,以及URL在src中写死的问题,修改request.js的访问路径为/api
同时修改vite.config.js代码解决跨域问题,访问后端8080路径
import { fileURLToPath, URL } from 'node:url' ; import { defineConfig } from 'vite' ; import vue from '@vitejs/plugin-vue' ; export default defineConfig ({ plugins : [ vue (), ], resolve : { alias : { '@' : fileURLToPath (new URL ('./src' , import .meta .url )) } }, server : { proxy : { '/api' : { target : 'http://localhost:8080' , secure : false , changeOrigin : true , rewrite : (path ) => path.replace (/^\/api/ , '' ), } } } });
这一段相当于固定代码
完成以上步骤请打开你的tlias后端尝试前后端联调吧🐱🐱🐱
要注释掉有关后端拦截器的代码,否则会报令牌校验错误
我这边前后端联调没有问题
呜呜呜谢谢老师,谢谢黑马。感觉离开发又近了一步
四、部门管理-新增 1. 页面原型
新增时点击会调用后端的新增操作
需要一个Dialog对话框组件和一个From表单组件
2. 新增部门 1. 定义dialog <el-dialog v-model ="dialogFormVisible" :title ="fromTitle" width ="500" > <el-form :model ="dept" > <el-form-item label ="部门名称" :label-width ="80" > <el-input v-model ="dept.name" /> </el-form-item > </el-form > <template #footer > <div class ="dialog-footer" > <el-button @click ="dialogFormVisible = false" > 取消</el-button > <el-button type ="primary" @click ="save" > 确定</el-button > </div > </template > </el-dialog >
这里需要注意的是:
当我们点击新增按钮时,需要将对话框显示的调用出来。因此需要在新增按钮上添加点击事件,当点击时将按钮显示出来。然后在页面点击取消按钮会隐藏对话框 然后需要在保存按钮上添加保存方法。当点击时会调用后端的插入方法 为了我们的Dialog对话框能实现复用效果。将对话框的标题设置为了动态绑定效果,当点击新增时,将标题渲染为新增部门 const dialogFormVisible = ref (false );const dept = ref ({name :'' });const fromTitle = ref ('' );const save = ( ) => {}; const addDept = ( ) => { dialogFormVisible.value = true ; fromTitle.value = '新增部门' ; };
2. 导入消息组件 import { ElMessage } from "element-plus" ;--- if (result.code ) { ElMessage .success ("添加成功" ); dialogFormVisible.value = false ; search (); }else { ElMessage .error (result.msg ); }
3. 初始代码功能完成(未完成表单校验功能) const dialogFormVisible = ref (false );const dept = ref ({name :'' });const fromTitle = ref ('' );const save = async ( ) => { const result = await addApi (dept.value ); if (result.code ) { ElMessage .success ("添加成功" ); dialogFormVisible.value = false ; search (); }else { ElMessage .error (result.msg ); } }; const addDept = ( ) => { dialogFormVisible.value = true ; fromTitle.value = '新增部门' ; dept.value = {name :'' }; };
emm 简单来描述一下吧
首先我们在Ele上面copy了代码过来,这时候我们是不是要声明这个组件,将值默认设为false(默认关闭的) 那么我们怎么才能开启这个对话框呢?聪明的你肯定想到了。哎我们在点击新增部门时把对话框显示不就好了 那么这样我们是不是要在新增部门按钮上添加一个点击方法呀。定义一个新增部门的方法。当用户点击新增部门时将对话框的值设为true(这样就能显示对话框了) 同时呢我们还需要声明这个部门模块。<el-form :model="dept">
这时候当我们想要复用dialog表单的话,我们就要将标题设为动态(不再赘述) 部门名称填写完毕,这时候当我们点击保存按钮是不是要发送数据到后端? 使用我们就要在api.js里面定义后端方法。定义完成后再引用。 声明这个保存方法,然后使用await
修饰这个方法,并且将我们部门名称这个值作为参数传入后端。当后端返回码为1时,我们就使用导入的消息通知给出提示插入成功,同时关闭对话框。否则给出失败提示(失败提示是后端返回的提示) 然后需要清空表单 4. 新增优化
1. 效果展示 const rules = ref ({ name : [ { required : true , message : "请输入部门名称" , trigger : "blur" }, { min : 2 , max : 10 , message : "长度在 2 到 10 个字符" , trigger : "blur" }, ], });
<el-form :model ="dept" :rules ="rules" > <el-form-item label ="部门名称" :label-width ="80" prop ="name" >
然后指定校验规则,然后将prop设置为里面具体值
2. 表单规则校验拦截 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊我直接战吼起手…太几把折磨人了
好的现在我们一起来看一下这个校验规则上一条我们完成了表单校验,不过有个bug,就是虽然页面可以校验但是不能拦截,当我们点击保存时还是可以插入信息。下面我们来解决这个问题
在表单上挂载ref="deptFormRef"
然后声明这个变量,这样我们就可以对这个表单进行操作了 当我们表单为空时直接返回 不为空就调用这个验证规则 从我的表述中我可以发现我这一块没有听懂 <script setup > import { ref, onMounted } from "vue" ;import { queryAllApi, addApi } from "@/api/dept" ;import { ElMessage } from "element-plus" ;onMounted (() => { search (); }); const search = async ( ) => { const result = await queryAllApi (); deptList.value = result.data ; }; const deptList = ref ([]);const dialogFormVisible = ref (false );const dept = ref ({ name : '' });const fromTitle = ref ('' );const save = async ( ) => { if (!deptFormRef.value ) { return } deptFormRef.value .validate (async (valid) => { if (valid) { const result = await addApi (dept.value ); if (result.code ) { ElMessage .success ("添加成功" ); dialogFormVisible.value = false ; search (); } else { ElMessage .error (result.msg ); } }else { ElMessage .error ("表单校验未通过" ); } }); }; const addDept = ( ) => { dialogFormVisible.value = true ; fromTitle.value = '新增部门' ; dept.value = { name : '' }; if (deptFormRef.value ){ deptFormRef.value .resetFields (); } }; const rules = ref ({ name : [ { required : true , message : "请输入部门名称" , trigger : "blur" }, { min : 2 , max : 10 , message : "长度在 2 到 10 个字符" , trigger : "blur" }, ], }); const deptFormRef = ref ();</script > <template > <h1 > 部门管理</h1 > <div class ="container" > <el-button type ="primary" @click ="addDept" > +新增部门</el-button > </div > <div class ="container" > <el-table :data ="deptList" border style ="width: 100%" > <el-table-column type ="index" label ="序号" width ="100" align ="center" /> <el-table-column prop ="name" label ="部门名称" width ="260" align ="center" /> <el-table-column prop ="updateTime" label ="最后操作时间" width ="300" align ="center" /> <el-table-column label ="操作" align ="center" > <template #default ="scope" > <el-button type ="primary" size ="small" > <el-icon > <Edit /> </el-icon > 编辑 </el-button > <el-button type ="danger" size ="small" > <el-icon > <Delete /> </el-icon > 删除 </el-button > </template > </el-table-column > </el-table > </div > <el-dialog v-model ="dialogFormVisible" :title ="fromTitle" width ="500" > <el-form :model ="dept" :rules ="rules" ref ="deptFormRef" > <el-form-item label ="部门名称" :label-width ="80" prop ="name" > <el-input v-model ="dept.name" /> </el-form-item > </el-form > <template #footer > <div class ="dialog-footer" > <el-button @click ="dialogFormVisible = false" > 取消</el-button > <el-button type ="primary" @click ="save" > 确定</el-button > </div > </template > </el-dialog > </template > <style scoped > .container { margin : 20px 0px ; } </style >
完整代码
五、部门管理-修改部门 1. 页面原型
点击编辑按钮,根据ID进行查询,弹出对话框,完成页面回显展示。(查询回显) 点击确定按钮,保存修改后的数据,完成数据更新操作。(保存修改) 2. 分析-查询回显 3. 分析-修改 当我们修改点击Dialog对话框确定时是不是要保存我们修改的信息。那么我们是不是要在确定按钮上添加点击事件。这时候就发现 我们已经在确定按钮上绑定点击事件了。那么什么时候我们需要修改方法,什么时候我们需要保存方法呢?就是当我们页面中有这个数据那我们就用修改方法,当页面没有这个数据就为保存方法
基于这个思路我们就要修改保存按钮中的代码
具体代码
const save = async ( ) => { if (!deptFormRef.value ) { return } deptFormRef.value .validate (async (valid) => { if (valid) { let result; if (dept.value .id ){ result= await updateApi (dept.value ); }else { result = await addApi (dept.value ); } if (result.code ) { ElMessage .success ("添加成功" ); dialogFormVisible.value = false ; search (); } else { ElMessage .error (result.msg ); } }else { ElMessage .error ("表单校验未通过" ); } }); };
对比
就是根据这个id进行判断,然后调用不同的api
学到了
六、部门管理-删除部门 1. 页面原型
2. 具体思路代码 删除时我们前端是要给出信息提示是否真的删除,当点击确定时调用api中的删除api,向后端发送删除信息即可(我的前端后端还感觉好混乱,我写前端还在想是咋在前端写的删除功能😵)
const delById = async (id )=>{ const result = await ElMessageBox .confirm ('此操作将永久删除该部门, 是否继续?' , '提示' , { confirmButtonText : '确定' , cancelButtonText : '取消' , type : 'warning' , }).then (async () => { const result = await deleteApi (id); if (result.code ){ ElMessage .success ('删除成功' ); search (); }else { ElMessage .error (result.msg ); } }).catch (() => { ElMessage .info ('已取消删除' ); }); }
给删除按钮绑定点击事件,要通过scope获取这一行的id。
然后就是一些信息提示代码。仔细查看就会明白了,这里不再赘述
七、员工管理-员工查询 1.页面原型
2. 接口文档 请求参数
响应数据
3. 搜索栏制作 1. 基础页面设置 遇到的错误: 性别不显示
当我第一次测试代码时我发现我的性别框无法显示
奶奶的,找了好长时间为啥,然后发现把官网下面的CSS代码拷过来就解决了
烤嫩羊!
注意时间要规定格式 value-format=“YYYY-MM-DD”
整体流程就是抄代码
<script setup > import { ref } from 'vue' ;const searchEmp = ref ({ name : '' , gender : undefined , date : [] })const search = ( ) =>{ } const clear = ( ) =>{ searchEmp.value ={name : '' , gender : undefined , date : []}; search (); } </script > <template > <h1 > 员工管理</h1 > <div class ="container" > <el-form :inline ="true" :model ="searchEmp" class ="demo-form-inline" > <el-form-item label ="姓名" > <el-input v-model ="searchEmp.name" placeholder ="请输入员工姓名" /> </el-form-item > <el-form-item label ="性别" > <el-select v-model ="searchEmp.gender" placeholder ="请选择男女" > <el-option label ="男" value ="1" /> <el-option label ="女" value ="2" /> </el-select > </el-form-item > <el-form-item label ="入职时间" > <el-date-picker v-model ="searchEmp.date" type ="daterange" range-separator ="到" start-placeholder ="开始日期" end-placeholder ="结束日期" :size ="size" value-format ="YYYY-MM-DD" /> </el-form-item > <el-form-item > <el-button type ="primary" @click ="search" > 查询</el-button > <el-button type ="info" @click ="clear" > 清空</el-button > </el-form-item > </el-form > </div > </template > <style scoped > .demo-form-inline .el-input { --el-input-width : 180px ; } .demo-form-inline .el-select { --el-select-width : 180px ; } .container { margin : 15px 0px ; } </style >
2. 优化data数据块(事件监听) 上一模块中,我们完成了页面的基本功能。不过还有一个功能需要解决,就是我们接口文档中的开始时间结束时间为begin和end,而我们的对象中定义的为数组。我们该怎么把data中的数组中的值给变成begin和end
方案一 :当点击查询按钮时,获取数组中的值,将第一个赋值给begin,第二个赋值给end
方案二 :在对象中增加两个属性(begin,end)。如果data数组中的数值发生变化(初始为null)就将第一个值给begin,第二个给end
知识点:Watch监听 作用:侦听一个或多个响应式数据源 ,并在数据源变化时调用传入的回调函数 。
用法:
导入watch函数 执行watch函数,传入要侦听的响应式数据源(ref对象)和回调函数;
第一个参数为数据源,第二个参数就是回调函数,回调函数可以传递两个值
第一个参数newVal
代表响应式数据变化前的数据,第二个oldVal
代表变化之后的值
那么这样我们就可以通过watch监听,来监听响应式数据a的变化。一旦a发生变化就会调用后面的函数
当我们要监听对象的全部属性时可以使用深度监听在方法末尾添加,{deep:true}
表示对象的任意属性变化都会执行后面的函数
使用watch监听函数监听日期 watch (()=> searchEmp.value .date ,(newVal,oldVal )=> { if (newVal.length == 2 ){ searchEmp.value .begin =newVal[0 ]; searchEmp.value .end =newVal[1 ]; }else { searchEmp.value .begin ='' ; searchEmp.value .end ='' ; } })
监听单个对象:监听serchEmp的值里面的数据,当里面数据发生变化时newVal就会被调用
那么当日期发生变化时我们的newVal就会变化。那么就将newVal里面的第一个值给begin,第二个给newVal
这样我们的开始结束就获取到了值
4.表单制作(详细版) 为了让我下次写表单时有个参考,这里我会写的很详细
第一步:在官网找到表单项下载源码拷贝到代码里,最好包裹一个div里面 <div class ="container" > <el-table :data ="tableData" border style ="width: 100%" > <el-table-column prop ="date" label ="姓名" width ="180" /> <el-table-column prop ="name" label ="" width ="180" /> <el-table-column prop ="address" label ="Address" width ="180" /> <el-table-column prop ="address" label ="Address" /> </el-table > </div >
初始代码长这样
第二步:table里面的data里面的tableData
代表表格数据来源。那么我们这里就声明一个empList,代表表格的数据来源。那么这里就将data里面的数据改一改名字。然后去声明对象。这里要考虑:因为要传多个值,所以要声明数组,然后去查看接口文档看看具体是哪些的值
根据这个文档我们来定义数组函数,将需要的值作为参数(里面的值在后面要删除,这里只是示例用于测试的数据,后面查询时需要将里面替换为后端传入到数据)
const empList=ref ([ { "id" : 0 , "username" : "string" , "password" : "string" , "name" : "string" , "gender" : 0 , "job" : 0 , "salary" : 0 , "image" : "string" , "entryDate" : "2019-08-24" , "deptId" : 0 , "deptName" : "string" , "createTime" : "2019-08-24T14:15:22.123Z" , "updateTime" : "2019-08-24T14:15:22.123Z" } ])
然后根据参数我们去完善表格的列元素
prop是元素名称,label是表格名称。必要时要使用自定义表格。使用align="center"
让元素居中显示。
删除修改按钮使用的是自定义列表
<div class ="container" > <el-table :data ="empList" border style ="width: 100%" > <el-table-column prop ="name" label ="姓名" width ="120" align ="center" /> <el-table-column prop ="gender" label ="性别" width ="120" align ="center" /> <el-table-column prop ="image" label ="头像" width ="120" align ="center" /> <el-table-column prop ="deptName" label ="所属部门" width ="120" align ="center" /> <el-table-column prop ="job" label ="职位" width ="120" align ="center" /> <el-table-column prop ="entryDate" label ="入职日期" width ="180" align ="center" /> <el-table-column prop ="updateTime" label ="最后操作时间" width ="200" align ="center" /> <el-table-column label ="操作" align ="center" > <template #default ="scope" > <el-button type ="primary" size ="small" @click ="" > <el-icon > <Edit /> </el-icon > 编辑</el-button > <el-button type ="danger" size ="small" @click ="" > <el-icon > <Delete /> </el-icon > 删除 </el-button > </template > </el-table-column > </el-table > </div >
在表头添加复选框
<el-table-column type ="selection" width ="55" align ="center" />
使用自定义列模板优化性别展示框
<el-table-column label ="性别" width ="120" align ="center" > <template #default ="scope" > {{ scope.row.gender ==1?'男':'女'}} </template > </el-table-column >
使用scope.row
获取一行中的性别,当等于1时显示男,否则显示女
使用自定义列模板完善图像处理
<el-table-column label ="头像" width ="120" align ="center" > <template #default ="scope" > <img :src ="scope.row.image" height ="40px" > </template > </el-table-column >
使用img
标签,然后别忘了添加高度,不然会很大
使用自定义模板定义职位
<el-table-column prop ="job" label ="职位" width ="120" align ="center" > <template #default ="scope" > <span v-if ="scope.row.job==1" > 班主任</span > <span v-else-if ="scope.row.job==2" > 班主任</span > <span v-else-if ="scope.row.job==3" > 学工主管</span > <span v-else-if ="scope.row.job==4" > 教研主管</span > <span v-else-if ="scope.row.job==5" > 咨询师</span > <span v-else > 其他</span > </template > </el-table-column >
5. 分页条制作 <div class ="container" > <el-pagination v-model:current-page ="currentPage" v-model:page-size ="pageSize" :page-sizes ="[5,10, 20, 30, 50,75,100]" :background ="background" layout ="total, sizes, prev, pager, next, jumper" :total ="total" @size-change ="handleSizeChange" @current-change ="handleCurrentChange" /> </div >
const currentPage = ref (1 ) const pageSize = ref (10 ) const background = ref (true ) const total =ref (0 ) const handleSizeChange = (val ) => { console .log (`每页展示${val} 条记录 ` ) } const handleCurrentChange = (val ) => { console .log (`当前页码: ${val} ` ) }
这样我们的页面布局就完成了
6. 列表查询 1. 查询功能页面分析 我们要实现查询就要先分析有哪些功能需要查询
首先进入页面需要查询,根据条件进行查询,清空时需要查询,每页记录数时需要查询页码切换时需要查询
2. 功能书写 我们要写查询功能时是不是要向后端传输请求。那么我们就要定义api了是不是。那么打开api文件夹新建emp.js
的api,在里面来写查询相关请求。根据请求参数模板来定义请求方式
import request from "@/utils/request" ;export const queryPageApi =(name,gender,begin,end,page,pageSize )=> request.get (`/emps?name=${name} &gender=${gender} &begin=${begin} &end=${end} &page=${page} &pageSize=${pageSize} ` )
定义完请求之后需要在index.vue
里面引用
import {queryPageApi} from '@/api/emp'
查询员工方法
引用之后我们就要在声明search方法。在search方法里面声明一个result方法,使用await来修饰,并且将参数声明进去,将表单里面的数据传入请求路径中
当返回值结果为200时:我们要将返回值结果中的数据传入到表格对象的值里面去。并且我们还需要将分页相关的功能动态传入分页相关内容中去
最后将声明的empList里面的数据删除掉即可
const search = async ( ) => { const result =await queryPageApi (searchEmp.value .name ,searchEmp.value .gender , searchEmp.value .begin ,searchEmp.value .end ,currentPage.value ,pageSize.value ); if (result.code ){ empList.value =result.data .rows ; total.value =result.data .total } } const empList = ref ([])
声明钩子函数,这样在页面加载完毕时就会自动触发搜索
onMounted (()=> { search (); })
在页面与记录数发生变化时查询数据
const handleSizeChange = (val ) => { search (); } const handleCurrentChange = (val ) => { search (); }
这样查询功能基本没有问题了
八、 员工管理-新增员工 1. 页面布局
2. 接口文档
请求方式为post,路径为/emps
3. Dialog 对话框 1. 页面基本布局 新增员工的对话框与修改员工的对话框可以共用
这里老师选择Copy准备好的代码,为了锻炼自己我决定自己敲一下
手敲对话框历程 我抄我抄我抄
完整代码示例
<script setup > const employee = ref ({ username : '' , name : '' , gender : '' , phone : '' , job : '' , salary : '' , deptId : '' , entryDate : '' , image : '' , exprList : [] }) const dialogVisible = ref (false )const dialogTitle = ref ('新增员工' ) const handleAvatarSuccess = (response ) => { console .log (response); } const beforeAvatarUpload = (rawFile ) => { if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png' ) { ElMessage .error ('只支持上传图片' ) return false } else if (rawFile.size / 1024 / 1024 > 10 ) { ElMessage .error ('只能上传10M以内图片' ) return false } return true } </script > <template > <el-dialog v-model ="dialogVisible" :title ="dialogTitle" > <el-form :model ="employee" label-width ="80px" > <el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="用户名" > <el-input v-model ="employee.username" placeholder ="请输入员工用户名,2-20个字" > </el-input > </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="姓名" > <el-input v-model ="employee.name" placeholder ="请输入员工姓名,2-10个字" > </el-input > </el-form-item > </el-col > </el-row > <el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="性别" > <el-select v-model ="employee.gender" placeholder ="请选择性别" style ="width: 100%;" > <el-option label ="男" value ="1" > </el-option > <el-option label ="女" value ="2" > </el-option > </el-select > </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="手机号" > <el-input v-model ="employee.phone" placeholder ="请输入员工手机号" > </el-input > </el-form-item > </el-col > </el-row > <el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="职位" > <el-select v-model ="employee.job" placeholder ="请选择职位" style ="width: 100%;" > <el-option label ="班主任" value ="1" > </el-option > <el-option label ="讲师" value ="2" > </el-option > <el-option label ="学工主管" value ="3" > </el-option > <el-option label ="教研主管" value ="4" > </el-option > <el-option label ="咨询师" value ="5" > </el-option > </el-select > </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="薪资" > <el-input v-model ="employee.salary" placeholder ="请输入员工薪资" > </el-input > </el-form-item > </el-col > </el-row > <el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="所属部门" > <el-select v-model ="employee.deptId" placeholder ="请选择部门" style ="width: 100%;" > <el-option label ="研发部" value ="1" > </el-option > <el-option label ="市场部" value ="2" > </el-option > </el-select > </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="入职日期" > <el-date-picker v-model ="employee.entryDate" type ="date" style ="width: 100%;" placeholder ="选择日期" format ="YYYY-MM-DD" value-format ="YYYY-MM-DD" > </el-date-picker > </el-form-item > </el-col > </el-row > <el-row :gutter ="20" > <el-col :span ="24" > <el-form-item label ="头像" > <el-upload class ="avatar-uploader" action ="/api/upload" :show-file-list ="false" :on-success ="handleAvatarSuccess" :before-upload ="beforeAvatarUpload" > <img v-if ="employee.image" :src ="employee.image" class ="avatar" /> <el-icon v-else class ="avatar-uploader-icon" > <Plus /> </el-icon > </el-upload > </el-form-item > </el-col > </el-row > <el-row :gutter ="10" > <el-col :span ="24" > <el-form-item label ="工作经历" > <el-button type ="success" size ="small" @click ="" > + 添加工作经历</el-button > </el-form-item > </el-col > </el-row > <el-row :gutter ="3" > <el-col :span ="10" > <el-form-item size ="small" label ="时间" label-width ="80px" > <el-date-picker type ="daterange" range-separator ="至" start-placeholder ="开始日期" end-placeholder ="结束日期" format ="YYYY-MM-DD" value-format ="YYYY-MM-DD" > </el-date-picker > </el-form-item > </el-col > <el-col :span ="6" > <el-form-item size ="small" label ="公司" label-width ="60px" > <el-input placeholder ="请输入公司名称" > </el-input > </el-form-item > </el-col > <el-col :span ="6" > <el-form-item size ="small" label ="职位" label-width ="60px" > <el-input placeholder ="请输入职位" > </el-input > </el-form-item > </el-col > <el-col :span ="2" > <el-form-item size ="small" label-width ="0px" > <el-button type ="danger" > - 删除</el-button > </el-form-item > </el-col > </el-row > </el-form > <template #footer > <span class ="dialog-footer" > <el-button @click ="dialogVisible = false" > 取消</el-button > <el-button type ="primary" @click ="" > 保存</el-button > </span > </template > </el-dialog > </template > <style scoped > .avatar { height : 40px ; } .avatar-uploader .avatar { width : 78px ; height : 78px ; display : block; } .avatar-uploader .el-upload { border : 1px dashed var (--el-border-color); border-radius : 6px ; cursor : pointer; position : relative; overflow : hidden; transition : var (--el-transition-duration-fast); } .avatar-uploader .el-upload :hover { border-color : var (--el-color-primary); } .el-icon .avatar-uploader-icon { font-size : 28px ; color : #8c939d ; width : 78px ; height : 78px ; text-align : center; border-radius : 10px ; border : 1px dashed var (--el-border-color); } </style >
这样我们的对话框组件就完成了
2. 显示对话框 完成了我们的表单项目是不是要当点击添加员工时要显示对话框
const addEmp = ( )=>{ dialogVisible.value =true ; dialogTitle.value ='新增员工' ; } <el-button type="primary" @click="addEmp" >+新增员工</el-button>
3.表单解析 一行里面是怎么只显示两列的
<el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="用户名" > <el-input v-model ="employee.username" placeholder ="请输入员工用户名,2-20个字" > </el-input > </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="姓名" > <el-input v-model ="employee.name" placeholder ="请输入员工姓名,2-10个字" > </el-input > </el-form-item > </el-col >
使用el-row
布局方式,代表一行,里面有两个el-col
代表有两列
el-col :span="12"
代表一行所占的宽度。总计为24
块。这里的两个代表是一行2个
gutter="20"
代表分栏间隔
4.页面布局优化(性别,职位,所属部门) 性别,职位,所属部门这些都不应该是页面写死的,性别与职位可以单独定义出来,所属部门由后端传入。
性别与职位是规定死的,而所属部门是动态的可以通过添加部门动态添加),因此部门相关要在后端处理
1. 性别与职位 在index.vue
中定义性别与职位的元数据
const jobs = ref ([{ name : '班主任' , value : 1 },{ name : '讲师' , value : 2 },{ name : '学工主管' , value : 3 },{ name : '教研主管' , value : 4 },{ name : '咨询师' , value : 5 },{ name : '其他' , value : 6 }])const genders = ref ([{ name : '男' , value : 1 }, { name : '女' , value : 2 }])
然后修改之前的定义,改为使用v-for进行动态查找。这样当我们需要添加性别与职位时就可以在表头进行修改了
<el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="性别" > <el-select v-model ="employee.gender" placeholder ="请选择性别" style ="width: 100%;" > <el-option v-for ="g in genders" :key ="g.value" :label ="g.name" :value ="g.value" > </el-option > </el-select > </el-form-item > </el-col > <el-col :span ="12" > <el-form-item label ="职位" > <el-select v-model ="employee.job" placeholder ="请选择职位" style ="width: 100%;" > <el-option v-for ="j in jobs" :key ="j.value" :label ="j.name" :value ="j.value" > </el-option > </el-select > </el-form-item > </el-col >
使用v-for来遍历我们上面的对象
v-for="j in jobs"
: j就是变量名称 in jobs就是对象名称
:key="j.value"
注意这里要加:号,进行动态绑定,将jobs的value作为唯一标识
:label="j.name"
动态绑定具体是那个名字 。对应<el-option label=“研发部” value=“1”>
:value="j.value"
动态绑定他们的值。 对应<el-option label=“研发部” value=“1”>
正常回显,没有问题啦🐻❄️🐻❄️
2. 所属部门动态优化 部门与部门管理的部门内容会向关联,那么什么时候需要展示呢? 当我们点击编辑和新增时都需要展示部门,因此可以在页面加载时就加载部门。那么就在钩子函数中定义查询部门数据
查询所有部门数据首先要声明一个部门数组,便于我们后面的引用
既然要查询所有部门是不是要导入查询部门的API。这里的名称会与员工查询向混淆,所以起了别名
import { queryAllApi as queryAllDeptApi } from '@/api/dept'
导入进来了我们肯定要用啊是不是,那么就声明一个查询所有部门的函数方法。调用api查询出结果,然后将结果给我们上面声明函数
const queryAllDepts = async ( ) =>{ const result=await queryAllDeptApi (); if (result.code ){ depts.value =result.data ; } }
然后就要在钩子函数中引用
onMounted (() => { search (); queryAllDepts (); })
最后使用for循环遍历
<el-select v-model="employee.deptId" placeholder="请选择部门" style="width: 100%;" > <el-option v-for ="d in depts" :key ="d.id" :label ="d.name" :value ="d.id" > </el-option > </el-select>
这里的id和name参照接口文档
最终页面显示
5. 图片上传相关 <el-row :gutter="20" > <el-col :span ="24" > <el-form-item label ="头像" > <el-upload class ="avatar-uploader" action ="/api/upload" :show-file-list ="false" :on-success ="handleAvatarSuccess" :before-upload ="beforeAvatarUpload" > <img v-if ="employee.image" :src ="employee.image" class ="avatar" /> <el-icon v-else class ="avatar-uploader-icon" > <Plus /> </el-icon > </el-upload > </el-form-item > </el-col > </el-row>
class="avatar-uploader"
给一个class属性,方便我们属性css的相关
action="/api/upload"
我们在上传文件时要调用的那个接口,请求地址
:show-file-list="false"
是否展示已经上传的文件列表
:on-success="handleAvatarSuccess"
文件上传成功调用的函数方法
:before-upload="beforeAvatarUpload"
文件上传之前要调用的函数(检查格式与大小是否符合要求)
const handleAvatarSuccess = (response ) => { console .log (response); } const beforeAvatarUpload = (rawFile ) => { if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png' ) { ElMessage .error ('只支持上传图片' ) return false } else if (rawFile.size / 1024 / 1024 > 10 ) { ElMessage .error ('只能上传10M以内图片' ) return false } return true }
校验了文件类型是不是图片,不是就返回错误信息。然后查看文件大小是否小于10M,然后阻止上传
头像回显逻辑 <img v-if ="employee.image" :src="employee.image" class ="avatar" /> <el-icon v-else class ="avatar-uploader-icon" >
这里先做一个判断v-if="employee.image"
里面有没有值呢?有就展示图片,没有就展示一个+号的图标
那么我们该如何回显图片呢?我们只需要将我们上传图片的URL传给image属性值就可以了
那么我们就在文件上传成功时把URL给image属性即可
const handleAvatarSuccess = (response ) => { employee.value .image =response.data ; }
这里的response.data;
为啥就是图像路径呢,可能与系统设计的有关。返回的就是一个URL
6. 工作经历的页面交互 1. 页面原型
当点击新增按钮时会往exprList[]
数组中新增一条数据。
当点击删除按钮时会在exprList[]
数组中删除一条数据
vue是基于数据驱动视图展示的
2. 实现思路 既然我们要点击添加工作经历就新增一个表格那么我们是不是需要绑定一个点击事件
<el-row :gutter="10" > <el-col :span ="24" > <el-form-item label ="工作经历" > <el-button type ="success" size ="small" @click ="addExprItem" > + 添加工作经历</el-button > </el-form-item > </el-col > </el-row>
绑定完点击事件然后就要去声明
const addExprItem =( )=>{ employee.value .exprList .push ({ company :'' ,job :'' ,begin :'' ,end :'' ,exprDate :[] }); }
声明一个点击事件,点击新增按钮是就调用employee
对象里面的值中的exprList
然后使用push
函数就可以新增数据了。
这里面要放公式,职位,开始时间,结束时间,以及一个时间数组
欸?这时候就要问了,这个时间数组是干嘛的??
我们页面中的开始时间和结束时间使用的是一个范围日期选择器。所以这个返回时间会存放进那个时间数组。然后就要使用我们的watch
监听事件去监听数组变化,在将数组中的值传入开始时间结束时间里面去
哇可以了耶。
页面新增添加工作经历表格
既然我们已经成功可以添加员工工作经历了,那么我们该如何让页面回显呢?
嘿嘿我们使用一个for循环不就好啦。循环遍历这个数组元素然后回显到页面上
<!-- 第七行 ... 工作经历 --> <el-row :gutter ="3" v-for ="expr in employee.exprList" > <el-col :span ="10" > <el-form-item size ="small" label ="时间" label-width ="80px" > <el-date-picker type ="daterange" v-model ="expr.exprDate" range-separator ="至" start-placeholder ="开始日期" end-placeholder ="结束日期" format ="YYYY-MM-DD" value-format ="YYYY-MM-DD" > </el-date-picker > </el-form-item > </el-col > <el-col :span ="6" > <el-form-item size ="small" label ="公司" label-width ="60px" > <el-input placeholder ="请输入公司名称" v-model ="expr.company" > </el-input > </el-form-item > </el-col > <el-col :span ="6" > <el-form-item size ="small" label ="职位" label-width ="60px" > <el-input placeholder ="请输入职位" v-model ="expr.job" > </el-input > </el-form-item > </el-col > <el-col :span ="2" > <el-form-item size ="small" label-width ="0px" > <el-button type ="danger" > - 删除</el-button > </el-form-item > </el-col > </el-row > </el-form>
在工作经历上添加for循环,循环遍历数组。使用v-model
来绑定数据
删除功能
当我们点击某一个按钮时就要删除某一条工作经历
所以我们要先往删除按钮上绑定点击事件
<el-form-item size="small" label-width="0px" > <el-button type ="danger" @click ="delExprItem" > - 删除</el-button > </el-form-item>
那我们怎么样才能指定某一条进行删除呢?
我们可以在调用这个函数时将当前的索引传递过去。那我们这么样才能获取到这个数据呢
我们可以根据v-for来获取当前索引,当我们遍历时拿到这个索引,在删除时将索引传递给删除按钮
<el-row :gutter ="3" v-for ="(expr,index) in employee.exprList" > <el-col :span ="10" > <el-form-item size ="small" label ="时间" label-width ="80px" > <el-date-picker type ="daterange" v-model ="expr.exprDate" range-separator ="至" start-placeholder ="开始日期" end-placeholder ="结束日期" format ="YYYY-MM-DD" value-format ="YYYY-MM-DD" > </el-date-picker > </el-form-item > </el-col > <el-col :span ="6" > <el-form-item size ="small" label ="公司" label-width ="60px" > <el-input placeholder ="请输入公司名称" v-model ="expr.company" > </el-input > </el-form-item > </el-col > <el-col :span ="6" > <el-form-item size ="small" label ="职位" label-width ="60px" > <el-input placeholder ="请输入职位" v-model ="expr.job" > </el-input > </el-form-item > </el-col > <el-col :span ="2" > <el-form-item size ="small" label-width ="0px" > <el-button type ="danger" @click ="delExprItem(index)" > - 删除</el-button > </el-form-item > </el-col > </el-row > </el-form > <template #footer > <span class ="dialog-footer" > <el-button @click ="dialogVisible = false" > 取消</el-button > <el-button type ="primary" @click ="" > 保存</el-button > </span > </template > </el-dialog >
重点看绑定的index
然后我们声明函数,根据索引进行删除
const delExprItem = (index ) => { employee.value .exprList .splice (index,1 ); }
然后就能实现功能了,前端太麻烦了😫😫😫
使用Watch监听绑定开始与结束时间
watch (() => employee.value .exprList ,(newVal,oldVal ) => { if (employee.value .exprList && employee.value .exprList .length >0 ){ employee.value .exprList .forEach ((expr ) => { expr.begin =expr.exprDate [0 ]; expr.end =expr.exprDate [1 ]; }) } },{deep :true })
这里使用watch监听函数,首先要指明监听的是哪个对象,我们要监听employee的员工数组对象,然后声明数组变化时的值。
当我们的数组中有值,并且值大于1时我们需要监听数组
使用forEach循环遍历数组,定义变量名称,然后将数组里面的第一个值给开始时间,第二个给结束时间
这里最关键的是要使用深度监听,如果直接监听数组只有数组的引用发生变化才会生效,这里当数组里面内容发生变化都需要监听,因此要使用深度监听
7. 保存员工 1. 页面分析 当点击提交按钮时向后端发送异步请求,将参数传入后端来保存数据 保存成功要给出提示信息。然后关闭当前对话框 重新查询列表数据 保存失败给出对应的错误信息 2. 功能开发 首先在api/js
文件中说明添加操作的请求,然后在index
中引用
export const addApi = (emp ) => request.post ('/emps' , emp);
在保存按钮上添加点击事件
<span class ="dialog-footer" > <el-button @click ="dialogVisible = false" > 取消</el-button > <el-button type ="primary" @click ="save" > 保存</el-button > </span>
然后去声明这个按钮。调用api当返回值为200说明插入成功了,然后发送信息关闭对话框重新搜索
const save = async ( ) =>{ const result = await addApi (employee.value ); if (result.code ){ ElMessage .success ("保存成功" ); dialogVisible.value =false ; search (); }else { ElMessage .error (result.msg ); } }
插入成功
哎 这时候又有一个bug,当我们已经新增完毕用户了,但是用户的信息还在表单里面
解决
当点击新增员工时将之前的数据全部置为空
const addEmp = ( ) => { dialogVisible.value = true ; dialogTitle.value = '新增员工' ; employee.value ={ username : '' , name : '' , gender : '' , phone : '' , job : '' , salary : '' , deptId : '' , entryDate : '' , image : '' , exprList : [] } }
添加表单验证规则
首先在el-form
表单项上添加:rules="reles"
的表单绑定项,然后在具体的el-form-item
使用prop
绑定名称
示例:
<el-form :model="employee" label-width="80px" :rules="rules" > <!-- 基本信息 --> <!-- 第一行 --> <el-row :gutter ="20" > <el-col :span ="12" > <el-form-item label ="用户名" prop ="username" > <el-input v-model ="employee.username" placeholder ="请输入员工用户名,2-20个字" > </el-input > </el-form-item > </el-col >
然后去声明这个对象在里面添加校验规则
const rules = ref ({ username : [ { required : true , message : '请输入用户名' , trigger : 'blur' }, { min : 2 , max : 20 , message : '用户名长度应在2到20个字符之间' , trigger : 'blur' } ], name : [ { required : true , message : '请输入姓名' , trigger : 'blur' }, { min : 2 , max : 10 , message : '姓名长度应在2到10个字符之间' , trigger : 'blur' } ], gender : [ { required : true , message : '请选择性别' , trigger : 'change' } ], phone : [ { required : true , message : '请输入手机号' , trigger : 'blur' }, { pattern : /^1[3-9]\d{9}$/ , message : '请输入有效的手机号' , trigger : 'blur' } ] });
这时候会发现我们表单校验虽然校验了但是没有实现,因此还需要再提交表单时进行校验
声明一个表单引用对象
然后再el-form
进行引用
<el-form :model="employee" label-width="80px" :rules="rules" ref="empFromRef" >
这样我们就可以通过这个响应式对象来操作表单对象了
重新修改保存员工的代码,添加表单校验信息,校验通过才可以执行添加代码
const save = async ( ) =>{ if (!empFromRef.value ) return ; empFromRef.value .validate (async (valid) =>{ if (valid){ const result = await addApi (employee.value ); if (result.code ){ ElMessage .success ("保存成功" ); dialogVisible.value =false ; search (); }else { ElMessage .error (result.msg ); } }else { ElMessage .error ('表单校验不通过' ); } }) }
最后一步:当重新添加时需要清空表单校验规则
修改添加员工的相关代码,添加清空表单校验规则代码
const addEmp = ( ) => { dialogVisible.value = true ; dialogTitle.value = '新增员工' ; employee.value ={ username : '' , name : '' , gender : '' , phone : '' , job : '' , salary : '' , deptId : '' , entryDate : '' , image : '' , exprList : [] } if (empFromRef.value ){ empFromRef.value .resetFields (); } }
九、修改员工 1. 页面原型
2. 查询回显功能 为编辑按钮绑定点击事件
发送异步请求,根据id查询员工的详细信息,页面进行回显
在api/js
里面添加异步请求的相关数据,然后在index
里面引用
export const queryInfoApi = (id ) => request.get (`/emps/${id} ` );
为编辑按钮绑定点击事件
<el-button type ="primary" size ="small" @click ="edit(scope.row.id)" > <el-icon > <Edit /> </el-icon > 编辑</el-button >
然后使用scope.row.id
获取这一行的id,并且作为参数传入点击事件
声明这个点击函数
const edit =async (id ) =>{ const result = await queryInfoApi (id); if (result.code ){ dialogVisible.value =true ; dialogTitle.value ='修改员工' ; employee.value =result.data ; } }
当我们点击按钮是将id
作为参数,而后调用查询api,将结果封装在result结果里面。当返回值为200就打开对话框,将标题改为修改员工,然后将结果集中的数据作为参数进行回显
回显失败错误
问题出现背景 当我在页面点击编辑按钮时不会弹出对话框,反而报了错误 index.vue:161 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘0’) at index.vue:161:29 at watch.deep (index.vue:160:28)
但是前端可以正确发送请求,我的后端控制台也有正确的数据返回
问题解析 当点击“编辑”按钮时,会调用 edit 函数。
edit 函数从后端API (queryInfoApi) 获取员工数据,并将其赋值给 employee.value。
后端返回的员工数据中,工作经历 (exprList) 里的每个条目包含 begin 和 end 日期,但不包含前端日期选择器所绑定的 exprDate 数组。
将后端数据直接赋值给 employee.value 后,exprList 中的条目没有 exprDate 属性。
此时,侦听 employee.value.exprList 的 watch 回调被触发。
在这个 watch 回调中,代码尝试通过 expr.exprDate[0] 和 expr.exprDate[1] 来更新 expr.begin 和 expr.end。但由于此时 expr.exprDate 是 undefined,访问 expr.exprDate[0] 就会导致报错,对话框也因此无法弹出。
问题修改 修改 edit 函数:在从后端获取到员工数据后,但在将其赋值给 employee.value 之前,我们需要遍历 exprList (如果存在),并根据每个工作经历条目的 begin 和 end 属性来初始化其对应的 exprDate 数组。同时,要确保 exprList 本身如果从后端返回为 null 或 undefined,会被正确处理为一个空数组。
修改 exprList 的侦听器:使其更健壮,能处理 expr.exprDate 可能不是一个包含两个元素的数组的情况(例如,当用户清空日期选择器或新添加一条工作经历时,exprDate 可能是空数组 [])。
解决完错误后的代码
const edit =async (id ) =>{ const result = await queryInfoApi (id); if (result.code ){ dialogVisible.value =true ; dialogTitle.value ='修改员工' ; employee.value =result.data ; let exprList= employee.value .exprList ; if (exprList && exprList.length >0 ){ exprList.forEach ((expr ) => { expr.exprDate =[expr.begin ,expr.end ]; }) } } }
奶奶的,找了半个小时的错误结果老师后面又讲了。
这里对工作经历进行处理,获取到工作经历的值,然后进行遍历,将开始时间结束时间封装在exprDate
里面去,这个时间段数据就可以在页面回显出来了
页面显示
3. 点击保存修改员工数据 我们的新增员工与修改员工复用的是同一个对话框,那么当我们新增员工时或修改员工时怎么才能区分不同的操作呢?
当有员工id时就为修改,没有就新增
在api
中定义更新员工信息的异步请求
export const updateApi = (emp ) => request.put ('/emps' , emp);
当将我们整个emp对象都要传入后端进行修改,然后在index里面引用
代码逻辑
const save = async ( ) =>{ if (!empFromRef.value ) return ; empFromRef.value .validate (async (valid) =>{ if (valid){ let result; if (employee.value .id ){ result = await updateApi (employee.value ); }else { result = await addApi (employee.value ); } if (result.code ){ ElMessage .success ("保存成功" ); dialogVisible.value =false ; search (); }else { ElMessage .error (result.msg ); } }else { ElMessage .error ('表单校验不通过' ); } }) }
当我们点击按钮时会去判断有没有id,如果有就执行更新,没有就执行添加。同时需要将result
由变量变为常量
十、删除功能 1. 删除单个员工 1. 页面原型
2. api设计 export const deleteApi = (ids ) => request.delete (`/emps?ids=${ids} ` );
3. 为删除按钮绑定点击事件 const deleteById = async (id ) =>{ ElMessageBox .confirm ('此操作将永久删除该员工, 是否继续?' , '提示' , { confirmButtonText : '确定' , cancelButtonText : '取消' , type : 'warning' , }).then (async () => { const result = await deleteApi (id); if (result.code ) { ElMessage .success ('删除成功' ); search (); } else { ElMessage .error (result.msg ); } }) }
2. 批量删除 1. 页面分析
为表格的复选框绑定事件,点击复选框之后,获取到目前选中的条件的id(多个id可以封装到数组中)。 为“批量删除”按钮绑定事件,发送异步请求到服务端,根据id批量删除员工信息。 2. 代码设计 参照官方文档需要在form
表单上绑定 <el-table :data="empList" border style="width: 100%" @selection-change="handleSelectionChange" >
这个函数代表当复选框发生变化时就触发
然后去声明这个函数
const handleSelectionChange = (selection ) => { selectedIds.value = selection.map (item => item.id ) }
为批量删除按钮绑定事件
const selectedIds = ref ([]);const handleSelectionChange = (selection ) => { selectedIds.value = selection.map (item => item.id ) } const deleteByIds = ( ) =>{ ElMessageBox .confirm ('此操作将永久删除该员工, 是否继续?' , '提示' , { confirmButtonText : '确定' , cancelButtonText : '取消' , type : 'warning' , }).then (async () => { if (selectedIds.value && selectedIds.value .length >0 ){ const result = await deleteApi (selectedIds.value ); if (result.code ) { ElMessage .success ('删除成功' ); search (); } else { ElMessage .error (result.msg ); } }else { ElMessage .error ("请选择要删除的员工" ); } }).catch (() => { ElMessage .info ('已取消删除' ); }); }
十一、 员工登录 1. 页面原型
2. 准备工作 在api/
目录下新建login.js
里面存放与登录有关的请求
import request from "@/utils/request" ;export const loginApi = (data ) =>request.post ("/login" ,data);
并且在index中引用
<script setup > import { ref } from 'vue' import { loginApi } from '@/api/login' let loginForm = ref ({username :'' , password :'' }) </script > <template > <div id ="container" > <div class ="login-form" > <el-form label-width ="80px" > <p class ="title" > Tlias智能学习辅助系统</p > <el-form-item label ="用户名" prop ="username" > <el-input v-model ="loginForm.username" placeholder ="请输入用户名" > </el-input > </el-form-item > <el-form-item label ="密码" prop ="password" > <el-input type ="password" v-model ="loginForm.password" placeholder ="请输入密码" > </el-input > </el-form-item > <el-form-item > <el-button class ="button" type ="primary" @click ="" > 登 录</el-button > <el-button class ="button" type ="info" @click ="" > 重 置</el-button > </el-form-item > </el-form > </div > </div > </template > <style scoped > #container { padding : 10% ; height : 410px ; background-image : url ('../../assets/bg1.jpg' ); background-repeat : no-repeat; background-size : cover; } .login-form { max-width : 400px ; padding : 30px ; margin : 0 auto; border : 1px solid #e0e0e0 ; border-radius : 10px ; box-shadow : 0 0 10px rgba (0 , 0 , 0 , 0.5 ); background-color : white; } .title { font-size : 30px ; font-family : '楷体' ; text-align : center; margin-bottom : 30px ; font-weight : bold; } .button { margin-top : 30px ; width : 120px ; } </style >
3. 初始登录功能 当我们点击登录按钮时需要进行登录,点击重置按钮可以重置,因此首先要为他们绑定点击事件
<script setup> import { ref } from 'vue' import { loginApi } from '@/api/login' import { ElMessage } from 'element-plus' ; import { useRouter } from 'vue-router' let loginForm = ref ({username :'' , password :'' }) const router = useRouter (); const login = async ( ) =>{ const result = await loginApi (loginForm.value ); if (result.code ){ ElMessage .success ("登录成功" ); router.push ('/index' ); }else { ElMessage .error (result.msg ) } } const clear = ( ) =>{ loginForm.value .username = '' loginForm.value .password = '' } </script>
这里要使用useRouter
进行跳转
4.存储登录token 1. localStorage
2. 将用户返回到token存储在浏览器中 localStorage .setItem ('loginUser' ,JSON .stringify (result.data ));
JSON.stringify(result.data)
因为我们的result.data
是一个对象,要转为字符串的形式,因此要使用JSON.stringify
5. 用户携带令牌登录 1. 功能引入 现在我们打开久违的idea,开启拦截器的相关功能这时登录会发现
咦我明明登了呀 怎么会没有数据。后端就显示没有令牌了。
也就是说我们在后序的所有请求中没有将token令牌到服务器端
2. axios的拦截器
使用axios
的拦截器拦截请求,并在请求中添加token
在request.js
中添加如下代码
request.interceptors .request .use ( (config ) => { const loginUser= JSON .parse (localStorage .getItem ('loginUser' )); if (loginUser&&loginUser.token ) { config.headers .token =loginUser.token ; } return config; }, (error ) => { } )
注意这里的loginUser
要与index
里面的存入token的键保持一致
3. 解决文件上传问题 在上一节代码中我们解决了token问题,不过我们的文件上传功能却不能实现了,原因是文件上传组件不是发送的异步请求,而是我们自定义的vue进行发送的。因此不能携带令牌也就不能进行文件上传了
怎么说不好说
参照官方文档我们需要在el-update
标签里面声明一个:headers="{'token':token}"
用于存放的token。这里的token不能写死因此用了动态绑定
写了token我们就要声明一个token函数
那我们是什么时候加载这个请求呢?要在页面加载时就要携带请求头,因此要在钩子函数中声明
要在钩子函数中声明就要先来获取token
const getToken = async ( ) =>{ const loginUser=JSON .parse (localStorage .getItem ('loginUser' )) if (loginUser && loginUser.token ){ token.value = loginUser.token ; } }
因为localStorage里面放的是字符串,我们要将其转化为对象
获取成功后将里面的token赋值给token里面去,这时候就会动态绑定到upload里面去。最后在钩子函数中应用
onMounted (() => { search (); queryAllDepts (); getToken (); })
携带令牌登录功能完成
6.未登录显示401跳转到登录页面 使用axios的拦截器的响应拦截器进行拦截,然后进行判断,如果返回的状态码是401就跳转到登陆页面
request.interceptors .response .use ( (response ) => { return response.data ; }, (error ) => { if (error.response .status === 401 ) { ElMessage .error ('登录超时,请重新登录' ); router.push ('/login' ); } return Promise .reject (error); } );
7. 退出登录 1. 页面显示当前用户姓名 我们该如何获取当前登录的用户名呢,当我们登录时员工信息已经封装在了localStorage里面了,只要从里面获取就可以了
<script setup> import {ref,onMounted} from 'vue' const loginName = ref ('' );onMounted (()=> { const loginUser = JSON .parse (localStorage .getItem ('loginUser' )); if (loginUser&&loginUser.name ){ loginName.value = loginUser.name ; } }) </script>
从本地中获取用户名
2. 退出登录 <a href="javascript:;" @click="logout" > <el-icon > <SwitchButton /> </el-icon > 退出登录【{{loginName}}】 </a>
退出登录需要绑定一个退出按钮,同时需要将超链接变为死链接
const router = useRouter ();const logout = ( ) =>{ ElMessageBox .confirm ('您确定要退出登录吗?' , '提示' , { confirmButtonText : '确定' , cancelButtonText : '取消' , type : 'warning' , }).then (async () => { ElMessage .success ('已退出登录' ); localStorage .removeItem ('loginUser' ); router.push ('/login' ); }).catch (() => { ElMessage .info ('已取消退出' ); }); }
退出时给出提示信息,然后删除本地用户信息,最后进行跳转
十一、项目打包 在vscode中运行build
脚本命令,这样会生成dish
文件夹代表打包成功
将项目部署在Nginx
上