关于审批流的记录

news/2025/10/9 17:34:10/文章来源:https://www.cnblogs.com/guanyifan/p/19131608

第三步:前端实现(审批消息展示与操作)
基于 Vue + Element UI 实现审批人页面的「消息通知」和「待审批列表」,集成到你的现有排班系统中。
1. 全局消息通知(顶部导航栏)
在系统顶部导航栏添加「消息图标」,显示未读消息数

 

<template> <div class="header-notify"> <!-- 消息图标 + 未读红点 --> <el-dropdown @command="handleNotifyCommand" placement="bottom-right"> <div class="notify-icon"> <el-icon size="20"><Bell /></el-icon> <span v-if="unreadCount > 0" class="unread-dot">{{ unreadCount }}</span> </div> <el-dropdown-menu slot="dropdown" class="notify-dropdown"> <el-dropdown-item disabled class="dropdown-header">待审批({{ unreadCount }})</el-dropdown-item> <el-dropdown-item v-for="todo in todoList" :key="todo.applyId" :command="{ type: 'todo', data: todo }" class="todo-item" > <div class="todo-info"> <p class="todo-title">{{ todo.businessInfo }}</p> <p class="todo-time">{{ formatTime(todo.applyTime) }}</p> </div> <el-button size="mini" type="text" @click.stop="handleGotoApproval(todo)">处理</el-button> </el-dropdown-item> <el-dropdown-item disabled v-if="todoList.length === 0">暂无待审批事项</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </template> <script> import { Bell } from '@element-plus/icons-vue'; import { getApproverTodoList, getUnreadNotifyCount } from '@/api/hrm/approval'; export default { components: { Bell }, data() { return { unreadCount: 0, todoList: [], timer: null // 定时器:定时刷新未读消息 }; }, mounted() { // 初始化加载待审批列表和未读消息数 this.loadTodoList(); this.loadUnreadCount(); // 定时刷新(5分钟一次,可根据需求调整) this.timer = setInterval(() => { this.loadUnreadCount(); this.loadTodoList(); }, 300000); }, beforeUnmount() { clearInterval(this.timer); // 清除定时器 }, methods: { // 加载待审批列表 async loadTodoList() { const userId = this.$store.state.user.id; // 从全局状态获取当前用户ID const res = await getApproverTodoList(userId); this.todoList = res.data; }, // 加载未读消息数 async loadUnreadCount() { const userId = this.$store.state.user.id; const res = await getUnreadNotifyCount(userId); this.unreadCount = res.data; }, // 格式化时间 formatTime(time) { return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }, // 处理消息命令(如点击“处理”按钮) handleNotifyCommand(command) { if (command.type === 'todo') { this.handleGotoApproval(command.data); } }, // 跳转到审批详情页 handleGotoApproval(todo) { this.$router.push({ path: '/hrm/approval/detail', query: { applyId: todo.applyId, nodeId: todo.nodeId } }); } } }; </script> <style scoped> .header-notify { position: relative; margin-right: 20px; cursor: pointer; } .unread-dot { position: absolute; top: -5px; right: -5px; width: 18px; height: 18px; line-height: 18px; border-radius: 50%; background-color: #f56c6c; color: white; font-size: 12px; text-align: center; } .notify-dropdown { width: 400px; max-height: 500px; overflow-y: auto; } .todo-item { padding: 12px; border-bottom: 1px solid #f5f5f5; } .todo-title { font-size: 14px; color: #333; margin-bottom: 4px; } .todo-time { font-size: 12px; color: #999; } </style>

 

2. 审批详情页(处理审批操作)

审批人点击「处理」后进入详情页,查看申请信息并执行同意 / 拒绝操作:
 
<template> <el-card class="approval-detail-card"> <div slot="header" class="card-header"> <h2>审批详情</h2> <span class="apply-no">审批单号:{{ applyDetail.applyNo }}</span> </div> <!-- 申请基本信息 --> <el-form :model="applyDetail" label-width="120px" class="apply-form"> <el-form-item label="审批类型"> <el-tag type="info">{{ applyTypeMap[applyDetail.applyType] }}</el-tag> </el-form-item> <el-form-item label="申请人"> {{ applyDetail.applyUserName }}({{ applyDetail.applyTime | formatTime }}) </el-form-item> <el-form-item label="申请内容"> <div class="business-info">{{ applyDetail.businessInfo }}</div> </el-form-item> <el-form-item label="申请备注"> <div class="apply-remark">{{ applyDetail.remark || '无' }}</div> </el-form-item> <el-form-item label="审批节点"> <div class="approval-node-list"> <div v-for="(node, index) in approvalNodeList" :key="node.id" class="approval-node" > <div class="node-header"> <span class="node-seq">第{{ node.nodeSeq }}审批人</span> <el-tag :type="getNodeStatusTagType(node.nodeStatus)"> {{ getNodeStatusText(node.nodeStatus) }} </el-tag> </div> <div class="node-content"> <p>审批人:{{ node.approverName }}</p> <p>审批时间:{{ node.approveTime ? (node.approveTime | formatTime) : '未处理' }}</p> <p>审批意见:{{ node.approveOpinion || '无' }}</p> </div> <!-- 当前用户的待处理节点:显示审批操作 --> <div v-if="node.nodeStatus === 0 && node.approverId === currentUserId" class="node-operation"> <el-form :model="handleForm" ref="handleFormRef" label-width="80px"> <el-form-item label="审批结果" prop="approveResult" required> <el-radio-group v-model="handleForm.approveResult"> <el-radio label="1">同意</el-radio> <el-radio label="2">拒绝</el-radio> </el-radio-group> </el-form-item> <el-form-item label="审批意见" prop="opinion"> <el-input v-model="handleForm.opinion" type="textarea" rows="3" placeholder="请输入审批意见(可选)" ></el-input> </el-form-item> <el-form-item> <el-button @click="handleCancel">取消</el-button> <el-button type="primary" @click="handleSubmitApproval">提交审批</el-button> </el-form-item> </el-form> </div> </div> </div> </el-form-item> </el-form> </el-card> </template> <script> import { getApprovalDetail, handleApproval } from '@/api/hrm/approval'; export default { filters: { formatTime(time) { return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } }, data() { return { applyDetail: {}, // 审批申请详情 approvalNodeList: [], // 审批节点列表 currentUserId: this.$store.state.user.id, // 当前用户ID applyTypeMap: { // 审批类型映射 'SHIFT_SWAP': '调班申请' }, // 审批操作表单 handleForm: { nodeId: '', // 当前处理的节点ID approveResult: '1', // 默认同意 opinion: '' } }; }, mounted() { // 从路由参数获取 applyId 和 nodeId const { applyId, nodeId } = this.$route.query; this.handleForm.nodeId = nodeId; this.loadApprovalDetail(applyId); }, methods: { // 加载审批详情 async loadApprovalDetail(applyId) { const res = await getApprovalDetail(applyId); this.applyDetail = res.data.apply; this.approvalNodeList = res.data.nodes; }, // 获取节点状态文本 getNodeStatusText(status) { const statusMap = { 0: '待审批', 1: '已同意', 2: '已拒绝' }; return statusMap[status] || '未知'; }, // 获取节点状态标签类型 getNodeStatusTagType(status) { const typeMap = { 0: 'warning', 1: 'success', 2: 'danger' }; return typeMap[status] || 'info'; }, // 提交审批 async handleSubmitApproval() { this.$refs.handleFormRef.validate(async (valid) => { if (valid) { // 转换 approveResult 为数字(1=同意,2=拒绝) this.handleForm.approveResult = Number(this.handleForm.approveResult); await handleApproval(this.handleForm); this.$message.success('审批操作已提交'); this.$router.push('/hrm/approval/todo-list'); // 返回待审批列表 } }); }, // 取消 handleCancel() { this.$router.go(-1); } } }; </script> <style scoped> .approval-detail-card { margin: 20px; } .card-header { display: flex; justify-content: space-between; align-items: center; } .apply-no { font-size: 14px; color: #666; } .apply-form { margin-top: 20px; } .business-info, .apply-remark { padding: 8px; background-color: #f5f7fa; border-radius: 4px; color: #333; } .approval-node-list { margin-top: 10px; } .approval-node { padding: 15px; border: 1px solid #f5f5f5; border-radius: 4px; margin-bottom: 10px; } .node-header { display: flex; justify-content: space-between; margin-bottom: 10px; } .node-seq { font-weight: 500; color: #333; } .node-operation { margin-top: 15px; padding-top: 15px; border-top: 1px dashed #eee; } </style>
 
 

五、第四步:实时消息推送(可选,提升体验)

为了让审批人实时收到新的审批消息(无需刷新页面),可集成 WebSocket 实现实时推送:
  1. 后端:基于 Spring WebSocket 实现用户消息订阅(按用户 ID 分组)。
  2. 前端:页面加载时建立 WebSocket 连接,监听审批消息事件,收到消息后更新未读计数和待审批列表。
示例(前端 WebSocket 封装):
// src/utils/websocket.js let websocket = null; export function initWebSocket(userId) { if ('WebSocket' in window) { // 连接地址(如 ws://localhost:8080/ws/approval?userId=123) const wsUrl = `${process.env.VUE_APP_WS_BASE_URL}/ws/approval?userId=${userId}`; websocket = new WebSocket(wsUrl); // 连接成功 websocket.onopen = function() { console.log('WebSocket 连接成功'); }; // 接收消息 websocket.onmessage = function(event) { const message = JSON.parse(event.data); // 触发全局事件,让其他组件监听 window.dispatchEvent(new CustomEvent('approvalNotify', { detail: message })); }; // 连接关闭 websocket.onclose = function() { console.log('WebSocket 连接关闭,3秒后重连'); setTimeout(() => initWebSocket(userId), 3000); // 自动重连 }; // 连接错误 websocket.onerror = function() { console.error('WebSocket 连接错误'); }; } else { alert('您的浏览器不支持 WebSocket,无法接收实时消息'); } } // 关闭连接 export function closeWebSocket() { if (websocket) { websocket.close(); } }
 
在全局入口(如 main.js 或 App.vue)初始化 WebSocket:
import { initWebSocket, closeWebSocket } from '@/utils/websocket'; // 登录后初始化(从全局状态获取用户ID) if (store.state.user.id) { initWebSocket(store.state.user.id); } // 监听全局消息事件,更新未读消息 window.addEventListener('approvalNotify', (event) => { const message = event.detail; if (message.type === 'APPROVAL_TODO') { // 刷新待审批列表和未读计数 const notifyComponent = window.notifyComponent; // 假设消息组件暴露到全局 if (notifyComponent) { notifyComponent.loadTodoList(); notifyComponent.loadUnreadCount(); } // 显示消息提示 ElMessage({ title: '新的审批通知', message: message.content, type: 'info', duration: 5000 }); } }); // 页面关闭时关闭连接 window.addEventListener('beforeunload', closeWebSocket);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/933729.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CF1726E Almost Perfect

Sol 首先不难注意到 \(p_i\) 和 \(p^{-1}_{i}\) 是距离恰好为 \(2\) 的点对。 然后不难想到图中每个连通块一定是 \(1,2,4\) 元环。 考虑只有 \(1,2\) 元环怎么做,考虑 DP,\(f_i\) 表示 \(i\) 个点的方案数,显然 \(…

Linux:线程池 - 指南

Linux:线程池 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "…

CSP-S模拟28

T1:挑战(challenge) 思路: 说是签到题(但是疑似没有T2简单?好吧,其实这题也不难,只是我傻而已) 只需要把所有的矿车挪到有矿车的最后一列,贪心和dp都可以,我写的dp。不难发现dp有两种状态转移过来,如下图,…

形式化验证提升RSA性能与部署效率

本文详细介绍了如何通过算法优化和微架构调整显著提升RSA签名在Graviton2芯片上的性能,同时利用形式化验证确保代码功能正确性,实现了33%-94%的吞吐量提升。形式化验证使RSA更快——部署也更迅速 大多数在线安全交易…

AI元人文的硅基实现可行性Ai研究报告

AI元人文的硅基实现可行性Ai研究报告 一、研究背景与核心挑战 人工智能技术正经历从"工具理性"向"价值理性"的深刻转型。在这一过程中,AI元人文构想作为一种新兴理论框架,试图通过将东方哲学智慧…

利用linux系统自带的cron 定时备份数据库,不需要写代码了

linux系统自带的cron 定时备份数据库本来在代码里面写了一个定时任务,每隔10分钟定时备份数据库,其他项目都没问题,原来部署的docker项目都没问题,这次新部署一个项目定时任务总是报错,报错信息:nsenter: reasso…

centos服务器实时备份

目标/usr/local/src 目录在两台 CentOS 7 服务器之间双向实时同步 任何一台机器目录变化,都会自动同步到另一台 开机自动运行,断网重连后继续同步过程自动安装 unison + inotify-tools 自动配置免密 SSH 自动创建 un…

task4.c

task4.cinclude<stdio.h> int main() { double x, y; char c1, c2, c3; int a1, a2, a3; scanf_s("%d%d%d", &a1, &a2,&a3); printf("a1 = %d, a2 = %d, a3= %d\n", a1, a2, a3…

Python安装与环境配置

View PostPython安装与环境配置前言 安装 首先,从Python官方网站(https://www.python.org/downloads/)下载并安装Python解释器(下图默认下载最新版本,也可以从网站上找旧的版本自行下载)其次,在安装完成后,使用…

实用指南:【双光相机配准】可见光与红外相机计算Homography

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

P14150 不动鸣神,恒常乐土

吃完饭回来写。 刚开始以为是图直接不可做。 考虑设 \(f_{i, j}\) 为第 \(i\) 个点相邻 \(j\) 个点被选且 \(i\) 不被选,把选自己的贡献用个 \(g\) 存一下即可。 然后你发现就是枚举每个儿子选不选就做完了,出这种题…

python本地生成验证码图片

from io import BytesIO from PIL import Image, ImageDraw, ImageFont from captcha.image import ImageCaptcha import random, stringdef get_captcha_picture():chr_all = string.ascii_uppercase + string.digits…

CentOS 7 一键安装 vsftpd 并创建可登录 FTP 用户 test - 教程

CentOS 7 一键安装 vsftpd 并创建可登录 FTP 用户 test - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Con…

破解工地防盗难题:如何利用国标GB28181视频平台EasyCVR实现视频监控统一管理?

破解工地防盗难题:如何利用国标GB28181视频平台EasyCVR实现视频监控统一管理?一、方案背景 在当代建筑施工领域,安全监管和防盗监控是保障工程顺利进行和资产安全的关键措施。随着科技进步,传统的监控系统已不足以…

autogen论文解读 - Sun

论文背景 该论文介绍的是微软的一个多智能体框架,曾经获得了ICLR2024大会LLM智能体专题研讨会最佳论文。在开发基于LLM的复杂应用程序时遇到了很多问题:以往的研究往往基于单个LLM 智能体,但这无法解决复杂任务,因…

高效仿真:功耗与散热攻略

在当今 IC 设计中,“功耗与热管理”是确保系统稳定性和可靠性的关键一环。本文深度聚焦仿真平台在动态功耗分析与热行为模拟中的应用,结合实际案例与现代工具,为你系统解锁仿真中的热控挑战与优化路径。 1、动态功耗…

编程开发工具集合汇总

编程开发工具集合汇总Posted on 2025-10-09 16:55 lzhdim 阅读(0) 评论(0) 收藏 举报这次提供编程开发工具集合的下载。通过网盘分享的文件:  链接: https://pan.baidu.com/s/1WWCFSerGZhhl4V0SS7UjMw?pwd=yi…

各编程语言对应的开发工具软件

各编程语言对应的开发工具软件Posted on 2025-10-09 17:00 lzhdim 阅读(0) 评论(0) 收藏 举报以下内容基于 2024~2025 年公开资料整理,给出主流编程语言与“官方/社区公认最常用”开发工具(IDE 或专用编辑器)…

Vue Day7 VueX ESLint介绍

这里先了解VueX,为后面 Vue3 的Pinia做准备VueX是一个Vue的状态(即数据)管理插件,可以帮我们管理vue通用的数据(多组件共享的数据)