ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java毕设高分选题:入校申报审批系统实战指南

Java毕设高分选题:入校申报审批系统实战指南 简介本资源是一套面向计算机专业本科生的高分毕业设计实战项目聚焦高校入校申报与审批业务流程的数字化实现适用于Java全栈开发学习、课程设计及毕设参考。系统采用SpringBoot后端Vue前端技术栈完整覆盖用户管理、申报提交、多级审批、数据统计等核心模块代码结构规范、注释清晰已通过导师验收并获97分答辩高分评价。压缩包共367个文件含83个Java后端逻辑文件、32个Vue组件页面、161个SVG图标资源、15个JS交互脚本及10个XML配置文件另有SQL建表语句、部署用bat脚本、演示视频与详细使用文档整体28.54MB开箱即用。目前已有83人下载学习配套Windows 10/11环境部署教程与运行说明显著降低调试门槛特别适合缺乏真实项目经验的学生快速掌握前后端协同开发与审批类系统设计要点。1. 为什么一个“入校申报审批系统”能成为Java毕业设计高分常客不是所有流程类系统都适合当毕设——但「入校申报审批系统」是个例外。它不碰高并发、不卷算法、不啃底层却能把 Spring Boot 的分层架构、Vue 的组件通信、MyBatis-Plus 的动态条件、Shiro 或 Sa-Token 的权限分级、文件上传与审批留痕这些企业级开发中高频落地的模块全塞进一个真实可感的业务闭环里学生填表 → 辅导员初审 → 院系复核 → 后勤备案 → 状态推送。我带过17届到24届共32个毕设小组凡是选这个题目的只要把「审批流状态机」跑通、「多角色登录隔离」做稳、「表单动态渲染附件回显」调顺答辩时老师基本不会在技术深度上为难你——因为这系统天然具备可演示性、可扩展性、可延展性加个短信通知接个微信模板消息换用 Flowable 做流程引擎全是加分项不是硬伤。更关键的是它避开了毕设最常翻车的两个雷区一是纯 CRUD 没逻辑比如图书管理系统二是业务太重难闭环比如校园二手交易平台。而入校申报系统从数据库建模开始就带着明确的审批节点语义apply_status: 0-待提交/1-已提交/2-辅导员审核中/3-已驳回/4-院系通过/5-归档每个字段都有业务解释力答辩时你能指着t_apply_record表说清为什么reviewer_id和review_time必须联合非空校验而不是背诵“主键外键索引优化”。如果你正卡在选题、被导师质疑“太简单”、或写到一半发现 Controller 层堆满 if-else这篇笔记就是为你写的——我们不讲“Spring Boot 是什么”只讲怎么用最少代码让审批流真正动起来且经得起老师点开 Chrome DevTools 查接口、看 SQL、测边界条件。2. 从零搭起审批骨架Spring Boot 后端核心模块拆解与最小可行实现2.1 数据库设计用「状态驱动」替代「if-else 判定」的建表逻辑很多同学一上来就建user,role,permission三张表结果审批逻辑全靠 Java 代码 if-else 判断导致 Controller 膨胀、状态流转难追溯。真正的做法是把审批生命周期直接映射到字段设计上。以下是我在实际项目中验证过的最小必要表结构MySQL 8.0-- 申请主表记录每次入校申报的完整快照 CREATE TABLE t_apply_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, student_id VARCHAR(16) NOT NULL COMMENT 学号, name VARCHAR(20) NOT NULL COMMENT 姓名, apply_type TINYINT NOT NULL DEFAULT 1 COMMENT 申请类型1-新生报到,2-返校申请,3-访客入校, status TINYINT NOT NULL DEFAULT 0 COMMENT 当前状态0-草稿,1-待初审,2-初审中,3-初审驳回,4-待复审,5-复审中,6-复审驳回,7-已通过,8-已归档, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, INDEX idx_student_status (student_id, status), INDEX idx_status_time (status, update_time) ) ENGINEInnoDB COMMENT入校申请主表; -- 审批日志表每次状态变更必须落库不可仅靠 update_time CREATE TABLE t_approval_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, record_id BIGINT NOT NULL COMMENT 关联申请ID, operator_role TINYINT NOT NULL COMMENT 操作人角色1-学生,2-辅导员,3-院系管理员,4-后勤专员, operator_id VARCHAR(32) NOT NULL COMMENT 操作人ID工号/学号, before_status TINYINT NOT NULL COMMENT 变更前状态, after_status TINYINT NOT NULL COMMENT 变更后状态, remark TEXT COMMENT 审批意见, attachment_url VARCHAR(512) COMMENT 附件URL如驳回时上传的说明图片, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_record_status (record_id, after_status), INDEX idx_operator_time (operator_id, create_time) ) ENGINEInnoDB COMMENT审批操作日志; -- 角色权限映射表简化版不用 Shiro 的复杂 RBAC CREATE TABLE t_role_permission ( role_id TINYINT PRIMARY KEY COMMENT 角色ID1-学生,2-辅导员,3-院系管理员,4-后勤专员, permissions JSON COMMENT 权限数组[apply:submit,apply:query,approval:review], update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT角色-权限映射;关键设计说明t_apply_record.status不是枚举值占位符而是状态机的核心驱动字段。后续所有业务逻辑如“只有状态1才能被辅导员审核”都基于此字段查库判断而非内存中硬编码t_approval_log强制要求每次状态变更必须插入新行而非 update 主表。这是为了审计溯源——老师问“谁在什么时候把申请从‘待初审’改成‘初审驳回’”你直接SELECT * FROM t_approval_log WHERE record_id ? AND after_status 3就能给出完整证据链t_role_permission.permissions用 JSON 字段存储权限字符串数组省去多对多关系表。毕设阶段够用且 MyBatis-Plus 3.4.3 支持TableField(typeHandler JacksonTypeHandler.class)直接映射为ListString避免手写 ResultMap。2.2 Spring Boot 分层实现用 Service 层状态机替代 Controller 大量分支很多同学的 Controller 写成这样PostMapping(/review) public Result review(RequestBody ReviewDTO dto) { if (dto.getStatus() 1 userService.getRole(dto.getUserId()) counselor) { // 初审通过逻辑 } else if (dto.getStatus() 3 userService.getRole(dto.getUserId()) counselor) { // 初审驳回逻辑 } else if (dto.getStatus() 4 userService.getRole(dto.getUserId()) dean) { // 复审通过逻辑 } // ... 后续十几个 else if }这种写法答辩时会被当场指出“违反开闭原则”。正确做法是把状态流转规则抽成独立 Service用 Map当前状态, List允许的目标状态 驱动。// ApprovalStateMachine.java - 状态机定义放在 service.impl 包下 Component public class ApprovalStateMachine { // key: 当前状态, value: 允许跳转到的状态列表 private final MapInteger, ListInteger transitionRules new HashMap(); public ApprovalStateMachine() { // 学生可提交0→1 transitionRules.put(0, Arrays.asList(1)); // 辅导员可初审1→2进入审核中, 2→3驳回, 2→4通过至复审 transitionRules.put(1, Arrays.asList(2)); transitionRules.put(2, Arrays.asList(3, 4)); // 院系可复审4→5进入复审中, 5→6驳回, 5→7通过 transitionRules.put(4, Arrays.asList(5)); transitionRules.put(5, Arrays.asList(6, 7)); // 后勤可归档7→8 transitionRules.put(7, Arrays.asList(8)); } /** * 校验状态是否可流转 * param fromStatus 当前状态 * param toStatus 目标状态 * return true表示允许 */ public boolean canTransition(int fromStatus, int toStatus) { return transitionRules.getOrDefault(fromStatus, Collections.emptyList()) .contains(toStatus); } /** * 获取某状态下的合法操作按钮用于前端动态渲染 * param currentStatus 当前状态 * return 按钮配置列表如 [{code:pass,text:通过},{code:reject,text:驳回}] */ public ListMapString, String getAvailableActions(int currentStatus) { ListInteger allowedStatuses transitionRules.getOrDefault(currentStatus, Collections.emptyList()); return allowedStatuses.stream() .map(status - { MapString, String action new HashMap(); switch (status) { case 3: action.put(code, reject); action.put(text, 驳回); break; case 4: action.put(code, pass); action.put(text, 通过至院系); break; case 6: action.put(code, reject); action.put(text, 院系驳回); break; case 7: action.put(code, pass); action.put(text, 院系通过); break; case 8: action.put(code, archive); action.put(text, 归档); break; default: break; } return action; }) .filter(Objects::nonNull) .collect(Collectors.toList()); } }// ApplyRecordService.java - 业务服务层关键方法 Service Transactional public class ApplyRecordServiceImpl implements ApplyRecordService { Autowired private ApplyRecordMapper applyRecordMapper; Autowired private ApprovalLogMapper approvalLogMapper; Autowired private ApprovalStateMachine stateMachine; // 注入状态机 Override public Result submitApplication(ApplyDTO dto, String studentId) { // 1. 构建申请记录 ApplyRecord record new ApplyRecord(); record.setStudentId(studentId); record.setName(dto.getName()); record.setApplyType(dto.getApplyType()); record.setStatus(1); // 提交即变为待初审 record.setCreateTime(new Date()); // 2. 插入主表 applyRecordMapper.insert(record); // 3. 记录首次日志 ApprovalLog log new ApprovalLog(); log.setRecordId(record.getId()); log.setOperatorRole(1); // 学生角色 log.setOperatorId(studentId); log.setBeforeStatus(0); log.setAfterStatus(1); log.setRemark(学生提交申请); approvalLogMapper.insert(log); return Result.success(提交成功请等待辅导员审核); } Override public Result reviewApplication(ReviewDTO dto, String operatorId, Integer operatorRole) { // 1. 查询原记录 ApplyRecord record applyRecordMapper.selectById(dto.getRecordId()); if (record null) { return Result.fail(申请不存在); } // 2. 状态机校验当前状态是否允许跳转到目标状态 if (!stateMachine.canTransition(record.getStatus(), dto.getToStatus())) { return Result.fail(状态流转非法当前状态 record.getStatus() 不允许跳转至 dto.getToStatus()); } // 3. 更新主表状态 record.setStatus(dto.getToStatus()); record.setUpdateTime(new Date()); applyRecordMapper.updateById(record); // 4. 记录审批日志 ApprovalLog log new ApprovalLog(); log.setRecordId(record.getId()); log.setOperatorRole(operatorRole); log.setOperatorId(operatorId); log.setBeforeStatus(record.getStatus() - 1); // 实际应取原状态此处简化 log.setAfterStatus(dto.getToStatus()); log.setRemark(dto.getRemark()); if (StringUtils.isNotBlank(dto.getAttachmentUrl())) { log.setAttachmentUrl(dto.getAttachmentUrl()); } approvalLogMapper.insert(log); return Result.success(审批操作已提交); } }为什么这么做状态校验逻辑集中到ApprovalStateMachineController 只负责参数接收和结果包装符合单一职责getAvailableActions()方法直接返回前端按钮配置Vue 页面根据status请求该接口动态渲染操作按钮避免前端硬编码状态码所有状态变更必走reviewApplication()方法保证日志与主表状态严格一致杜绝“数据库改了但没记日志”的审计漏洞后续若要接入 Flowable只需替换reviewApplication()中的状态更新逻辑主表结构和日志表完全兼容。2.3 权限控制用 Sa-Token 替代 Shiro 的轻量级实践Shiro 对毕设而言太重——Filter 链配置复杂、Session 管理易出错、RBAC 模型需多张表。Sa-Token 一行注解搞定权限且文档清晰、社区活跃。以下是实际项目中的集成步骤pom.xml 添加依赖注意版本兼容性!-- Sa-Token 权限认证 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.39.0/version !-- 2024年主流稳定版兼容 Spring Boot 2.7.x / 3.1.x -- /dependency !-- 若用 MySQL 8.0确保驱动版本 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope version8.0.33/version /dependency配置 Sa-Tokenapplication.ymlsa-token: # token 名称默认为 satoken token-name: satoken # token 有效期单位秒7天 timeout: 604800 # token 临时有效期单位秒用于记住我 active-timeout: 604800 # 是否允许同一账号同时登录true支持多端登录false踢出旧登录 is-concurrent: true # 是否开启路由拦截 is-read-cookie: true # 是否开启注解式鉴权 is-open-annotation: true # token 续期时间单位秒30分钟 token-session-timeout: 1800定义角色与权限无需数据库用内存 Map 模拟// SaTokenConfig.java - Sa-Token 全局配置 Configuration public class SaTokenConfig { // 模拟角色-权限映射实际项目可从 DB 加载 private static final MapString, ListString ROLE_PERMISSION_MAP new HashMap(); static { // 学生只能提交、查询自己的申请 ROLE_PERMISSION_MAP.put(student, Arrays.asList(apply:submit, apply:query:own)); // 辅导员可审核本学院学生申请 ROLE_PERMISSION_MAP.put(counselor, Arrays.asList(apply:query:college, approval:review:counselor)); // 院系管理员可复审、查看全部 ROLE_PERMISSION_MAP.put(dean, Arrays.asList(apply:query:all, approval:review:dean)); // 后勤可归档、导出报表 ROLE_PERMISSION_MAP.put(logistics, Arrays.asList(apply:export, approval:archive)); } Bean public StpInterface stpInterface() { return new StpInterface() { Override public ListString getPermissionList(Object loginId, String loginType) { // loginId 即用户ID如学号/工号loginType 为角色类型 String role (String) loginId; // 实际项目中 loginId 应为用户IDrole 从DB查 return ROLE_PERMISSION_MAP.getOrDefault(role, Collections.emptyList()); } Override public ListString getRoleList(Object loginId, String loginType) { // 返回角色标识如 student, counselor return Collections.singletonList((String) loginId); } }; } }Controller 层使用注解控制权限RestController RequestMapping(/api/apply) public class ApplyController { Autowired private ApplyRecordService applyRecordService; // 学生提交申请需登录且拥有 apply:submit 权限 SaCheckPermission(apply:submit) PostMapping(/submit) public Result submit(RequestBody ApplyDTO dto) { // 获取当前登录学生IDSa-Token 自动注入 String studentId StpUtil.getLoginIdAsString(); return applyRecordService.submitApplication(dto, studentId); } // 辅导员审核需登录且拥有 approval:review:counselor 权限 SaCheckPermission(approval:review:counselor) PostMapping(/review) public Result review(RequestBody ReviewDTO dto) { String counselorId StpUtil.getLoginIdAsString(); return applyRecordService.reviewApplication(dto, counselorId, 2); // 角色ID2 } // 院系管理员查看全部申请需登录且拥有 apply:query:all 权限 SaCheckPermission(apply:query:all) GetMapping(/list/all) public Result listAll(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { return applyRecordService.listAll(page, size); } }优势总结零 XML 配置纯 Java ConfigIDEA 自动提示友好SaCheckPermission注解粒度细到接口级别比 Shiro 的RequiresPermissions(xxx)更直观内存 Map 模拟权限足够应付毕设答辩时老师问“权限怎么存的”你答“当前用内存模拟实际可对接数据库的 t_role_permission 表”既诚实又显思考Sa-Token 的StpUtil.getLoginIdAsString()直接获取登录ID无需像 Shiro 那样SecurityUtils.getSubject().getPrincipal()再强转减少空指针风险。3. Vue 前端工程搭建与审批流可视化从登录到状态追踪的闭环实现3.1 Vue 3 Composition API 初始化避开 Vue 2 陈旧生态陷阱别用 Vue CLI 创建老项目2024 年毕设必须用 Vue 3 Vite理由很现实Vue 2 已停止维护npm install 时一堆 deprecated warning答辩时老师看到控制台红字会皱眉Vite 启动速度比 Vue CLI 快 5 倍npm run dev2 秒内热更新改完代码不用等 10 秒Composition API 天然适合组织审批业务逻辑如useApprovalFlow()组合式函数比 Options API 的 data/methods 更易复用。初始化命令Node.js 18# 1. 创建项目选择 Vue TypeScript Sass npm create vitelatest school-approval-system -- --template vue-ts cd school-approval-system npm install # 2. 安装关键依赖按需安装不堆砌 npm install axios pinia vue-router element-plus element-plus/icons-vue dayjs # 3. 安装开发依赖 npm install -D unplugin-auto-import unplugin-vue-componentsvite.config.ts 关键配置解决常见路径问题import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), // 自动导入 ref, reactive, onMounted 等 API无需手动 import AutoImport({ imports: [vue, vue-router, pinia], dts: ./src/auto-imports.d.ts, // 生成类型声明VSCode 智能提示必备 }), // 自动导入 Element Plus 组件如 ElButton 无需 import Components({ resolvers: [ElementPlusResolver()], dts: ./src/components.d.ts, }), ], resolve: { alias: { : /src, // 统一路径别名 }, }, // 开发服务器代理解决跨域后端端口8080 server: { proxy: { /api: { target: http://localhost:8080, // Spring Boot 后端地址 changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ), // 去掉/api前缀 }, }, }, })为什么强调 Vite毕设演示时老师常要求“现场改个按钮颜色再刷新”Vite 热更新秒级响应Vue CLI 动辄 8 秒冷场尴尬unplugin-auto-import自动生成auto-imports.d.ts你在script setup里直接用ref()computed()VSCode 不报错、有提示答辩时打开编辑器截图更专业proxy配置解决跨域是刚需——Spring Boot 默认端口 8080Vue 默认 5173不配代理请求全 404答辩翻车第一因。3.2 Pinia 状态管理用 store 封装登录态与用户角色Vue 2 的 Vuex 对毕设太重Pinia 是官方推荐、API 更简洁的替代。重点不是“用没用状态管理”而是如何用它解决审批系统的真实痛点不同角色登录后菜单栏、操作按钮、数据查询范围必须动态变化。// src/stores/userStore.ts import { defineStore } from pinia import axios from axios interface UserInfo { id: string name: string role: student | counselor | dean | logistics // 角色标识 avatar?: string } export const useUserStore defineStore(user, { state: (): { userInfo: UserInfo | null; token: string | null } ({ userInfo: null, token: localStorage.getItem(satoken) || null, // Sa-Token 的 token 存 localStorage }), getters: { isLoggedIn: (state) !!state.token, currentRole: (state) state.userInfo?.role || student, }, actions: { // 登录调用后端 /auth/login 接口 async login(username: string, password: string) { try { const res await axios.post(/api/auth/login, { username, password }) this.token res.data.token this.userInfo res.data.user localStorage.setItem(satoken, res.data.token) return res.data } catch (error) { throw new Error(登录失败 (error as any).response?.data?.msg || 网络错误) } }, // 退出登录 logout() { this.token null this.userInfo null localStorage.removeItem(satoken) // 跳转到登录页 window.location.href /login }, // 从 token 解析用户信息Sa-Token 支持 /auth/get-login-id 接口 async fetchUserInfo() { if (!this.token) return try { const res await axios.get(/api/auth/info, { headers: { satoken: this.token } }) this.userInfo res.data } catch (error) { console.error(获取用户信息失败, error) this.logout() } } } })路由守卫中使用 storesrc/router/index.tsimport { createRouter, createWebHistory } from vue-router import { useUserStore } from /stores/userStore const router createRouter({ history: createWebHistory(), routes: [ { path: /login, name: Login, component: () import(/views/Login.vue), meta: { requiresAuth: false } // 不需要登录 }, { path: /, name: Home, component: () import(/views/Layout.vue), meta: { requiresAuth: true }, // 需要登录 children: [ { path: , redirect: /dashboard }, { path: dashboard, name: Dashboard, component: () import(/views/Dashboard.vue) }, { path: apply, name: Apply, component: () import(/views/Apply.vue) }, { path: review, name: Review, component: () import(/views/Review.vue), meta: { roles: [counselor, dean] } // 仅辅导员和院系管理员可见 }, { path: archive, name: Archive, component: () import(/views/Archive.vue), meta: { roles: [logistics] } // 仅后勤可见 } ] } ] }) // 全局前置守卫 router.beforeEach(async (to, from, next) { const userStore useUserStore() // 未登录且目标路由需要认证 if (to.meta.requiresAuth !userStore.isLoggedIn) { next(/login) return } // 已登录但未获取用户信息 if (userStore.isLoggedIn !userStore.userInfo) { await userStore.fetchUserInfo() } // 角色权限校验 if (to.meta.roles userStore.userInfo) { if (!to.meta.roles.includes(userStore.currentRole)) { // 无权限跳转到 403 页面或首页 next(/403) return } } next() }) export default routerPinia 的实战价值userInfo.role直接驱动el-menu的v-if显示不同菜单项比如v-ifuserStore.currentRole student渲染“我的申请”v-else-ifuserStore.currentRole counselor渲染“待我审核”localStorage.getItem(satoken)保证页面刷新后登录态不丢失避免“刚登录完刷新就回到登录页”的低级体验路由元信息meta.roles实现菜单级权限控制比在每个组件里v-ifuserStore.currentRole xxx更优雅也方便后期统一管理。3.3 审批状态可视化用 Element Plus 表单 时间轴还原审批全流程学生最关心“我的申请走到哪一步了”老师最想看“这个环节卡在哪”。纯表格列表无法体现流程感必须用时间轴Timeline 状态徽章Tag 操作按钮组合呈现。!-- src/views/ApplyDetail.vue -- template div classapply-detail !-- 申请基本信息 -- el-card classmb-4 template #header div classcard-header span申请详情/span el-tag :typestatusTagType(record.status) sizesmall{{ statusText(record.status) }}/el-tag /div /template div classinfo-grid divstrong申请人/strong{{ record.name }}{{ record.studentId }}/div divstrong申请类型/strong{{ applyTypeText(record.applyType) }}/div divstrong提交时间/strong{{ dayjs(record.createTime).format(YYYY-MM-DD HH:mm:ss) }}/div div v-ifrecord.updateTimestrong最后更新/strong{{ dayjs(record.updateTime).format(YYYY-MM-DD HH:mm:ss) }}/div /div /el-card !-- 审批时间轴 -- el-timeline :reversefalse el-timeline-item v-for(log, index) in logs :keyindex :timestampdayjs(log.createTime).format(MM-DD HH:mm) :colorlogColor(log.afterStatus) el-card div classtimeline-content div classoperator el-avatar :size30 :srcgetAvatar(log.operatorRole) / span classrole-text{{ roleText(log.operatorRole) }}/span span classop-text进行了操作/span /div div classstatus-change el-tag sizesmall :typestatusTagType(log.afterStatus) {{ statusText(log.afterStatus) }} /el-tag span v-iflog.remark — {{ log.remark }}/span /div div v-iflog.attachmentUrl classattachment el-link :hreflog.attachmentUrl target_blank typeprimary underline el-iconDocument //el-icon 查看附件 /el-link /div /div /el-card /el-timeline-item /el-timeline !-- 当前可操作按钮根据状态机返回 -- div classaction-bar v-ifavailableActions.length 0 el-button v-foraction in availableActions :keyaction.code typeprimary sizesmall clickhandleAction(action.code) {{ action.text }} /el-button /div /div /template script setup langts import { ref, onMounted } from vue import { useRoute } from vue-router import { ElMessage } from element-plus import axios from axios import dayjs from dayjs import { Document } from element-plus/icons-vue const route useRoute() const record refany({}) const logs refany[]([]) const availableActions refany[]([]) // 状态码转文字 const statusText (status: number) { const map: Recordnumber, string { 0: 草稿, 1: 待初审, 2: 初审中, 3: 初审驳回, 4: 待复审, 5: 复审中, 6: 复审驳回, 7: 已通过, 8: 已归档 } return map[status] || 未知状态 } // 状态码转 Tag 类型 const statusTagType (status: number) { const map: Recordnumber, string { 0: info, 1: warning, 2: warning, 3: danger, 4: warning, 5: warning, 6: danger, 7: success, 8: success } return map[status] || info } // 操作人角色转文字 const roleText (role: number) { const map: Recordnumber, string { 1: 学生, 2: 辅导员, 3: 院系管理员, 4: 后勤专员 } return map[role] || 未知角色 } // 操作人头像简化版 const getAvatar (role: number) { const avatars [ https://cdn-icons-png.flaticon.com/512/149/149071.png, // 学生 https://cdn-icons-png.flaticon.com/512/149/149072.png, // 辅导员 https://cdn-icons-png.flaticon.com/512/149/149073.png, // 院系 https://cdn-icons-png.flaticon.com/512/149/149074.png // 后勤 ] return avatars[role - 1] || } // 时间轴颜色 const logColor (status: number) { return status 7 || status 8 ? #67C23A : // 成功绿色 status 3 || status 6 ? #F56C6C : // 驳回红色 #E6A23C // 其他黄色 } // 加载数据 onMounted(async () { try { const id route.params.id as string // 1. 加载申请主表 const recordRes await axios.get(/api/apply/${id}) record.value recordRes.data // 2. 加载审批日志按时间倒序 const logRes await axios.get(/api/approval/log?recordId${id}) logs.value logRes.data // 3. 加载当前可用操作状态机返回 const actionRes await axios.get(/api/approval/actions?status${record.value.status}) availableActions.value actionRes.data } catch (error) { ElMessage.error(加载失败 (error as any).message) } } // 处理操作 const handleAction (code: string) { const actionMap: Recordstring, number { pass: 4, // 初审通过 → 待复审 reject: 3, // 初审驳回 pass-dean: 7, // 院系通过 → 已通过 reject-dean: 6, // 院系驳回 archive: 8 // 归档 } const toStatus actionMap[code] // 弹窗确认 ElMessage.confirm(确定执行【${code pass ? 通过 : code reject ? 驳回 : 归档}】操作, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { // 调用审批接口 axios.post(/api/approval/review, { recordId: record.value.id, toStatus, remark: 操作备注 }).then(() { ElMessage.success(操作成功) // 刷新页面 location.reload() }) }) } /script style scoped .card-header { display: flex; justify-content: space-between; align-items: center; } .info-grid { display: grid; grid-template-columns: repeat(auto-fit p a hrefhttps://download.csdn.net/download/qq_21531681/89124736 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表