资讯动态

Vue + Pinia搭建SOP作业指导系统:从模型设计到工程化实践

发布时间:2026/9/16 15:36:10 来源:尧图企业网站定制
简介基于Vue框架的SOP作业指导系统设计源码面向需要搭建标准化作业管理平台的前端开发者或企业技术团队覆盖后台日志管理、装配指导步骤上传及终端步骤展示、安装指导等核心场景。压缩包总计包含992个文件以240个Vue组件、230个JavaScript脚本、168个BCMap地图文件、166个SVG矢量图形和110个属性文件为主并附有项目配置文件与构建脚本可支撑服务端渲染或静态站点生成等部署方式。其中BCMap地图文件用于地理信息展示SVG矢量图形便于绘制直观作业流程图标Properties属性文件则保存界面与业务参数配置整体约为19.71MB。目前已有108人学习适合希望快速上手Vue工程化结构、深入理解SOP系统模块划分与前后端协作逻辑的开发者资源目录完整可直接用于二次开发、代码研读或功能扩展有助于缩短企业级作业指导系统的设计周期。1. 为什么 SOP 作业指导系统要选 Vue 框架车间里的工艺卡、维修手册、实验规范过去靠打印张贴现在要变成手机和平板上的电子作业指导书。SOPStandard Operation Procedure系统要解决的从来不是「有没有文件」而是「作业现场能不能按当前有效版本一步步执行到位」。需求拆开无非是三件事按工序展示步骤、附图和视频、记录每一步的确认结果让工艺员能改、能审、能发新版本让现场的人永远只看到「已发布」的那一版。Vue 框架被这类系统频繁选中的原因很直接步骤天然是数组Vue 的列表渲染和组件化正好贴合后台管理需要表单、表格、上传、权限按钮Element Plus / Ant Design Vue 这些都现成与后端 REST API、WebSocket 的对接在 Vue 生态里几乎没有心智负担。但这套系统真正值钱的部分不是页面而是数据模型、版本状态机和工程目录的组织方式。这篇按「模型设计 → 阅读器实现 → 编辑与权限 → 工程化优化」的顺序把一套可直接落地的 Vue 3 Pinia 方案讲清楚。2. SOP 作业指导系统的核心设计步骤拆解与状态机2.1 作业步骤的数据模型主表、步骤表与附件拆分SOP 文档和普通文章最大的区别是结构一篇 SOP 由多个有序步骤组成每个步骤可能带参数、工具、图片、视频和一个检查点。设计数据库时不能把正文存成一个长文本否则审核、版本对比、步骤确认全都做不了。常见的做法是拆三张表或三个集合表名字段要点说明sop_documentid, doc_no, title, product_code, version, status, created_by, published_atSOP 主表一条记录就是一份有效文件sop_stepid, sop_id, step_no, title, content, tool_type, tool_code, param_json, media_url, check_type步骤表step_no 控制顺序param_json 存结构化参数sop_attachmentid, sop_id, step_id, file_type, file_url, mime_type, duration_sec附件表区分 image / video / pdf可与步骤或整篇绑定param_json是很容易被忽略但很实用的字段。SOP 里经常出现「扭矩 25 N·m」「温度 180℃」这类参数如果写在 content 里后续做防错校验时就要解析文本存成 JSON 才能在前端单独渲染高亮并在执行确认时做范围判断。-- 按版本号取当前有效 SOP 的步骤 SELECT s.step_no, s.title, s.content, s.param_json, s.media_url FROM sop_step s JOIN sop_document d ON d.id s.sop_id WHERE d.doc_no SOP-2024-018 AND d.status published AND d.version (SELECT MAX(version) FROM sop_document WHERE doc_no SOP-2024-018) ORDER BY s.step_no;这段 SQL 的关键在于AND d.status published和子查询取最大版本。前者保证现场端只看到已发布内容后者保证同一文档编号下只有一个「当前版本」。历史版本不是删掉而是留在表里用于追溯这正是「基于源码实现 SOP 系统设计」时最容易做错的地方——直接覆盖旧版本。2.2 审批与发布的状态机草稿、审核、发布、作废SOP 的生命周期不能用「编辑 / 保存」两个状态糊弄。工艺文件一旦出错产线照做就可能出批量事故所以状态流转必须明确而且能审计。一个够用的状态机如下当前状态触发动作目标状态允许角色draft提交审核review工艺员review审核通过published审核主管review审核驳回draft审核主管published发起修订revising工艺员revising提交审核review工艺员published作废obsolete审核主管这里有个设计细节revising状态不要直接改原文档。常见做法是「复制一份当前版本到新版本号然后编辑新版本」原版本保持 published 直到新版通过审核。这样线上的作业端永远有可用版本不会因为修订而断供。2.3 接口约定一个 SOP 详情接口返回什么前端要展示一个步骤制导页面最好的接口形态不是让前端调 5 个接口自行拼接而是一次返回聚合结构GET /api/sop/detail/SOP-2024-018{ docNo: SOP-2024-018, title: CNC 主轴油脂加注作业, version: 3, status: published, author: 张工, publishedAt: 2025-06-12 10:00:00, steps: [ { stepNo: 1, title: 清洁注油口, content: 使用无尘布清洁主轴注油口周边, mediaUrl: /media/sop/2024/018/step1.jpg, checkType: confirm }, { stepNo: 2, title: 注油量确认, content: 使用定量注油枪注入油脂, paramJson: { target: 30, unit: ml, min: 28, max: 32 }, mediaUrl: /media/sop/2024/018/step2.mp4, checkType: param } ] }推荐一次性返回整个步骤列表而不是「先查文档再逐个查步骤」。原因是现场作业场景网络通常不稳定聚合接口可以减少一次页面加载的并发请求数步骤数量一般几十个以内单接口返回完全不会造成响应体积问题。前端拿到这个结构后剩下的事就交给了 Vue 组件。3. 用 Vue 实现 SOP 作业指导阅读器步骤进度、视频与检查点3.1 步骤进度组件从数组渲染到状态记忆阅读器是现场人员打开最频繁的页面核心诉求是「快速定位到我现在做到哪一步」。Vue 里实现一个步骤进度组件并不复杂关键是当前步骤的索引要持久化退出再进入不丢。template div classsop-reader el-steps :activecurrentStep simple el-step v-forstep in steps :keystep.stepNo :titlestep.title / /el-steps div classstep-content h3{{ current.title }}/h3 p{{ current.content }}/p video v-ifisM3u8(current.mediaUrl) refstepVideo controls classstep-media /video img v-else-ifisImage(current.mediaUrl) :srccurrent.mediaUrl classstep-media / el-button typeprimary clickconfirmStep :disabledconfirmedSet.has(current.stepNo) {{ confirmedSet.has(current.stepNo) ? 已完成 : 确认完成 }} /el-button /div /div /template这里用currentStep记录当前步骤索引confirmedSet记录已完成步骤号。确认按钮基于 Set 做幂等重复点击不会重复提交。这个组件本身不负责数据获取只负责渲染数据从 Pinia store 或父组件传入职责清晰后续改造才容易。配套的脚本逻辑如下import { computed, ref, onMounted, onBeforeUnmount } from vue import { useSopStore } from /stores/sop const props defineProps({ sopId: { type: String, required: true } }) const store useSopStore() const currentStep ref(0) const confirmedSet ref(new Set()) const steps computed(() store.currentSop.steps || []) const current computed(() steps.value[currentStep.value] || {}) const isM3u8 (url ) url.endsWith(.m3u8) const isImage (url ) /\.(jpg|png|jpeg|gif|webp)$/i.test(url) const confirmStep async () { if (current.value.checkType param) { // 有参数校验时先校验输入范围再确认 const { min, max } current.value.paramJson if (inputValue.value min || inputValue.value max) { ElMessage.warning(参数必须在 ${min} ~ ${max} 范围内) return } } confirmedSet.value.add(current.value.stepNo) } onMounted(() { store.fetchSopDetail(props.sopId) // 从 localStorage 恢复上次进度避免现场退出后从头翻 const saved localStorage.getItem(sop-progress-${props.sopId}) if (saved) currentStep.value JSON.parse(saved) }) onBeforeUnmount(() { localStorage.setItem( sop-progress-${props.sopId}, JSON.stringify(currentStep.value) ) })进度恢复是现场场景的刚需作业指导书通常有几十步做到一半被叫走去处理异常回来后重新选步骤翻页体验很差。用localStorage按sopId维度保存currentStep就足够不需要为此引入额外的状态同步方案。3.2 在 Vue 里播放 M3U8 视频的可靠写法SOP 视频多数来自现场手机拍摄或监控系统转存很多情况拿到的是 M3U8 切片流而不是 MP4 文件。原生video标签不认 M3U8需要配合 Hls.js 做转播。import Hls from hls.js const videoRef ref(null) function playM3u8(url) { if (!videoRef.value) return if (videoRef.value.canPlayType(application/vnd.apple.mpegurl)) { // 方案一Safari 原生支持 m3u8直接赋值即可 videoRef.value.src url return } if (Hls.isSupported()) { // 方案二标准浏览器用 Hls.js 拉流并绑定 const hls new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60, enableWorker: true }) hls.loadSource(url) hls.attachMedia(videoRef.value) return } ElMessage.error(当前浏览器不支持 m3u8 播放) }两个分支分别处理 Safari 和 Chrome/Edge 系浏览器。参数推荐maxBufferLength设为 30 秒避免车间弱网场景下预加载太多内存enableWorker: true让 TS 切片解析跑在 Web Worker 里界面不卡顿。注意组件卸载时要调用hls.destroy()否则视频流会继续占用带宽。提示Hls.js 版本差异较大建议锁定安装一个稳定版本并统一封装在useHlsPlayer.ts里不要在业务组件里散落创建 Hls 实例。3.3 检查点确认与操作日志步骤不只「看过」要「做完」SOP 系统区别于普通文档阅读器最大差异是「执行确认」。包装行业的称重检查、设备维护的断电挂牌这些动作不能只翻页不执行因此每个步骤可配置检查方式checkType前端行为提交内容confirm点击「确认完成」按钮步骤号 时间戳param弹出参数输入框校验 min/max步骤号 实测值photo调用相机/相册上传照片步骤号 图片 URL 时间戳scan扫描二维码/条码步骤号 条码内容确认动作通过一次异步请求写入后端操作日志数据包括sopId、version、stepNo、operateBy、operateTime、result。字段里必须带version不然后续追溯时无法判断这步操作对应的是第几版 SOP。现场端如果存在弱网确认请求可以先进入本地的待上传队列网络恢复后由后端按时间先后补写常见的做法是维护一个pendingActions数组持久化到 IndexedDB。4. SOP 编辑器的版本提交与权限控制Pinia 状态管理与按钮级鉴权4.1 Pinia store让草稿、版本、步骤编辑各司其职SOP 编辑器比阅读器复杂因为要维护「正在编辑的步骤列表」「新增的附件清单」「是否已被他人抢先提交」这些状态分散在各组件里容易乱。用 Pinia 把数据集中管理是最稳妥的做法。import { defineStore } from pinia export const useSopEditorStore defineStore(sopEditor, { state: () ({ draft: { docNo: , title: , version: 0, steps: [], removedStepIds: [] }, submitting: false, lastSavedAt: null }), getters: { stepCount: (state) state.draft.steps.length, publishedVersion: (state) state.publishedMeta?.version || 0 }, actions: { loadDraft(docNo) { // 有草稿回到草稿没有则基于当前版本复制一份 }, addStep(step) { this.draft.steps.push({ ...step, stepNo: this.draft.steps.length 1 }) }, removeStep(stepNo) { this.draft.removedStepIds.push(stepNo) this.draft.steps this.draft.steps.filter(s s.stepNo ! stepNo) }, async submitForReview() { this.submitting true try { await api.post(/api/sop/review, { docNo: this.draft.docNo, steps: this.draft.steps, removedStepIds: this.draft.removedStepIds }) } finally { this.submitting false } } } })重点看removedStepIds这个字段。编辑时删除步骤如果不记录被删的原始步骤号提交审核时后端就无法判断「是删除了旧步骤还是漏传了步骤」。带上这个字段后端可以生成完整的变更对比审核人看到的是「删除了第 4 步新增了第 5 步」而不是只有最终结果。submitting标志位用于全局按钮 loading 状态防止工艺员重复点击提交生成两条审核任务。4.2 版本提交与审核流编辑、提交、通过、打回前端在版本流转中只做「发起动作」和「展示状态」真正的状态校验在后端。但前端的按钮显隐和路由守卫必须配合状态机避免用户点出不合法操作。template div v-ifdetail.status draft || detail.status revising el-button typeprimary clicksubmitToReview提交审核/el-button /div div v-else-ifdetail.status review el-button typesuccess clickapprove审核通过/el-button el-button typedanger clickreject驳回/el-button /div div v-else-ifdetail.status published el-button clickstartRevision发起修订/el-button /div /template对应的路由守卫要拦截「正在审核中」的文档被再次编辑router.beforeEach((to, from, next) { if (to.path.startsWith(/sop/edit)) { const status editorStore.draft.status if (status review) { ElMessage.warning(该文档正在审核中不能编辑) next(/sop/list) return } next() } })这里有一个很多项目会忽略的细节发起修订时应通过明文的startRevision接口把原版本状态改为revising并生成新版本号而不是让前端直接改status字段。前端能改自己的 Vuex/Pinia 状态但后端必须知道「新版本号从几开始递增」否则两个人同时发起修订就出错了。版本号的生成应当由数据库的事务与行锁保证UPDATE ... SET version version 1 WHERE id ? AND status published利用受影响行数判断是否被并发抢先。4.3 按钮级权限给操作工和工艺员渲染不同的界面同一个 Vue 前端后面往往有多类用户工艺员建 SOP、审核主管审 SOP、线长查看进度、操作工执行步骤。前端不能只靠「是否登录」控制要做到按钮级权限。一个轻量方案是通过自定义指令调用后端返回的权限码集合import { useAuthStore } from /stores/auth // 注册一个 v-perm 指令 app.directive(perm, { mounted(el, binding) { const auth useAuthStore() const required binding.value // 如 sop:review:approve if (!auth.permissionCodes.includes(required)) { el.parentNode?.removeChild(el) } } })模板里的用法el-button v-permsop:review:approve typesuccess clickapprove 审核通过 /el-button权限码的粒度建议配到「资源和动作」两级资源是sop、step、attachment、version动作是view、edit、submit、approve、obsolete。不要用admin/operator这种粗粒度角色去前端判断地板会经常变动最后要么开发改代码要么权限需求方妥协两边都难受。5. 源码落地时的目录规划与 3 个必调优化项5.1 路由懒加载与分包SOP 系统值得拆开的边界SOP 系统的代码天然分两块现场阅读器对性能敏感和管理端编辑器功能重但用户少。如果全部打进一个 bundle产线平板上的首屏资费会包含大量无用的表单组件代码。用路由懒加载做分割是性价比最高的优化const sopReader () import(/views/sop/SopReader.vue) const sopEditor () import(/views/sop/SopEditor.vue) const reviewList () import(/views/review/ReviewList.vue)配好后npm run build会按路由生成独立 chunk。需要关注的是 Webpack/Vite 可能会把体积大的异步组件再拆成共享 chunk可以在构建后检查dist/assets下的资源大小。如果发现 Hls.js 被同时打进了阅读器和编辑器两个 chunk把它在打包配置中单独拆出如果发现某个 chunk 超过 300KB进一步用manualChunks或 Vite 的rollupOptions细化。提示Vite 项目根目录的vite.config.js里build.chunkSizeWarningLimit默认是 500KB按 SOP 系统的规模建议调小到 300KB 并据此做拆分优化而不是靠调大限制回避问题。5.2 长步骤列表的虚拟滚动不要用 v-for 渲染上百步骤大多数 SOP 在 20 步以内但设备维修类的详细指导书可能上百步。此时页面一次性渲染 DOM 会明显卡顿。实现虚拟滚动不需要引入重型库Vue 自带的动态高度处理也可以完成轻量版本import { ref, computed } from vue const allSteps ref([]) const containerRef ref(null) const SCROLL_VIEWPORT 600 const ROW_HEIGHT 80 const visibleSteps computed(() { const scrollTop containerRef.value?.scrollTop || 0 const start Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - 4) const end Math.min( allSteps.value.length, Math.ceil((scrollTop SCROLL_VIEWPORT) / ROW_HEIGHT) 4 ) return allSteps.value.slice(start, end).map((step, i) ({ ...step, index: start i })) })虚拟滚动的核心是「渲染窗口内才有的 DOM」数据总量allSteps不变visibleSteps根据滚动位置计算 上下各多渲染 4 行作为缓冲避免快速滚动露白。每行高度固定时用ROW_HEIGHT就能算位置简洁且不依赖第三方。如果步骤内容高度不固定再考虑使用vue-virtual-scroller但固定高度场景先用自研方案更可控。5.3 部署环境中的 history 路由、跨域与弱网容忍Vue 项目的 browser 模式路由把 URL 放在 path 上发布到生产环境时需要在 Nginx 配 fallbacklocation / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend-svc:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }try_files ... /index.html这段是 history 路由不 404 的前提删掉后直接刷新/sop/detail/xxx就会白屏报错。/api/的proxy_pass同时解决了前后端域名不同的问题前端axios用相对路径访问即可。SOP 现场使用的设备经常是车间平板网络质量不如办公室。前端实现可以预留一个 Service Worker 或 PWA 缓存池把已发布 SOP 的核心 JSON 在首次加载后缓存允许用户在弱网时继续浏览已缓存的步骤内容确认记录进入待同步队列网络恢复后补偿提交。这是一个不错的交付亮点也回应了「这套系统能不能真正用在一线」这个本质问题。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价