Vue+Node.js+Element UI+小程序:从零搭建法院案件阅卷申请系统全流程实践

发布时间:2026/10/17 16:07:06
Vue+Node.js+Element UI+小程序:从零搭建法院案件阅卷申请系统全流程实践
从零搭建一套法院案件阅卷申请系统vue nodejs Element UI 小程序的完整实践这段时间一直在折腾一个法院场景下的案件阅卷申请系统技术栈是 vue nodejs Element UI 加小程序端。这类政务信息化项目跟普通业务系统最大的区别不在于用多 fancy 的架构而在于你把业务流程吃得多透、权限边界卡得多死。折腾完整个项目之后我把从需求分析到上线的每个环节梳理了一遍包括数据库怎么设计、接口怎么划分、小程序端怎么和 Web 端共用一套逻辑以及那些只有实际踩过坑才写得出的问题排查经验。这套内容应该对正在做类似“管理后台 小程序 审批流”项目的开发者特别有用。1. 业务模型拆解法院阅卷申请到底在“申请”什么1.1 先搞清业务流程再谈技术架构很多人拿到这类项目就开始建表写接口这是最容易翻车的做法。法院案件阅卷申请不是简单的“用户填单 - 管理员通过”就完事它背后有一套严格的线下流程需要被数字化还原。一个完整的阅卷申请链路是这样的申请人律师、当事人或其代理人在系统里看到自己关联的案件列表发起阅卷申请选择想要查看的卷宗册次填写申请事由上传必要的身份证明和委托材料。系统生成一条待审批记录推送给法院内部负责档案管理的审核人员。审核人员查看申请材料初步判断申请资格和申请范围是否合规先做一次预审。预审通过后可能还要经过业务庭室或者档案管理负责人的二次复核尤其涉及刑事案件或者有敏感信息的卷宗复核是免不了的。复核通过后系统给申请人开放在线查阅权限同步生成水印化的卷宗预览如果涉及线下阅卷则生成预约时段和阅卷室信息。整个过程中的每一次通过、驳回、退回补正都要留下完整的操作记录事后能追溯。这一点恰恰是这类系统和普通后台管理系统的本质区别普通系统关注的是“功能能不能跑”阅卷系统关注的是“每一步操作合不合法、合不合规、能不能追溯”。代码写得好不好是次要的流程上的每一个状态变更都必须合规。1.2 角色权限的边界划分在系统设计上权限模型直接决定数据库表和接口的设计方向。我最终把角色分成三类每一类的权限边界非常清晰申请人仅能查看本人名下或者通过委托关系关联的案件能发起申请能查看自己申请单的办理进度能下载已获准卷宗的阅卷副本。档案审核员能查看分配给自己的待审申请单能对申请进行预审、提交复核或驳回能管理卷宗目录的基础信息。系统管理员负责角色分配、用户管理、日志审计、基础参数配置不参与具体审批业务。这里有一个容易犯的错误以为“档案审核员”就是最高权限什么都能看。真正上线后你会发现法院内部对卷宗的查阅权限是有严格分级的审核员能看到卷宗的目录和基础信息但未必有权限直接在线浏览卷宗内容。所以我把“卷宗信息查看”和“申请审核”拆成了两个独立权限点来配置这是按真实业务经验做的取舍。1.3 为什么这套业务模型必须先于代码确定我见过不少项目在开发到一半时被业务方要求大改根源就是把业务模型讨论这一步跳过了。比如“补正材料”这个动作一开始我只做了“驳回”结果实际业务中很多申请并非不能通过而是材料不齐需要补充直接驳回会让申请人重新走一次完整流程体验极差法院侧也觉得不合理。后来增加了“退回补正”状态整个系统才算真正贴合实际场景。所以在动任何代码前我强烈建议你花时间把业务流程状态图画完整、把角色和动作的矩阵表列清楚。哪怕你是自己做一个演示项目这一套思考过程也会让你的代码结构清晰很多。2. 技术选型复盘vue、nodejs、ElementUi 和小程序端为什么这么配2.1 Vue 2 Element UI 还是 Vue 3 Element Plus项目标题里写的是 ElementUi实际操作中我做过一次权衡。如果完全从零开始我建议直接上 Vue 3 Element Plus组件更活跃生态也更现代。但如果你接手的是一个已经跑了一段时间的维护型项目或者团队里大部分人还停留在 Vue 2 的习惯上那 Vue 2 Element UI 依然完全够用毕竟这类系统的页面复杂度并不高主要就是表格、表单、弹窗、步骤条这些基础组件。我这次实际用的是 Vue 2 Element UI原因是团队里维护这套代码的同事更熟这一套且系统不需要特别花哨的交互。如果你是自学做作品集想显示自己跟得上技术趋势就选 Vue 3 Element Plus这个选择本身没有对错主要看你的使用场景。2.2 Node.js 端框架选择Express 还是 Egg/Koa后端我用的是 Node.js 的 Express 框架没有上更重的企业级框架。原因很简单这个项目的核心业务是 CRUD 加状态流转复杂度集中在业务规则和权限控制上框架本身越稳越简单越好。Express 中间件生态丰富文档多遇到问题随便一搜就有解决方案。当然如果你预期这个系统将来会接入很多外部系统比如统一身份认证、电子签章服务、短信通知平台那么换 Egg.js 这类带约定式目录结构和插件机制的框架会让你后期舒服一些。我的建议是初期用 Express 把业务跑通当项目明显变复杂时再重构到更结构化的框架这个节奏比一开始就选最重的方案更实际。2.3 小程序端的技术路线原生还是 uni-app小程序端我选了原生小程序而不是 uni-app 或 Taro。原因是我这个项目里 Web 管理后台本身就是 Vue小程序业务相对独立复用代码的需求不强原生小程序没有额外编译层调试排错最简单直接。如果你有大量代码要跨端复用那选 uni-app 可能会省事但它也会给你带来一层抽象遇到底层兼容问题的时候排查起来更头疼。原生小程序需要注意的一点是它的语法和 Vue 的响应式思想非常接近但又略有不同最典型的就是setData的显式调用。很多从 Vue 转过来的开发者一开始都会忘记在数据变更后调用setData导致界面不刷新我后面在实操部分会专门提到。2.4 数据库设计核心表结构和关联关系数据库我用的是 MySQL。表结构设计直接服务前面的业务模型核心表大致如下用户表记录系统用户的账号、姓名、角色、手机号同时冗余一个用户在法院侧的身份标识。案件表记录案件编号、案件名称、案件类型、承办法庭、关联当事人信息等。这张表的数据往往来自法院内部业务系统可能是同步过来的所以需要预留外部案件 ID。卷宗表和案件是多对一关系一个案件会有多本卷宗每本卷宗的状态要独立维护。阅卷申请表核心业务表记录申请人、申请案件、申请的卷宗范围、申请事由、附件材料地址、当前状态和处理时间线。审批记录表保存每一次审批动作的信息包括审批人、审批动作、审批意见、操作时间。附件表统一管理所有上传的文件包括申请材料、卷宗扫描件、水印预览件。这里最核心的设计要点是把“申请单”和“审批记录”分开而不是在申请单上堆一堆审批字段。这样做的好处是审批历史天然完整以后要出审计报表、查操作轨迹都极其方便。字段状态我设计了 draft、pending_first_review、pending_second_review、approved、rejected、returned、expired 这几个分别对应草稿、待初审、待复核、通过、驳回、退回补正、已过期。状态流转只能按固定方向走不允许往前跳。3. 数据库设计与核心接口从申请单到卷宗借阅的流转链路3.1 申请模块的接口设计我把申请相关的接口都收敛在一组路由下面// 路由示例 router.post(/api/application/create, authMiddleware, applicationController.createApplication); router.post(/api/application/update, authMiddleware, applicationController.updateApplication); router.post(/api/application/submit, authMiddleware, applicationController.submitApplication); router.get(/api/application/detail, authMiddleware, applicationController.getApplicationDetail); router.get(/api/application/list, authMiddleware, applicationController.getApplicationList); router.post(/api/application/withdraw, authMiddleware, applicationController.withdrawApplication);这里需要注意的一个设计细节是创建和更新是独立接口而提交也是一个独立动作。原因是在真实操作中申请人往往是先填了一部分信息保存成草稿等材料齐全了再正式提交。如果只做一个“创建即提交”草稿功能就没了后面会被业务方追着改。接口权限控制上所有申请人都必须带上 token接口里再判断操作的数据是否属于当前用户。这里最容易被攻击的点是“水平越权”也就是 A 用户尝试操作 B 用户的申请单。所以每个查询和更新方法里都强制拼接用户 ID 作为过滤条件而不是单纯依赖前端传过来的申请单 ID。这一点在后端写的时候要形成肌肉记忆。3.2 审核模块的状态推进逻辑审核模块是这类系统里业务复杂度最高的地方因为审批不是一个简单的“同意/拒绝”它涉及多级流转。我设计了一个统一的状态推进方法async function transitionApplication(applicationId, currentStatus, targetStatus, operator, options {}) { const app await db.query(SELECT * FROM application WHERE id ? FOR UPDATE, [applicationId]); if (!app) throw new Error(申请单不存在); if (app.status ! currentStatus) throw new Error(状态异常无法执行该操作); const allowed getAllowedTransitions(currentStatus); if (!allowed.includes(targetStatus)) throw new Error(非法状态流转); // 更新状态 await db.query(UPDATE application SET status ? , updated_at NOW() WHERE id ?, [targetStatus, applicationId]); // 写入审批记录 await db.query( INSERT INTO approval_record (application_id, operator, action, comment, created_at) VALUES (?, ?, ?, ?, NOW()), [applicationId, operator, targetStatus, options.comment || ] ); // 如果审批通过异步处理阅卷权限的开通 if (targetStatus approved) { await enableAccessPermission(applicationId); } }代码里通过FOR UPDATE锁行来避免并发状态下重复审核通过预置的getAllowedTransitions限制状态只能按官方流程走即使有人恶意调用接口也无法把已驳回的申请直接改成已通过。这套思路同样适用于其他有状态流转的业务。状态流转表长这样当前状态允许流转到的状态触发动作draftpending_first_review申请人提交pending_first_reviewapproved / returned / rejected初审通过 / 退回补正 / 驳回returnedpending_first_review / draft申请人重新提交 / 申请人撤回pending_second_reviewapproved / rejected复核通过 / 复核驳回approvedexpired超出授权查阅期限3.3 卷宗目录与在线预览的实现思路卷宗不是简简单单一个附件文件它通常是一个层级目录案件下有卷册卷册下有文件页文件页对应具体的扫描图片或 PDF。我为此单独设计了一张卷宗目录表用 parent_id 做树形结构前端通过一次接口拿到整棵目录树。在线预览方面我没有直接返回原文件而是在审批通过后调用一个转换服务把卷宗 PDF 加工成带水印的预览版本水印上带上申请人姓名和申请单编号。这个模块可以用像 pdf-lib 这样的库去实现核心思路是在每一页上叠加半透明的文字。原文件一律不允许在线预览只能走线下流程或导出申请授权后的水印副本这是整个系统最底线的一条规则。3.4 申请列表的多条件筛选与分页列表页看起来简单实际上属于这类系统的门面审核员一天到晚就对着列表工作。我在列表接口里一次性支持了案件编号模糊搜索、申请状态筛选、申请时间段筛选以及分页排序const sql SELECT a.*, c.case_no, c.case_name, u.real_name FROM application a LEFT JOIN case_info c ON a.case_id c.id LEFT JOIN user u ON a.user_id u.id WHERE 11 ${caseNo ? AND c.case_no LIKE ? : } ${status ? AND a.status ? : } ${startDate ? AND a.created_at ? : } ${endDate ? AND a.created_at ? : } ORDER BY a.updated_at DESC LIMIT ?, ? ;多条件筛选这类代码写起来不复杂但字段的可选范围和组合关系必须在接口文档里写清楚否则前端对接的时候会一个参数一个参数来问。这里我用了一个小技巧所有筛选条件都是可选的SQL 用WHERE 11加字符串拼接的方式虽然看起来有点粗暴但配合参数绑定后实际上非常安全也不容易出现查询条件丢三落四的情况。4. 页面与交互实操管理后台和小程序端的关键实现4.1 管理后台的页面结构设计管理后台基于 Vue 2 Element UI整体采用经典的后台布局左侧菜单栏 右侧内容区。菜单项包括工作台、案件管理、卷宗管理、阅卷申请管理、用户管理和系统日志。阅卷申请管理页面是整个系统的核心页面我拆成四个 Tab待办列表只显示状态为待初审和待复核的申请单对应审核员日常处理的工作台。已办列表显示当前审核员处理过的历史申请单。全部申请按各种筛选条件查看所有申请单供管理员做统计和审计。异常单列表专门用来展示流转异常、超时未处理、反复退回的记录。列表操作列放了“详情”“审批”“处理记录”三个入口。“审批”按钮打开对话框里面有通过、驳回、退回补正三个动作每个动作必须填写意见才能提交。这个“必须填写意见”的规则是我在测试阶段被档案科同事吐槽之后加上的否则大量审批连个理由都没有出了问题根本没法追溯。4.2 关键页面组件的拆解方式在组件化拆分上我没有做得很细核心逻辑是把“申请详情”拆成了一个独立组件列表页和详情页共用。这个申请详情组件内部包含了申请人的信息、案件基本信息、卷宗选择列表、附件材料预览、审批时间线和审批表单几个子模块。时间线是用 Element UI 的el-steps组件实现的但要注意el-steps默认是横向布局当审批环节多的时候容易挤压我加了一个简单的竖向样式覆盖。审批表单框使用el-radio-group加el-input组合通过选中不同动作动态切换颜色提示比如“通过”显示成绿色提示文案“退回补正”显示成橙色提示文案让审核员一眼就能看出当前操作的语义。4.3 小程序端的核心页面与交互小程序端我做了三个主要页面案件列表页、申请发起页、申请进度页。案件列表页展示当前用户有权限查看的案件列表数据来自后端接口。这里最需要留意的是用户只能看到自己关联的案件所以后端在返回数据前必须根据用户 ID 做关联查询过滤不能把所有案件都返回让前端筛选过滤。这是一个新手经常犯的逻辑错误会造成数据越权暴露。申请发起页是整个小程序端交互最繁琐的地方因为它包含案件选择、卷宗多选、申请事由填写、材料上传四个功能。小程序里的文件上传用的是wx.uploadFile与 Web 端的表单提交完全不一样要注意它必须用 FormData 的方式将 token 一并传过去否则后端鉴权直接给挡回来。申请进度页就是把 Web 端的审批时间线在小程序端复刻一遍。小程序原生没有步骤条组件我是自己写的一个横向块状时间线当前节点高亮历史节点打勾不可达节点置灰。虽然看起来简单但这里要处理动态数据渲染时setData的调取时机否则会出现页面已经渲染完成数据才填充进去的闪烁问题。4.4 接口联调与 Token 状态管理的统一方案前后端联调时最影响开发效率的问题就是 token 管理不统一。Web 管理后台我把 token 存在本地 localStorage 中每次请求在 axios 拦截器里带上小程序端我把 token 存在 storage 中在wx.request的封装函数里统一追加。两个端各写各的但逻辑保持完全一致// Web 端 axios 拦截器 axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); // 小程序端请求封装 function request(url, data, method GET) { const token wx.getStorageSync(token); return new Promise((resolve, reject) { wx.request({ url, method, data, header: { Authorization: Bearer token }, success: (res) { if (res.data.code 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); return; } resolve(res.data); }, fail: reject }); }); }统一将 401 视为登录失效前端不管 Web 还是小程序都直接跳回登录页。这套约定让我在联调阶段省下了大量重复沟通的时间也让两端的行为表现保持高度一致。5. 部署、权限与安全政务类项目最容易忽略的细节5.1 权限校验的前后端双重验证这类系统对权限的要求不是“功能隐藏”而是“数据隔离”。前端隐藏按钮、隐藏菜单只是用户体验层面的展示控制真正的权限控制必须发生在后端接口层。我在后端写了一个简单的权限校验中间件能够根据当前用户的角色以及接口配置的允许角色列表进行判断function requireRole(...roles) { return function (req, res, next) { const userRole req.user.role; if (roles.includes(userRole)) return next(); return res.status(403).json({ code: 403, message: 无权限执行该操作 }); }; } router.post(/api/application/approve, authMiddleware, requireRole(archivist, admin), approvalController.approveApplication);不要觉得接口层做权限校验麻烦这是整个系统安全的地基。前端做得再花哨接口没守住照样被掏空。我见过太多项目只在前端判断用户有没有某个按钮的可见权后端接口裸奔这几乎是政务类项目里最要命的漏洞。5.2 文件上传的坑类型校验、大小限制与存储路径文件上传模块也是高风险区域。原则上只允许上传两种类型PDF 和图片。但前端传什么类型完全不可信后端必须用文件的真实二进制头来判断。常见做法是使用第三方库解析文件签名而不是单纯信任file.originalname的后缀名。这种校验尤其不能省否则有人传一个伪装成图片的可执行文件到服务器上后果不堪设想。文件大小限制我分了两档申请材料的附件单文件限制 10 MB卷宗扫描件单文件限制 50 MB。超过大小的文件前端直接拦截并提示后端同时再限制一次。这样做可以避免大文件占用上传带宽导致整个后台卡死。顺带一提如果条件允许文件存储应该和数据库服务分离放到独立的云存储或者对象存储服务上这样即便文件访问量暴涨也不会拖垮应用服务本身。5.3 微信小程序登录态与 Web 端账号体系的打通小程序端不是独立的一套用户体系它是 Web 端账号体系的延伸。我的做法是小程序通过微信授权拿到身份标识后后端拿着这个标识去系统用户表里查找对应关系查到就直接下发 token查不到就提示当前微信未绑定系统账号请联系管理员。这个设计保证了用户不需要在小小的手机屏上再注册一套账号密码又让管理员可以从后台统一管理所有入口的用户。小程序端在登录时调wx.login拿到临时凭证然后传给后端后端调用微信接口换取用户身份信息完成匹配。这里最常遇到的坑是临时凭证有效期很短必须立即使用不能存到数据库里否则调试时会莫名其妙报错。我在第一次联调时就被这个问题坑了半个多小时排查到最后才发现是调试工具的缓存导致第二次请求用了已经失效的凭证。5.4 日志审计的设计细节日志审计可分为两块操作日志和登录日志。登录日志比较简单记录登录者的账号、登录时间、IP 和登录结果。操作日志则要覆盖所有关键业务动作尤其是审批动作的留痕。我在审批记录表里记录了审批人、审批意见、操作时间、申请单号再加上操作时的 IP 字段这样即便将来产生纠纷也有一套完整的追责链路可以追溯。日志记录本身用中间件做能减少业务代码里的侵入。但是要特别注意的是日志表不能随业务主表一起做物理删除只允许逻辑删除。这类系统的审计数据是只增不删的相关合规要求本质上盯的就是完整性和不可篡改性。6. 上线部署与后续扩展建议6.1 部署方案与环境区分部署这块没有用太复杂的方案因为项目规模不大我用的是云服务器 Nginx 反向代理 PM2 进程守护的标准流程。前端的构建产物直接让 Nginx 托管后端 Node.js 服务由 PM2 拉起MySQL 单独使用一台数据库实例或者云数据库避免把数据库和应用服务器挤在一台低配机器上。环境区分我做了三套开发环境、测试环境、生产环境。前端通过环境变量区分接口地址后端通过环境变量区分数据库连接配置。最容易出状况的是前端开发时拿生产环境接口联调这会导致线上数据被测试数据污染。我的建议是开发环境一律指向测试接口只有发版前才能动生产接口并且要有一套给测试人员专用的数据清理脚本每次测试完可以一键恢复干净的初始数据。6.2 上线后最容易遇到的三类问题上线后我遇到过几类比较典型的问题在这里列一下一是附件路径写死。开发时我直接用了相对路径存储上线后前段文件在 CDN 域名下后端文件在服务器本地结果预览全部裂开。解决方法是配置一个公共的文件访问基地址后端返回附件 URL 时永远拼接这个基地址而不是直接返回相对路径。二是时间时区问题。MySQL 默认使用系统时区而前端 JavaScript 默认使用浏览器本地时区如果服务器设置了 UTC 时间前端显示的时间就会差 8 个小时。这个问题排查起来非常隐蔽因为数据和接口返回都在变但总是感觉时间对不上。建议在数据库连接串里显式设置时区并在后端统一返回时间戳由前端做格式化。三是小程序的合法域名配置。开发工具里可以勾选“不校验合法域名”一上线就全部失效。必须在小程序管理后台把 HTTPS 的接口域名加到白名单里且该域名必须已经备案。6.3 下一步可以做的扩展方向系统跑通以后有几个方向可以继续扩展。一是对接电子签章服务让申请人、审核人都在线签字彻底实现无纸化审批。二是接入暗水印技术在卷宗预览页面嵌入肉眼不可见但可提取追踪的数字水印一旦有人截图外发可以从泄露的图片中追溯到具体用户。三是引入消息推送服务审批状态变化时主动推送给申请人的微信。四是把案件和卷宗的同步任务做成定时同步而不是依赖人工导入减少数据不一致的概率。这些扩展方向取决于实际业务需求和资源投入但即便都不做当前的系统已经能完整覆盖“在线申请 多级审核 卷宗查阅权限管理”这个核心闭环在日常使用层面是完全够用的。最后说一点我自己的体会这类项目做完以后我最大的感受是技术本身并不是难点难点在于把业务边界想清楚宁可多花时间在流程梳理和权限设计上也不要急着写代码。阅卷申请系统本质上做的是“把不合适的申请挡在门外把合规的申请用最高效的方式放行”这句话听起来像业务口号但它确实决定了系统里每一张表、每一个接口、每一处权限校验的最终走向。如果你手头刚好也在做类似的后台管理系统加小程序项目的组合希望这篇内容能帮你少走一些弯路。