Day16 Web前端实战(案例):整体布局、 部门管理


目录

Tlias智能学习辅助系统

图1 Tlias智能学习辅助系统开发步骤

1. 开发模式

前后端分离开发

图2 前后端分离开发流程

小结

什么是前后端分离开发模式?

需求的开发流程?

图3 小结

2. 整体布局

图4 整体布局

2.1 准备工作

① index.html 单页面入口文件

绝对路径G:\workspace\duSSM\vue\vue-tlias-management\index.html,修改其中的title内容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tlias智能学习辅助系统(张婧怡)</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
② src\main.js js入口文件
// 引入Vue、根组件、vue路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

// 引入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

// 引入样式
import './assets/main.css'

// 创建Vue实例并使用路由、ElementPlus、图标、并挂载到#app
const app = createApp(App)
app.use(router)
app.use(ElementPlus, {locale: zhCn})
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
app.mount('#app')
③ src\App.vue 根组件
<script setup>
//引入views/layout/index.vue命名为Layout
import Layout from "@/views/layout/index.vue";
</script>

<template>
  <Layout></Layout>
</template>

<style scoped>

</style>
④ src\views\layout\index.vue 布局组件

修改el-header内容

<script setup>

</script>

<template>
  <div class="common-layout">
    <el-container>
      <!-- Header 区域 -->
      <el-header class="header">
        <span class="title">Tlias智能学习辅助系统(张婧怡)</span>
        <span class="right_tool">
          <a href="">
            <el-icon><EditPen /></el-icon> 修改密码 &nbsp;&nbsp;&nbsp; |  &nbsp;&nbsp;&nbsp;
          </a>
          <a href="">
            <el-icon><SwitchButton /></el-icon> 退出登录
          </a>
        </span>
      </el-header>

      <el-container>
        <!-- 左侧菜单 -->
        <el-aside width="200px" class="aside">
          左侧菜单栏
        </el-aside>

        <el-main>
          右侧核心展示区域
        </el-main>
      </el-container>
      <el-footer style="text-align: center; height: 60px;display: flex; align-items: center; justify-content: center;" class="footer">
        <span>
          &copy; 2025 张婧怡工作室 版权所有
        </span>
      </el-footer>
    </el-container>
  </div>
</template>

<style scoped>
.header, .footer{
  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: 800px;
}
</style>
⑤ src\views\login\index.vue 登录组件

修改<p class="title">内容

<script setup>
  import { ref } from 'vue'

  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>
⑥ src\router\index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    // {
    //   path: '/',
    //   name: 'home',
    //   component: HomeView
    // },
    // {
    //   path: '/about',
    //   name: 'about',
    //   component: () => import('../views/AboutView.vue')
    // }
  ]
})

export default router
⑦ 其他组件

src\views\clazz\index.vue,其他组件初始代码类同

<script setup>

</script>

<template>
  班级管理
</template>

<style scoped>

</style>
组件 组件相对路径 template内容
首页组件 src\views\index\index.vue <img src="../../assets/index.png">
班级管理组件 src\views\clazz\index.vue 班级管理
学员管理组件 src\views\stu\index.vue 学员管理
部门管理组件 src\views\dept\index.vue 部门管理
员工管理组件 src\views\emp\index.vue 员工管理
学员信息统计组件 src\views\report\stu\index.vue 学员信息统计
员工信息统计组件 src\views\report\emp\index.vue 员工信息统计
日志管理组件 src\views\log\index.vue 日志管理
布局组件 src\views\layout\index.vue
登录组件 src\views\login\index.vue
图5 源代码目录

Container 布局容器

<el-container>:外层容器。

当子元素中包含 <el-header><el-footer> 时,全部子元素会垂直上下排列, 否则会水平左右排列

<el-header>:顶栏容器。

<el-aside>:侧边栏容器。

<el-main>:主要区域容器。

<el-footer>:底栏容器。

src\views\layout\index.vue 布局组件 主要布局如下
    <el-container>
      <!-- 上下垂直排列 -->
      <el-header> 顶部标题 </el-header>
      <el-container>
         <!-- 左右水平排列 -->
         <el-aside> 左侧菜单栏 </el-aside>
         <el-main> 右侧核心展示区域 </el-main>
      </el-container>
    </el-container
图6 页面布局验证

小结

图7 页面布局小结

② 动态菜单

页面布局-左侧菜单

src\views\layout\index.vue 左侧菜单栏 替换 下列菜单定义
<!-- 左侧菜单栏 -->
<el-menu>
  <!-- 首页菜单 -->
  <el-menu-item index="/index">
    <el-icon><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>
图8 左侧菜单栏验证
Vue Router
图9 Vue Router三个组成
src\views\layout\index.vue 左侧菜单栏 替换 下列菜单定义
<!-- 左侧菜单栏 -->
<el-menu router>
  <!-- 首页菜单 -->
src\views\layout\index.vue 修改以下2处
<el-menu router>

