Spring Boot + Vue 疫情防控系统工程化实践指南
2026/9/16 14:56:56 网站建设 项目流程

简介:本资源是一套完整的疫情防控管理系统毕业设计源码案例,面向Java全栈初学者与高校计算机专业毕业生,聚焦信息化管理场景下的疫情数据采集、统计与可视化需求。项目采用Spring Boot构建后端服务,Vue实现响应式前端界面,MySQL+MyBatis完成数据持久化,覆盖前后端分离开发全流程,适合作为课程设计、毕设参考或技术栈整合实践。压缩包共85个文件,包含31个Java业务逻辑类、13个MyBatis映射XML、7个Vue组件及7个JS交互脚本,辅以SQL建表语句、配置文件(yml/properties)、运行说明文档(txt/md)及系统效果图(jpg/png),结构清晰、开箱即用。资源大小仅1016KB,轻量易部署,已有122人学习下载。读者可直接导入IDEA运行,获取完整可执行系统、数据库初始化脚本、功能需求说明及多张界面效果截图,快速掌握企业级Web应用的工程组织方式与典型模块实现逻辑。

1. 这不是又一个“毕设管理系统”,而是 Spring Boot + Vue 工程化落地的典型切口

很多同学拿到“基于 Spring Boot + Vue 的疫情防控管理系统”这类毕业设计题目时,第一反应是:找源码、改数据库、换 logo、调接口、打包交差。但真正拉开差距的,从来不是功能是否“能跑”,而是系统如何支撑真实业务逻辑闭环——比如人员轨迹关联风险等级的动态计算、多级审核流程的状态机驱动、核酸数据批量校验与异常告警、前端权限随角色实时收敛、部署后静态资源路径不崩、跨域配置在 Nginx 和开发代理间无缝切换。这不是教科书里的 CRUD 演示,而是把 Spring Boot 的自动装配、事务边界、参数校验、统一响应体,和 Vue 的 Composition API、路由守卫、Pinia 状态持久化、Axios 请求拦截器,拧成一股能应对真实业务压力的工程流。适合正在用 Vue 3 + Spring Boot 2.7.x / 3.2.x 搭建前后端分离项目的开发者,尤其当你发现vue-router路由守卫拿不到登录态、@RequestBody接收嵌套对象报 400、application.ymlspring.profiles.active=prod切换后日志不输出、或者npm run build后访问/login返回 404 —— 这些都不是“环境问题”,而是架构层设计缺位的信号。


2. 从零构建可运行的最小闭环:Spring Boot 后端骨架与核心接口契约

一个真正能交付的疫情防控管理系统,后端不能只暴露几个@RestController方法。它必须具备清晰的分层结构、可验证的数据契约、可追踪的请求链路,以及面向运维的可观测性基础。我们以 Spring Boot 2.7.18(LTS)为基准,避免使用 Spring Boot 3.x 的 Jakarta EE 9+ 带来的兼容性陷阱(如javax.validation包迁移),同时兼顾主流 IDE(IntelliJ IDEA 2023.3)的项目识别能力。

2.1 初始化工程与关键依赖选型逻辑

