Files
klp-oa/klp-ui/src/views/wms/post/flow.vue

335 lines
11 KiB
Vue
Raw Normal View History

<template>
<div class="app-container flow-page">
<div class="flow-tabs">
<span
v-for="tab in tabs"
:key="tab.key"
class="flow-tab-item"
:class="{ active: activeTab === tab.key }"
@click="switchTab(tab.key)"
>
<i :class="tab.icon"></i>
{{ tab.label }}
</span>
</div>
<div v-loading="loading" class="flow-content">
<div ref="diagram" class="flow-diagram" v-html="currentSvg" @click="onNodeClick"></div>
</div>
</div>
</template>
<script>
import { renderMermaidSVG, THEMES } from 'beautiful-mermaid'
const theme = {
...THEMES['zinc-light'],
padding: 24,
nodeSpacing: 28,
layerSpacing: 44,
font: 'Inter, "Microsoft YaHei", sans-serif',
}
const diagrams = {
afterSales: `
graph TD
A["<b>创建售后单</b><br/>填写基本信息<br/>选择需售后处理的钢卷"]:::step
A --> B["<b>多部门并行处理</b>"]:::fork
B --> C["<b>生产部</b><br/>出具处理意见"]:::dept1
B --> D["<b>质量部</b><br/>出具处理意见"]:::dept2
B --> E["<b>销售部</b><br/>出具处理意见"]:::dept3
C --> F{"三个部门<br/>全部提交?"}:::decision
D --> F
E --> F
F -->|已全部提交| G["<b>售后负责人</b><br/>汇总各部门意见<br/>形成最终处理方案<br/>指定执行部门"]:::approve
G --> H["<b>部门执行</b><br/>执行处理方案"]:::execute
H --> I["<b>返回执行结果</b><br/>提交执行报告"]:::result
I --> J(["<b>售后单封存</b><br/>流程结束"]):::end
classDef step fill:#409eff,stroke:#337ecc,color:#fff,stroke-width:2px
classDef fork fill:#f0f5ff,stroke:#409eff,color:#303133,stroke-width:2px
classDef dept1 fill:#e6fffa,stroke:#00b4a0,color:#303133,stroke-width:2px
classDef dept2 fill:#fff7e6,stroke:#fa8c16,color:#303133,stroke-width:2px
classDef dept3 fill:#fff0f6,stroke:#eb2f96,color:#303133,stroke-width:2px
classDef decision fill:#f9f0ff,stroke:#722ed1,color:#303133,stroke-width:2px
classDef approve fill:#e6f7ff,stroke:#1890ff,color:#303133,stroke-width:2px
classDef execute fill:#fffbe6,stroke:#fadb14,color:#303133,stroke-width:2px
classDef result fill:#f6ffed,stroke:#52c41a,color:#303133,stroke-width:2px
classDef end fill:#dcf7e8,stroke:#52c41a,color:#303133,stroke-width:2px,rx:10,ry:10
linkStyle default stroke:#bfbfbf,stroke-width:2px
`,
steelFullChain: `
graph TD
A["<b>销售部创建合同</b><br/>录入产品所需内容<br/>规格/数量/技术标准"]:::s1
A --> B["<b>原料卷到货</b>"]:::s2
B --> C["<b>入库验收</b>"]:::s2
C --> D["<b>登记原料库存</b>"]:::s2
D --> E["<b>成品钢卷加工</b>"]:::s3
E --> F["加工前质检"]:::s3qc
E --> G["加工中质检"]:::s3qc
E --> H["加工完工质检"]:::s3qc
F --> I["<b>登记质量缺陷</b><br/>各工序均可录入"]:::s3
G --> I
H --> I
I --> J["<b>质量等级判定</b><br/>依据质检标准"]:::s3
J --> K["<b>单卷档案</b><br/>缺陷记录与等级<br/>全程绑定留存"]:::s3
K --> L["<b>编排发货计划</b><br/>基于合同信息"]:::s4
L --> M["<b>生成发货单</b>"]:::s4
M --> N{"<b>发货质量校验</b><br/>缺陷/等级检查"}:::dec
N -->|达标| O["<b>出库发货</b>"]:::s4
N -->|不达标| P["<b>禁止出库</b>"]:::s5
K --> Q["<b>钢卷库存管理</b>"]:::s4
Q --> R["<b>逻辑库</b><br/>按功能/用途分类"]:::s4
Q --> S["<b>物理库</b><br/>实际存放场地"]:::s4
R --> T["<b>跨库/跨区调拨</b>"]:::s4
S --> T
T --> U["<b>实时更新库存数据</b>"]:::s4
E --> V{"<b>生产过程<br/>数据异常检测</b>"}:::dec
V -->|异常| W["<b>自动触发告警</b><br/>推送异常信息至<br/>业务人员核查处置"]:::s5
K --> X["<b>生产数据报表统计</b><br/>原料/加工/质检<br/>库存/发货全周期汇总"]:::s4
classDef s1 fill:#409eff,stroke:#337ecc,color:#fff,stroke-width:2px
classDef s2 fill:#e6fffa,stroke:#13c2c2,color:#303133,stroke-width:2px
classDef s3 fill:#fff7e6,stroke:#fa8c16,color:#303133,stroke-width:2px
classDef s3qc fill:#fffbe6,stroke:#fadb14,color:#303133,stroke-width:2px
classDef s4 fill:#f0f5ff,stroke:#597ef7,color:#303133,stroke-width:2px
classDef s5 fill:#fff1f0,stroke:#f5222d,color:#303133,stroke-width:2px
classDef dec fill:#f9f0ff,stroke:#722ed1,color:#303133,stroke-width:2px
linkStyle default stroke:#bfbfbf,stroke-width:2px
`,
inventoryCheck: `
graph TD
A["<b>创建盘库单</b><br/>填写盘库单基本信息<br/>添加盘库计划"]:::c1
A --> B["<b>盘库计划</b><br/>设定起止时间<br/>选择库区类型"]:::c2
B --> C{"<b>库区类型?</b>"}:::cdec
C -->|逻辑库| D["<b>获取库存快照</b><br/>记录当前时间节点<br/>库存情况"]:::c3
C -->|物理库| E["<b>获取库存快照</b><br/>记录当前时间节点<br/>库存情况"]:::c3
E --> F["<b>记录吞吐记录</b><br/>额外记录物理库<br/>出入库流水明细"]:::c4
D --> G["<b>人工实地盘库</b><br/>按盘库计划执行<br/>录入实际库存数据"]:::c5
F --> G
G --> H["<b>系统自动对照</b><br/>快照库存 vs 实际库存<br/>逐项比对查找差异"]:::c6
H --> I["<b>盘亏明细</b><br/>系统有 实际无<br/>库存缺失项"]:::loss
H --> J["<b>盘盈明细</b><br/>实际有 系统无<br/>库存多出项"]:::gain
H --> K["<b>明细差异</b><br/>数据不一致<br/>数量/规格偏差"]:::diff
I --> L["<b>生成盘库差异报告</b><br/>汇总盘亏/盘盈/差异<br/>存储差异记录"]:::c7
J --> L
K --> L
L --> M(["<b>盘库单封存</b><br/>流程结束"]):::cend
classDef c1 fill:#409eff,stroke:#337ecc,color:#fff,stroke-width:2px
classDef c2 fill:#e6fffa,stroke:#13c2c2,color:#303133,stroke-width:2px
classDef cdec fill:#f9f0ff,stroke:#722ed1,color:#303133,stroke-width:2px
classDef c3 fill:#f0f5ff,stroke:#597ef7,color:#303133,stroke-width:2px
classDef c4 fill:#fff7e6,stroke:#fa8c16,color:#303133,stroke-width:2px
classDef c5 fill:#e6f7ff,stroke:#1890ff,color:#303133,stroke-width:2px
classDef c6 fill:#fffbe6,stroke:#fadb14,color:#303133,stroke-width:2px
classDef loss fill:#fff1f0,stroke:#f5222d,color:#303133,stroke-width:2px
classDef gain fill:#f6ffed,stroke:#52c41a,color:#303133,stroke-width:2px
classDef diff fill:#fff0f6,stroke:#eb2f96,color:#303133,stroke-width:2px
classDef c7 fill:#f0f5ff,stroke:#597ef7,color:#303133,stroke-width:2px
classDef cend fill:#dcf7e8,stroke:#52c41a,color:#303133,stroke-width:2px,rx:10,ry:10
linkStyle default stroke:#bfbfbf,stroke-width:2px
`,
productionSchedule: `
stateDiagram-v2
[*] --> 创建排产单: 填写基本信息
创建排产单 --> 关联合同获取需求: 选择合同
关联合同获取需求 --> 选择需求合并明细: 选取需求<br/>合并相同条目
选择需求合并明细 --> 提交审批
提交审批 --> 审批驳回: 审批不通过
提交审批 --> 审批通过: 审批通过
审批驳回 --> 选择需求合并明细: 退回修改后重新提交
审批通过 --> 车间接收执行单: 转为执行单推送车间
车间接收执行单 --> 执行生产: 车间接收
车间接收执行单 --> 审批驳回: 车间打回拒绝
执行生产 --> [*]: 排产完结
`,
equipmentRepair: `
stateDiagram-v2
[*] --> 创建维修计划: 点选异常巡检记录<br/>绑定记录与异常设备
创建维修计划 --> 审批维修计划
审批维修计划 --> 审批驳回: 审批不通过
审批维修计划 --> 审批通过: 审批通过
审批驳回 --> 创建维修计划: 退回修改后重新提交
审批通过 --> 逐设备维修记录: 逐一执行设备维修<br/>记录维修过程与结果
逐设备维修记录 --> 逐设备维修记录: 存在未维修设备
逐设备维修记录 --> [*]: 全部设备维修完成<br/>流程结束
`
}
export default {
name: 'FlowChart',
data() {
return {
loading: false,
activeTab: 'steelFullChain',
tabs: [
{ key: 'steelFullChain', label: '生产全链路流程', icon: 'el-icon-s-operation' },
{ key: 'afterSales', label: '售后处理流程', icon: 'el-icon-s-claim' },
{ key: 'inventoryCheck', label: '盘库流程', icon: 'el-icon-s-check' },
{ key: 'productionSchedule', label: '排产流程', icon: 'el-icon-s-order' },
{ key: 'equipmentRepair', label: '设备维修流程', icon: 'el-icon-s-tools' },
],
svgCache: {},
selectedNode: null,
}
},
watch: {
currentSvg() {
this.$nextTick(() => {
this.bindNodeEvents()
})
},
},
computed: {
currentSvg() {
const code = diagrams[this.activeTab]
if (!code) return ''
if (!this.svgCache[this.activeTab]) {
try {
this.svgCache[this.activeTab] = renderMermaidSVG(code, theme)
} catch (e) {
console.error('Mermaid render error:', e)
return '<p style="color:#f5222d;text-align:center;">流程图渲染失败: ' + e.message + '</p>'
}
}
return this.svgCache[this.activeTab]
},
},
methods: {
switchTab(key) {
this.activeTab = key
this.selectedNode = null
},
bindNodeEvents() {
const el = this.$refs.diagram
if (!el) return
const svg = el.querySelector('svg')
if (!svg) return
const groups = svg.querySelectorAll('g:not([class*="edge"])')
groups.forEach(g => {
const hasRect = g.querySelector('rect, path, ellipse, polygon')
const text = g.querySelector('text, span')
if (hasRect && text) {
g.style.cursor = 'pointer'
}
})
},
onNodeClick(e) {
let target = e.target
while (target && target !== e.currentTarget) {
if (target.tagName === 'g') {
const textEl = target.querySelector('text, span')
if (textEl) {
const label = textEl.textContent.replace(/\s+/g, ' ').trim()
if (label) {
this.selectedNode = label
this.$message({ message: label, type: 'info', duration: 1500 })
return
}
}
}
target = target.parentElement
}
},
},
}
</script>
<style scoped>
.flow-page {
padding: 0;
}
.flow-tabs {
display: flex;
gap: 4px;
padding: 8px 12px;
background: #fafafa;
border-bottom: 1px solid #e8e8e8;
}
.flow-tab-item {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 16px;
font-size: 13px;
color: #606266;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
white-space: nowrap;
user-select: none;
}
.flow-tab-item:hover {
color: #409eff;
background: rgba(64, 158, 255, 0.06);
}
.flow-tab-item.active {
color: #409eff;
background: #e6f0fd;
}
.flow-tab-item i {
font-size: 13px;
}
.flow-content {
min-height: 480px;
background: #fff;
overflow: auto;
}
.flow-diagram {
display: flex;
justify-content: center;
align-items: center;
padding: 16px;
}
.flow-diagram :deep(svg) {
max-width: 100%;
height: auto;
}
.flow-diagram :deep(svg g) {
transition: opacity 0.15s ease;
}
.flow-diagram :deep(svg g:hover) {
opacity: 0.8;
}
</style>