<el-main>
    <!-- 右侧核心展示区域 -->
    <router-view></router-view>
</el-main>
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
图10 Vue Router验证1

小结

Vue的路由指的是什么 ?

Vue Router中的三个核心组成部分 ?

图11 Vue的路由小结

案例路由实现

图12 嵌套路由
src\App.vue
<script setup>
//引入views/layout/index.vue命名为Layout
//import Layout from "@/views/layout/index.vue";
</script>

<template>
  <!-- <Layout></Layout> -->
  <router-view></router-view>
</template>

<style scoped>

</style>
src\router\index.js
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
图13 Vue Router验证2
图14 嵌套路由

3. 部门管理

① 列表查询

列表查询-页面布局

图15 部门管理基本布局
src\views\dept\index.vue
<script setup>
  import { ref, onMounted } from 'vue'
  // 声明列表展示数据
  let 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="300" align="center"/>
      <el-table-column prop="updateTime" label="最后修改时间" width="300" align="center"/>
      <el-table-column fixed="right" label="操作" align="center">
        <template #default="{ row }">
          <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 0;
}
</style>
图16 部门管理基本布局验证
src\views\dept\index.vue
<script setup>
  import { ref, onMounted } from 'vue'
  import axios from 'axios';

  //查询
  const search = async () => {
    const result = await axios.get('https://apifoxmock.com/m1/3128855-1224313-default/depts');
    if(result.data.code)//Js隐式类型转换 0- false,其他数字- true; '' false,其他都是true;null,undefined--false
      deptList.value = result.data.data;
  }

  onMounted(()=>{
    search();
  })
  // 声明列表展示数据
  let 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="300" align="center"/>
      <el-table-column prop="updateTime" label="最后修改时间" width="300" align="center"/>
      <el-table-column fixed="right" label="操作" align="center">
        <template #default="{ row }">
          <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 0;
}
</style>
图17 自动加载全部部门数据验证

小结

动态加载部门列表数据

列表查询-程序优化

图18 查询优化问题
图19 列表查询-程序优化
src\utils\request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
import router from '../router'

//创建axios实例对象
const request = axios.create({
  baseURL: 'https://apifoxmock.com/m1/3128855-1224313-default/', //'/api',
  timeout: 600000
})

//axios的响应 response 拦截器
request.interceptors.response.use(
  (response) => { //成功回调
    return response.data
  },
  (error) => { //失败回调
    return Promise.reject(error)
  }
)

export default request
src\api\dept.js
import request from '@/utils/request'
//部门列表查询
export const queryAllApi = () => request.get('/depts');

//部门添加

//部门删除

//部门详情查询

//部门修改
src\views\dept\index.vue
<script setup>
  import { ref, onMounted } from 'vue'
  //import axios from 'axios';
  import { queryAllApi } from '@/api/dept'

  //查询
  const search = async () => {
    //const result = await axios.get('https://apifoxmock.com/m1/3128855-1224313-default/depts');
    //if(result.data.code)
    //Js隐式类型转换 0- false,其他数字- true; '' false,其他都是true;null,undefined--false
    //deptList.value = result.data.data; */
    const result = await queryAllApi()
    deptList.value = result.data
  }
  //钩子函数
  onMounted(()=>{
    search();
  })
  // 声明列表展示数据
  let 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="300" align="center"/>
      <el-table-column prop="updateTime" label="最后修改时间" width="300" align="center"/>
      <el-table-column fixed="right" label="操作" align="center">
        <template #default="{ row }">
          <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 0;
}
</style>
图20 配置前端代理服务器
vite.config.js 添加代理服务器配置
 server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        secure: false,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
src\utils\request.js 修改baseURL
  baseURL: '/api',

启动后台服务器(停止令牌检验过滤器、拦截器)后,前端测试

图21 启动后台服务器、前端代理服务器验证

小结

请求处理工具类

② 新增部门

新增部门

图22 新增和修改部门对话框
src\views\dept\index.vue 添加或修改如下代码
<script setup>
  import { queryAllApi, addApi } from '@/api/dept'
  import { ElMessage } from 'element-plus'

  // 打开新增部门对话框
  const addDept = () => {
    dialogFormVisible.value = true;
    formTitle.value = '新增部门';
    dept.value = { name: '' };
  }

  // 保存新增部门
  const save = async () => {
    const result = await addApi(dept.value);
    if(result.code){
      //提示信息
       ElMessage.success('新增成功');
      //关闭对话框
      dialogFormVisible.value = false;
      //查询
      search();
    } else {
      ElMessage.error(result.msg);
    }
  }
</script>

    <el-button type="primary" @click="addDept"> + 新增部门</el-button>


  <!--  Dialog对话框 -->
  <el-dialog v-model="dialogFormVisible" :title="formTitle" width="500">
    <el-form :model="dept">
    <el-form-item label="部门名称"label-width="80px">
      <el-input v-model="dept.name" placeholder="请输入部门名称,长度为2-10位" />
    </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>