使用 Spring Initializr(https://start.spring.io)生成基础项目,勾选以下模块:

  • Spring Web:提供 RESTful 接口能力,底层基于 Tomcat 9.0.x(Spring Boot 2.7 默认)
  • Spring Data JPA:替代 MyBatis 的轻量选择,配合 H2 内存数据库快速验证逻辑(生产环境再替换为 MySQL 8.0+)
  • Validation:用于@Valid校验@RequestBody,比手动if判断更符合契约精神
  • Lombok:消除样板代码,@Data+@Builder+@NoArgsConstructor组合覆盖实体类 90% 冗余
  • Actuator:暴露/actuator/health/actuator/metrics等端点,为后续 Nginx 健康检查或 Prometheus 采集打基础

提示:不要勾选Spring Security初期就引入。先让/api/user/list不带鉴权返回 JSON,再逐步叠加 JWT 认证。过早引入安全框架容易掩盖接口设计缺陷,比如UserVO是否包含敏感字段、password字段是否被@JsonIgnore正确屏蔽。

生成后的pom.xml中需显式声明 Lombok 版本(避免与 Spring Boot 内置版本冲突):

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <version>1.18.30</version> </dependency>

2.2 定义疫情防控领域核心实体与 DTO 分离策略

疫情防控场景下,Person(人员)、TestRecord(核酸记录)、RiskArea(风险区域)、ReviewTask(审核任务)四类实体构成主干。关键在于严格区分 Entity、DTO、VO

  • Person(Entity):对应数据库表,含id,name,idCard,phone,currentAddress,createdAt
  • PersonDTO(DTO):用于 Controller 层接收前端 POST 数据,仅含name,idCard,phone,currentAddress,且@NotBlank校验
  • PersonVO(VO):用于返回给前端,不含idCard(脱敏),增加riskLevel: String(根据地址匹配风险区域动态计算)
// src/main/java/com/example/covid/entity/Person.java @Entity @Table(name = "t_person") @Data @NoArgsConstructor public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @Column(unique = true) // 身份证号唯一索引 private String idCard; private String phone; private String currentAddress; private LocalDateTime createdAt; }
// src/main/java/com/example/covid/dto/PersonDTO.java @Data public class PersonDTO { @NotBlank(message = "姓名不能为空") private String name; @NotBlank(message = "身份证号不能为空") @Pattern(regexp = "^\\d{17}[\\dXx]$", message = "身份证格式不正确") private String idCard; @NotBlank(message = "手机号不能为空") @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String phone; @NotBlank(message = "当前住址不能为空") private String currentAddress; }

2.3 实现带业务语义的 REST 接口与统一响应体

避免直接return personRepository.findAll(),而是封装为Result<T>统一结构:

// src/main/java/com/example/covid/common/Result.java @Data @AllArgsConstructor @NoArgsConstructor public class Result<T> { private Integer code; // 200 成功,400 参数错误,500 服务器异常 private String msg; private T data; public static <T> Result<T> success(T data) { return new Result<>(200, "操作成功", data); } public static <T> Result<T> fail(String msg) { return new Result<>(500, msg, null); } }

Controller 层使用@Valid校验 DTO,并捕获全局异常:

// src/main/java/com/example/covid/controller/PersonController.java @RestController @RequestMapping("/api/person") @Slf4j public class PersonController { @Autowired private PersonService personService; @PostMapping public Result<PersonVO> create(@Valid @RequestBody PersonDTO dto) { try { PersonVO vo = personService.create(dto); return Result.success(vo); } catch (Exception e) { log.error("创建人员失败", e); return Result.fail("系统繁忙,请稍后重试"); } } @GetMapping("/list") public Result<List<PersonVO>> list() { return Result.success(personService.listAll()); } }

注意:@Valid触发校验失败时,默认返回400 Bad Request和原始错误信息(如Field error in object 'personDTO' on field 'idCard': rejected value [123]; codes [Pattern.personDTO.idCard,Pattern.idCard,Pattern.java.lang.String,Pattern]; arguments [org.springframework.validation.beanvalidation.SpringValidatorAdapter$SpringConstraintValidatorContext@3a7e6b2c,]; default message [身份证格式不正确])。这不利于前端解析。需配置全局异常处理器统一转换:

// src/main/java/com/example/covid/exception/GlobalExceptionHandler.java @ControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) { String defaultMessage = ex.getBindingResult().getFieldError().getDefaultMessage(); return Result.fail("参数错误:" + defaultMessage); } }

3. Vue 3 前端工程搭建与疫情防控业务组件化实现

Vue 部分不能停留在vue create后改App.vue。疫情防控系统的交互复杂度远超普通后台,需从路由设计、状态管理、API 封装、权限控制四个维度构建可维护结构。我们采用 Vue 3.4.21 + Vite 5.2.12 + Pinia 2.1.7 + Element Plus 2.3.12(兼容 Vue 3)组合,避开 Vue 2 的 Options API 陷阱。

3.1 Vite 项目初始化与关键配置项说明

执行命令初始化:

npm create vue@latest # 选择:✔ Project name: … covid-vue # ✔ Add TypeScript? … No / Yes → 选 Yes(类型安全对疫情数据字段至关重要) # ✔ Add JSX Support? … No # ✔ Add Vue Router for Single Page Application routing? … Yes(必须) # ✔ Add Pinia for state management? … Yes(替代 Vuex,更轻量) # ✔ Add Vitest for Unit Testing? … No(毕设阶段可跳过) # ✔ Add an ESLint configuration? … Yes(推荐 Prettier + Standard) # ✔ Add Cypress for End-to-End testing? … No

生成后,修改vite.config.ts关键配置:

import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': path.resolve(__dirname, 'src') // 支持 @/components 导入 } }, server: { port: 8080, open: true, proxy: { '/api': { target: 'http://localhost:8081', // Spring Boot 默认端口 changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') // 去掉 /api 前缀,后端无需处理 } } } })

提示:proxy配置仅在开发环境生效。生产环境需通过 Nginx 反向代理,因此vite.config.ts中的 proxy 不会影响最终打包产物。务必在vue.config.js(若使用 Vue CLI)或vite.config.ts中明确区分 dev/prod 行为。

3.2 基于 Vue Router 的权限路由守卫与动态菜单生成

疫情防控系统需按角色展示不同菜单:管理员看到「人员管理」「审核中心」「风险区域维护」;网格员只看到「我的辖区」「核酸上报」;居民只能查看「个人健康码」「核酸记录」。路由结构设计如下:

// src/router/index.ts import { createRouter, createWebHistory } from 'vue-router' import Layout from '@/layout/index.vue' // 通用布局组件 import Login from '@/views/Login.vue' import Home from '@/views/Home.vue' import PersonList from '@/views/person/PersonList.vue' import ReviewList from '@/views/review/ReviewList.vue' import RiskArea from '@/views/risk/RiskArea.vue' const routes = [ { path: '/login', name: 'Login', component: Login, meta: { requiresAuth: false } // 不需要登录 }, { path: '/', name: 'Layout', component: Layout, meta: { requiresAuth: true }, // 需要登录 children: [ { path: '', redirect: '/home' }, { path: 'home', name: 'Home', component: Home, meta: { title: '首页' } }, { path: 'person', name: 'PersonList', component: PersonList, meta: { title: '人员管理', roles: ['admin', 'grid'] } }, { path: 'review', name: 'ReviewList', component: ReviewList, meta: { title: '审核中心', roles: ['admin'] } }, { path: 'risk', name: 'RiskArea', component: RiskArea, meta: { title: '风险区域', roles: ['admin'] } } ] } ] const router = createRouter({ history: createWebHistory(), routes }) // 全局前置守卫:检查登录态 & 权限 router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else if (to.meta.requiresAuth && token) { const userRoles = JSON.parse(localStorage.getItem('user') || '{}').roles || [] if (to.meta.roles && !to.meta.roles.some(role => userRoles.includes(role))) { next('/403') // 无权限页面 } else { next() } } else { next() } }) export default router

3.3 使用 Pinia 管理全局状态与 Axios 请求封装

创建src/stores/user.ts存储用户信息与 token:

// src/stores/user.ts import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', userInfo: JSON.parse(localStorage.getItem('user') || '{}'), roles: [] as string[] }), actions: { setToken(token: string) { this.token = token localStorage.setItem('token', token) }, setUserInfo(userInfo: any) { this.userInfo = userInfo this.roles = userInfo.roles || [] localStorage.setItem('user', JSON.stringify(userInfo)) }, clear() { this.token = '' this.userInfo = {} this.roles = [] localStorage.removeItem('token') localStorage.removeItem('user') } } })

封装src/utils/request.ts,集成请求拦截、响应拦截、错误统一处理:

// src/utils/request.ts import axios from 'axios' import { ElMessage } from 'element-plus' import { useUserStore } from '@/stores/user' const request = axios.create({ baseURL: '/api', // 与 vite.config.ts 中 proxy 保持一致 timeout: 10000 }) // 请求拦截:添加 token request.interceptors.request.use( config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }, error => Promise.reject(error) ) // 响应拦截:统一处理业务错误码 request.interceptors.response.use( response => { const { code, msg, data } = response.data if (code === 200) { return data } else { ElMessage.error(msg || '请求失败') return Promise.reject(new Error(msg)) } }, error => { if (error.response?.status === 401) { const userStore = useUserStore() userStore.clear() window.location.href = '/login' } else if (error.response?.status === 403) { ElMessage.error('权限不足') } else { ElMessage.error('网络错误,请检查连接') } return Promise.reject(error) } ) export default request

注意:baseURL: '/api'是关键。它让所有request.get('/person/list')实际请求http://localhost:8080/api/person/list,而 Vite 的 proxy 会将/api/**转发到http://localhost:8081/**。这样前端代码无需硬编码后端地址,也避免了 CORS 问题。

3.4 实现「核酸记录上报」业务组件与表单校验联动

src/views/test/TestUpload.vue为例,展示如何用 Composition API + Element Plus 实现带文件上传、日期校验、地理位置选择的复杂表单:

<!-- src/views/test/TestUpload.vue --> <template> <el-card> <template #header> <div class="card-header">核酸记录上报</div> </template> <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> <el-form-item label="检测时间" prop="testTime"> <el-date-picker v-model="form.testTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择检测时间" /> </el-form-item> <el-form-item label="检测机构" prop="institution"> <el-input v-model="form.institution" placeholder="请输入检测机构名称" /> </el-form-item> <el-form-item label="检测结果" prop="result"> <el-select v-model="form.result" placeholder="请选择结果"> <el-option label="阴性" value="negative" /> <el-option label="阳性" value="positive" /> <el-option label="待检测" value="pending" /> </el-select> </el-form-item> <el-form-item label="上传报告" prop="reportFile"> <el-upload class="upload-demo" action="#" :http-request="handleUpload" :show-file-list="false" :limit="1" > <el-button size="small" type="primary">点击上传</el-button> <div slot="tip" class="el-upload__tip">仅支持 JPG/PNG/PDF,大小不超过 5MB</div> </el-upload> <div v-if="form.reportUrl" class="mt-2"> <el-link :href="form.reportUrl" target="_blank">已上传:{{ form.reportName }}</el-link> </div> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm">提交</el-button> <el-button @click="resetForm">重置</el-button> </el-form-item> </el-form> </el-card> </template> <script setup lang="ts"> import { ref, reactive } from 'vue' import { ElMessage } from 'element-plus' import request from '@/utils/request' const formRef = ref() const form = reactive({ testTime: '', institution: '', result: '', reportUrl: '', reportName: '' }) const rules = { testTime: [{ required: true, message: '请选择检测时间', trigger: 'change' }], institution: [{ required: true, message: '请输入检测机构', trigger: 'blur' }], result: [{ required: true, message: '请选择检测结果', trigger: 'change' }] } const handleUpload = (params: any) => { const file = params.file const reader = new FileReader() reader.onload = (e) => { const base64 = e.target?.result as string form.reportUrl = base64 form.reportName = file.name } reader.readAsDataURL(file) } const submitForm = () => { formRef.value.validate((valid: boolean) => { if (valid) { request.post('/test/upload', form) .then(() => { ElMessage.success('上报成功') resetForm() }) .catch(() => { ElMessage.error('上报失败') }) } }) } const resetForm = () => { form.testTime = '' form.institution = '' form.result = '' form.reportUrl = '' form.reportName = '' } </script>

提示:此处handleUpload使用FileReader读取本地文件转 Base64,适用于小文件(<5MB)。生产环境应对接后端文件上传接口,返回 URL 后存入form.reportUrlrequest.post('/test/upload', form)form包含reportUrl(Base64 字符串),后端需解码并保存为文件。


4. 前后端联调关键路径与 Spring Boot + Vue 部署实操

完成单侧开发只是开始,真正的难点在于联调时的请求链路打通、跨域策略适配、静态资源路径修正、以及生产环境部署的路径一致性。很多“毕设源码”跑不起来,根源不在代码,而在这些工程细节。

4.1 联调必查的 5 个断点与验证命令

断点位置验证方式常见失败现象解决方案
后端接口是否可达curl -X GET http://localhost:8081/api/person/listConnection refused检查 Spring Boot 是否启动(Application started in X.XXX seconds日志)、端口是否被占用(netstat -ano | findstr :8081
前端代理是否生效浏览器 Network Tab 查看/api/person/list请求 URL显示http://localhost:8080/api/person/list(未代理)检查vite.config.tsserver.proxy配置是否正确,重启 Vite 服务
JWT Token 是否携带Network Tab → Headers → AuthorizationAuthorization: Bearer xxx检查request.interceptors.request.use是否执行,localStorage.getItem('token')是否存在
后端返回是否为 Result 结构Network Tab → Response返回原始 JSON(非{code:200,msg:"ok",data:[...]}检查 Controller 方法是否返回Result.success(...),而非直接return list
Vue 路由是否匹配浏览器地址栏输入/person显示空白页或 404检查router.beforeEach是否阻止跳转,PersonList.vue文件路径是否与routescomponent路径一致

4.2 生产环境部署:Nginx 配置与 Spring Boot 静态资源路径修正

开发时 Vue 通过vite previewnpm run serve运行,Spring Boot 独立运行。生产环境需合并部署,常见两种模式:

  • 模式一:Nginx 反向代理(推荐)
    Vue 打包产物放 Nginxhtml/目录,Spring Boot 仍运行在 8081 端口,Nginx 将/api/**转发给 Spring Boot,其他请求返回 Vue 静态文件。

  • 模式二:Spring Boot 内嵌 Vue(简化运维)
    npm run build生成的dist/目录复制到 Spring Bootsrc/main/resources/static/,Spring Boot 启动后自动托管http://localhost:8081/

我们采用模式一,因其更符合前后端分离原则,且便于独立升级。

Nginx 配置 (/etc/nginx/conf.d/covid.conf):

upstream backend { server 127.0.0.1:8081; } server { listen 80; server_name localhost; # Vue 静态资源 location / { root /var/www/covid-vue/dist; try_files $uri $uri/ /index.html; } # API 接口转发 location /api/ { proxy_pass http://backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 防止 favicon.ico 404 location = /favicon.ico { log_not_found off; access_log off; } }

注意:try_files $uri $uri/ /index.html;是关键。它确保 Vue Router 的 History 模式在刷新页面时不会返回 404。例如访问/person时,Nginx 先找dist/person/index.html(不存在),再找dist/person(不存在),最后 fallback 到dist/index.html,由 Vue Router 解析路由。

Spring Boot 需关闭默认静态资源路径冲突(如果启用内嵌模式):

# application-prod.yml spring: web: resources: static-locations: classpath:/static/ mvc: static-path-pattern: /static/**

模式一无需修改 Spring Boot,只需确保其application.ymlserver.port=8081且不暴露/static/**路径。

4.3 Vue 打包后路径问题的终极解决方案:public 目录与 BASE_URL

npm run build后,Vue 默认生成相对路径引用(如<script src="/assets/index-xxx.js">)。若部署在子路径(如http://example.com/covid/),需配置BASE_URL

vite.config.ts中设置:

export default defineConfig({ base: '/covid/', // 对应 Nginx location /covid/ // ... 其他配置 })

同时,在index.html中确保:

<head> <base href="/covid/"> </head>

提示:base配置必须与 Nginx 的location路径完全一致。若 Nginx 配置location /covid/ { root /var/www/covid-vue/dist; },则base必须为/covid/。否则 CSS/JS 加载 404。


5. 疫情防控业务特性的深度落地:风险等级动态计算与多级审核状态机

毕业设计常止步于“增删改查”,但真实疫情防控系统的核心价值在于业务规则引擎的嵌入。Spring Boot + Vue 的组合,恰恰提供了在后端做复杂计算、前端做状态可视化的能力。我们以“人员风险等级动态计算”和“审核任务状态流转”为例,展示如何让系统真正“活”起来。

5.1 基于地理围栏的风险等级计算:Spring Boot 实现

风险等级(低风险/中风险/高风险)不存储在Person表中,而是根据currentAddress实时匹配RiskArea表中的areaNamelevel字段。为提升性能,使用ConcurrentHashMap缓存风险区域数据(避免每次查询 DB):

// src/main/java/com/example/covid/service/RiskAreaService.java @Service @Slf4j public class RiskAreaService { @Autowired private RiskAreaRepository riskAreaRepository; // 缓存:areaName -> level private final Map<String, String> areaLevelCache = new ConcurrentHashMap<>(); @PostConstruct public void initCache() { List<RiskArea> areas = riskAreaRepository.findAll(); areas.forEach(area -> areaLevelCache.put(area.getAreaName(), area.getLevel())); log.info("风险区域缓存加载完成,共 {} 条", areaLevelCache.size()); } public String getRiskLevelByAddress(String address) { // 简化版:精确匹配区域名(实际可用 GeoHash 或行政区划树) return areaLevelCache.getOrDefault(address, "低风险"); } }

PersonService中调用:

// src/main/java/com/example/covid/service/PersonService.java @Service public class PersonService { @Autowired private PersonRepository personRepository; @Autowired private RiskAreaService riskAreaService; public PersonVO create(PersonDTO dto) { Person person = new Person(); BeanUtils.copyProperties(dto, person); person.setCreatedAt(LocalDateTime.now()); Person saved = personRepository.save(person); // 动态计算风险等级 String riskLevel = riskAreaService.getRiskLevelByAddress(dto.getCurrentAddress()); PersonVO vo = new PersonVO(); vo.setId(saved.getId()); vo.setName(saved.getName()); vo.setPhone(saved.getPhone()); vo.setCurrentAddress(saved.getCurrentAddress()); vo.setRiskLevel(riskLevel); // 不存库,只返回 vo.setCreatedAt(saved.getCreatedAt()); return vo; } }

5.2 多级审核状态机:用枚举 + 状态流转表驱动业务

审核任务(ReviewTask)需经历:DRAFT(草稿)→SUBMITTED(已提交)→REVIEWING(审核中)→APPROVED(通过)或REJECTED(驳回)。硬编码if-else易出错,改用状态机模式:

// src/main/java/com/example/covid/enums/ReviewStatus.java public enum ReviewStatus { DRAFT("草稿"), SUBMITTED("已提交"), REVIEWING("审核中"), APPROVED("已通过"), REJECTED("已驳回"); private final String desc; ReviewStatus(String desc) { this.desc = desc; } public String getDesc() { return desc; } }

定义合法状态流转规则(防止非法跳转):

// src/main/java/com/example/covid/config/ReviewStateMachine.java @Component public class ReviewStateMachine { // key: 当前状态, value: 允许跳转到的状态列表 private static final Map<ReviewStatus, Set<ReviewStatus>> TRANSITIONS = Map.of( ReviewStatus.DRAFT, Set.of(ReviewStatus.SUBMITTED), ReviewStatus.SUBMITTED, Set.of(ReviewStatus.REVIEWING), ReviewStatus.REVIEWING, Set.of(ReviewStatus.APPROVED, ReviewStatus.REJECTED) ); public static boolean canTransition(ReviewStatus from, ReviewStatus to) { return TRANSITIONS.getOrDefault(from, Collections.emptySet()).contains(to); } }

Controller 中校验:

@PostMapping("/review/{id}/approve") public Result<String> approve(@PathVariable Long id) { ReviewTask task = reviewTaskRepository.findById(id).orElseThrow(); if (!ReviewStateMachine.canTransition(task.getStatus(), ReviewStatus.APPROVED)) { return Result.fail("当前状态不允许通过操作"); } task.setStatus(ReviewStatus.APPROVED); task.setApprovedAt(LocalDateTime.now()); reviewTaskRepository.save(task); return Result.success("审核通过"); }

5.3 Vue 前端状态可视化:使用 ElTag 与状态颜色映射表

ReviewList.vue中,将状态码转为带颜色的标签:

<template> <el-table :data="tableData"> <!-- ... --> <el-table-column prop="status" label="状态" width="120"> <template #default="{ row }"> <el-tag :type="getStatusType(row.status)" disable-transitions> {{ getStatusText(row.status) }} </el-tag> </template> </el-table-column> </el-table> </template> <script setup lang="ts"> const getStatusText = (status: string) => { const map: Record<string, string> = { DRAFT: '草稿', SUBMITTED: '已提交', REVIEWING: '审核中', APPROVED: '已通过', REJECTED: '已驳回' } return map[status] || status } const getStatusType = (status: string) => { const map: Record<string, string> = { DRAFT: 'info', SUBMITTED: 'warning', REVIEWING: 'primary', APPROVED: 'success', REJECTED: 'danger' } return map[status] || 'info' } </script>

提示:状态文本与颜色映射应与后端ReviewStatus枚举严格一致。前端不应自行定义新状态,所有状态变更必须经后端 API 触发并返回最新状态,保证数据源头唯一。


本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询