一、开发模式
主流:前后端分离


1、什么是前后端分离开发模式?
前后端开发人员独立开发,独立部署
2、需求的开发流程?
需求分析->接口定义->前后端并行开发->测试->前后端联调测试
二、整体布局
1、准备工作
在vite.config.js目录中
javascript
alias: {
// 将 @/xxx 映射到 src/xxx,相当于为src目录起别名
'@': fileURLToPath(new URL('./src', import.meta.url))

src/view/layout/index.vue文件下
html
<!-- 左侧菜单 -->
<template>
<el-container>
<el-aside width="200px" class="aside">
<el-menu>
<el-sub-menu index="1">
<template #title>
<el-icon><HomeFilled /></el-icon>班级学员管理
</template>
<el-menu-item index="1-1">
<el-icon><HelpFilled /></el-icon>班级管理
</el-menu-item>
<el-menu-item index="1-2">
<el-icon><UserFilled /></el-icon>学员管理
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<el-container>
<template>
在main.js中注册icon图标
javascript
// 引入icon
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
// 注册icon图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
2、动态菜单
(1)VueRouter
Vue Router:Vue Router | Vue.js 的官方路由
VueRouter:Vue的官方路由。为Vue提供富有表现力、可配置的、方便的路由。
Vue中的路由:路径与组件的对应关系。
组成:
Router实例:路由实例,基于createRouter函数创建,维护了应用的路由信息。
<router-link>:路由链接组件,浏览器会解析成<a>。
<router-view>:动态视图 组件,用来渲染展示与路由路径对应的组件。

src/view/layout/index.vue文件下,router-link路由链接组件环绕首页菜单并指定访问路径
html
<!-- 首页菜单 -->
<router-link to="/index">
<el-menu-item index="/index">
<el-icon><Promotion /></el-icon> 首页
</el-menu-item>
</router-link>
或者只需在他上级加上 router即可,以el-menu-item作为路径跳转
html
<el-menu router>//默认为true
<!-- 首页菜单 -->
<el-menu-item index="/index">
<el-icon><Promotion /></el-icon> 首页
</el-menu-item>
src/router/index.js下创建Router实例
javascript
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: "index", component: LoginView },
],
});
export default router;
访问地址就会访问路由展示组件处
1.Vue的路由指的是什么?
VueRouter是vue官方的路由 ,描述的是路径与组件的对应关系
2、VueRouter中的三个核心组成部分?
router实例,维护了应用的路由信息(routes)
<router-link>:路由链接组件
<router-view>:动态视图组件
(2)VueRouter嵌套路由
src/router/index.js这样写
javascript
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: "/",
name: "",
component: LayoutView,
redirect: "/index", //重定向
children: [
{ 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;
三、部门管理
1、列表查询
(1)页面布局
基础页面布局,在src/views/dept/index.vue
html
<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 prop="id" 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 prop="address" label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small"><el-icon><EditPen /></el-icon>编辑</el-button>
<el-button type="danger" size="small"><el-icon><DeleteFilled /></el-icon>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<style scoped>
.container {
margin: 10px 0px;
}
</style>
(2)加载数据
html
<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';
//钩子函数 页面加载完毕后调用钩子函数,加载其他函数
onMounted(() => {
search();
});
//查询
const search = async () => {
const result = await axios.get(
"https://apifoxmock.com/m1/3128855-1224313-default/depts"
);
if (result.data.code) {
//JS隐式类型转换 0、''、null、undefined都是false;其他都是true
deptList.value = result.data; //拿到数据->数组
}
};
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 prop="id" 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 prop="address" label="操作" align="center">
<template #default="scope">
<el-button type="primary" size="small"
><el-icon><EditPen /></el-icon>编辑</el-button
>
<el-button type="danger" size="small"
><el-icon><DeleteFilled /></el-icon>删除</el-button
>
</template>
</el-table-column>
</el-table>
</div>
</template>
<style scoped>
.container {
margin: 10px 0px;
}
</style>
(3)程序优化1

为解决上述问题,我们在前端项目开发时,通常会定义一个请求处理 的工具类-request.js
与服务端进行异步交互 的逻辑,通常会封装到一个单独的api中,如:dept.js。
在src目录下新建utils/request.js,创建请求处理工具类,并导出请求工具

src/api/depts目录下导入工具,并导出查询操作

使用工具的查询操作查询部门

(4)程序优化2
utils/request.js中指定url请求路径的前缀

vite.config.js中配置前端请求服务器信息,配置代理服务器,交给后端8080端口的tomcat处理,并把请求路径的/api替换为空串

这样就做到了前端访问后端

2、新增部门
api/dept.js中
javascript
//新增
export const addApi = (dept) => request.post('/depts', dept);
src/views/dept/index.vue中
提示: 新增部门和编辑部门可以共用一个dialog对话框。

在ElementPlus提供的form表单组件中,就提供了对应的表单校验的功能。具体步骤如下:
定义表单校验规则,通过rules属性与表单绑定
通过prop属性绑定对应的表单项。
通过ref属性注册表单的引用,表单提交时,校验表单,校验通过,则允许提交表单。



javascript
//Dialog对话框
const dialogFormVisible = ref(false);//控制弹窗显示 / 隐藏的响应式变量
const formTitle = ref('');//弹窗的标题文字
const dept = ref({name: ''});//保存弹窗表单里部门的表单数据对象。
javascript
//新增部门
const addDept = () => {
dialogFormVisible.value = true;
formTitle.value = '新增部门';
dept.value = {name: ''};//再次打开清空
//重置表单的校验规则-提示信息
if (deptFormRef.value){
deptFormRef.value.resetFields();
}
}
3、修改部门
对于修改操作,通常会分为两步进行:①查询回显、②保存修改。
javascript
//根据ID查询
export const queryByIdApi = (id) => request.get(`/depts/${id}`);//路径参数传输,使用模板字符串
//修改
export const updateApi = (dept) => request.put('/depts', dept);
交互逻辑:
点击编辑按钮,根据ID进行查询,弹出对话框,完成页面回显展示。(查询回显)
javascript
//编辑
const edit = async (id) => {
formTitle.value = '修改部门';
//重置表单的校验规则-提示信息
if (deptFormRef.value){
deptFormRef.value.resetFields();
}
const result = await queryByIdApi(id);
if(result.code){
dialogFormVisible.value = true;
dept.value = result.data;
}
}
点击确定按钮,保存修改后的数据,完成数据更新操作。(保存修改)
javascript
let result ;
if(dept.value.id){ //修改
result = await updateApi(dept.value);
}else{ //新增
result = await addApi(dept.value);
}
4、删除部门
点击删除按钮,需要删除当前这条数据,删除完成之后,刷新页面,展示出最新的数据。 由于删除是一个比较危险的操作,为避免误操作,通常会在点击删除之后,弹出确认框进行确认。
四、员工管理-列表查询
1、页面布局(搜索栏)

2、页面布局(搜索栏-watch侦听)

如果表单封装的数据较多,建议绑定到一个对象中

作用:侦听一个或多个响应式数据源 ,并在数据源变化时 调用传入的回调函数。
用法:
1、导入watch函数
2、执行watch函数,传入要侦听的响应式数据源(ref对象)和回调函数;

侦听对象
javascript
// 1. 侦听一个响应式数据
const a = ref('');
watch(a, (newVal, oldVal) => {
console.log(`变化后的值: ${newVal}, 变化前的值: ${oldVal}`);
})
// 2. 侦听一个对象(侦听对象的全部属性)
const user = ref({name: '', age: 10});
watch(user, (newVal, oldVal) => {
console.log(newVal);
}, {deep: true}) //深度侦听
// 3. 侦听对象中的某一个属性
const user2 = ref({name: '', age: 10});
watch(() => user2.value.age, (newVal, oldVal) => {
console.log(`变化后的值: ${newVal}, 变化前的值: ${oldVal}`);
})
小结
1、watch侦听函数的作用?
侦听一个或多个响应式数据源 ,并在数据源变化时调 用所给的回调函数
2、watch函数调用时传入的参数?
参数一:侦听的源,可以是一个ref对象,一个函数等。
参数二:侦听的源发生变化时,触发的回调函数
参数三:可选,是一个对象(deep、immediate)
3、能否侦听对象的一个属性或全部属性?
可以
侦听一个属性:第一个参数传递一个函数()=>user.value.name
侦听全部属性:第三个参数传递一个对象 ,指定deep深度侦听
3、页面布局(表格)
4、页面布局(分页条)
5、页面交互
五、员工管理-新增员工
1、页面布局(引入)
2、页面布局(展示优化)
3、页面布局(Upload组件)
4、页面交互(工作经历)
5、页面交互(工作经历-watch监听)
6、页面交互(保存员工)
7、页面交互(表单校验)
六、员工管理-修改员工
1、查询回显
2、修改数据
七、员工管理-删除员工
1、删除单个
2、批量删除
八、登录退出
1、登录功能实现
2、携带令牌访问服务端
3、携带令牌访问服务端(Upload组件处理)
九、登录-响应401跳转登录页面
十、退出
1、展示当前登录员工
2、功能实现
十一、Tlias案例-打包部署
1、生成打包文件

2、安装Nginx代理服务器
介绍:Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。其特点是占有内存少,并发能力强,在各大型互联网公司都有非常广泛的使用。
官网:nginx

3、部署Nginx服务器
将上面的打包文件放到html目录下,双击可执行文件启动

4、访问前后端
访问 http://localhost:80,并运行后端
注意:Nginx默认占用80端口号,如果80端口号被占用,可以在Nginx.conf中修改端口号。使用新的nginx.conf文件,进行反向代理
nginx目录下cmd,重新加载nginx的配置文件

停止nginx服务器

十二、关键字总结
1、导入组件
(1)vite.config.js 配置文件
javascript
// 转成电脑真实磁盘路径字符串;拼装文件路径地址;*
import { fileURLToPath, URL } from 'node:url'
//写代码时有代码提示、语法校验*
import { defineConfig } from 'vite'
//让 vite 能识别.vue单文件组件*
import vue from '@vitejs/plugin-vue'
(2)main.js 主入口
javascript
//Vue3 的创建应用实例的函数。
import { createApp } from 'vue'
//创建 Pinia 实例,用于全局共享状态数据(登录用户信息、全局变量等)。
import { createPinia } from 'pinia'
//导入根组件
import App from './App.vue'
import router from './router'
//导入 Element‑Plus UI 组件库(vue3 版本),提供 el‑button、el‑table、el‑dialog、el‑form 等全套组件。
import ElementPlus from 'element-plus'
//导入 Element‑Plus 的全部 css 样式文件。
//⚠️必须导入!不导入,所有 element 组件没有样式,页面样式错乱。
import 'element-plus/dist/index.css'
// 把组件默认英文改成中文
import zhCn from 'element-plus/es/locale/lang/zh-cn'
//把图标库全部图标一次性导入
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 导入项目全局自定义 css
import './assets/main.css'
(3)request.js 携带token
javascript
//axios 网络请求库,用来发 http 请求。
import axios from 'axios'
//消息提示组件
import { ElMessage } from 'element-plus'
//vue‑router 路由对象;可以做页面跳转,比如没登录跳转到登录页。
import router from '../router'
(4)conter.js 全局仓库容器
javascript
//Vue3 响应式 API,定义基础类型响应式数据(数字、字符串、布尔)。
//依赖别的响应式数据,自动算出新值;依赖的数据变了,它自动更新,不能直接手动赋值。
import { ref, computed } from 'vue'
//pinia 提供的函数,用来创建全局仓库 store。
import { defineStore } from 'pinia'
(5)api包 发送请求,交接后端
javascript
//导入根组件
//自带请求拦截器(自动带 token)、响应拦截器。所有接口都使用这个 request 发请求,不用原生 axios。
import request from '@/utils/request'
(6)views包 主布局页面
javascript
//定义响应式数据。简单类型(字符串、数字、布尔)或者对象数组,用 ref 包裹,页面才会数据驱动,数据变页面自动更新。
//生命周期钩子函数。组件渲染完成(页面挂载完毕)自动执行,一般在这里加载页面初始数据,比如一进来就查询部门列表。
import { ref, onMounted } from 'vue'
// ElMessage:轻提示消息,成功 / 失败提示
// ElMessageBox:确认弹窗(删除前弹框确认)
import { ElMessage, ElMessageBox } from 'element-plus'
// 前面讲的部门接口文件,导入已经封装好的后端请求函数。
import { queryAllApi, addDeptApi, queryInfoApi, updateDeptApi, deleteDeptApi } from '@/api/dept'