src\api\dept.js 添加下列代码
//部门添加
export const addApi = (dept) => request.post('/depts',dept);

新增部门-表单校验

图23 新增部门-表单校验
src\views\dept\index.vue 添加或修改如下代码
  <!--  Dialog对话框 -->
  <el-dialog v-model="dialogFormVisible" :title="formTitle" width="500">
    <el-form :model="dept" :rules="rules" ref="deptFormRef">
    <el-form-item label="部门名称"label-width="80px" prop="name">
      <el-input v-model="dept.name" placeholder="请输入部门名称,长度为2-10位" />
    </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>

<script>
  // 打开新增部门对话框
  const addDept = () => {
    ...
    //重置表单的校验规则-提示信息
    if (deptFormRef.value){
      deptFormRef.value.resetFields();
    }
  }

  //表单校验
  const deptFormRef = ref()
  const rules = ref({
    name: [
      { required: true, message: '部门名称是必填项', trigger: 'blur' },
      { min: 2, max: 10, message: '部门名称的长度应该在2-10位', trigger: 'blur' }
    ]
  })

  // 保存新增部门
  const save = async () => {
    //表单校验
    if(!deptFormRef.value) return;
    deptFormRef.value.validate(async(valid)=>{//valid表示是否校验通过:true通过/false不通过
      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('表单校验未通过');
      }
    })
  }
</script>
图24 新增部门验证

③ 修改部门

图25

查询回显

src\api\dept.js 添加下列代码
//根部门id查询部门详情
export const queryByIdApi = (id) => request.get('/depts/'+id);
src\views\dept\index.vue 添加或修改如下代码
<script setup>
  import { queryAllApi, addApi, queryByIdApi } from '@/api/dept'

  //打开编辑部门对话框 回显数据
  const editDept = async (id) => {
    dialogFormVisible.value = true;
    formTitle.value='修改部门';
    //重置表单的校验规则-提示信息
    if (deptFormRef.value){
      deptFormRef.value.resetFields();
    }
    const result = await queryByIdApi(id);
    if(result.code){
      dept.value = result.data;
    }
  }
</script>

        <template #default="scope">
          <el-button type="primary" size="small" @click="editDept(scope.row.id)"><el-icon><EditPen /></el-icon>修改</el-button>
          <el-button type="danger" size="small" ><el-icon><DeleteFilled /></el-icon>删除</el-button>
        </template>
图26 修改部门数据回显验证
src\api\dept.js 添加下列代码
//部门修改
export const updateApi = (dept) => request.put('/depts',dept);
src\views\dept\index.vue 修改如下js代码
  import { queryAllApi, addApi, queryByIdApi, updateApi } from '@/api/dept'


  // 保存新增、修改部门
  const save = async () => {
    //表单校验
    if(!deptFormRef.value) return;
    deptFormRef.value.validate(async(valid)=>{//valid表示是否校验通过:true通过/false不通过
      if(valid){//通过
        //保存新增、修改部门
        let result;
        if(dept.value.id){//修改
          result = await updateApi(dept.value);
        } else { //新增
          result = await addApi(dept.value);
        }
        if(result.code){
          //提示信息
          ElMessage.success(`${formTitle.value}成功`);
          //关闭对话框
          dialogFormVisible.value = false;
          //查询
          search();
        } else { //失败
          ElMessage.error(result.msg);
        }
      }else {//不通过
        //提示信息
        ElMessage.error('表单校验未通过');
      }
    })
  }
图27 修改部门保存数据验证

④ 删除部门

图28 删除部门需求
src\api\dept.js 添加下列代码
//根部门id删除部门
export const deleteByIdApi = (id) => request.delete(`/depts?id=${id}`);
src\views\dept\index.vue 修改如下代码
<script>
  import { queryAllApi, addApi, queryByIdApi, updateApi, deleteByIdApi } from '@/api/dept'
  import { ElMessage, ElMessageBox } from 'element-plus'

   //删除
  const delById = async (id,name) => {
    //弹出确认框
    ElMessageBox.confirm('您确认删除【' + name + '】部门吗?','提示',
      {confirmButtonText:'确认',cancelButtonText:'取消',type:'warning'}
    ).then(async () =>  {//确认
      const result = await deleteByIdApi(id);
      if(result.code){
        ElMessage.success('删除成功')
        search();
      }else{
        ElMessage.error(result.msg)
    }).catch(() =>  {//取消
      ElMessage.info('您已取消删除')
    })
  }
</script>
          <el-button type="primary" size="small" @click="editDept(scope.row.id)"><el-icon><EditPen /></el-icon>修改</el-button>
          <el-button type="danger" size="small" @click="delById(scope.row.id,scope.row.name)"><el-icon><DeleteFilled /></el-icon>删除</el-button>
图29 删除部门验证

返回