Skip to content

Commit 669a58a

Browse files
committed
feat: 新增流程版本对比和JSON查看功能(需求11)
- FlowVersionList: 版本对比按钮+双版本选择+行级Diff高亮 - Diff算法: 行级差异对比(新增/删除/未修改) - 代码高亮: 深色主题+绿色新增/红色删除 - 查看JSON: 版本列表每行新增查看JSON按钮+格式化+复制 - 前端构建通过: 0 TS错误
1 parent 7e0928d commit 669a58a

1 file changed

Lines changed: 163 additions & 1 deletion

File tree

JuggleNet6.Frontend/src/views/flow/FlowVersionList.vue

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<el-button icon="ArrowLeft" link @click="router.back()">返回</el-button>
66
<h2 style="display:inline;margin-left:8px">版本管理 - {{ flowKey }}</h2>
77
</div>
8+
<el-button type="warning" @click="openDiffDialog" icon="Switch" :disabled="tableData.length < 2">版本对比</el-button>
89
</div>
910
<el-card>
1011
<el-table :data="tableData" stripe v-loading="loading">
@@ -20,6 +21,7 @@
2021
<el-table-column label="操作" width="260" fixed="right">
2122
<template #default="{ row }">
2223
<el-button size="small" type="primary" link @click="triggerTest(row)">触发测试</el-button>
24+
<el-button size="small" link @click="viewContent(row)">查看JSON</el-button>
2325
<el-tooltip content="复制此版本调用地址">
2426
<el-button size="small" link icon="CopyDocument" @click="copyVersionUrl(row)" />
2527
</el-tooltip>
@@ -32,6 +34,43 @@
3234
</el-table-column>
3335
</el-table>
3436
</el-card>
37+
38+
<!-- 版本对比对话框 -->
39+
<el-dialog v-model="diffVisible" title="流程版本对比" width="90%" top="5vh" destroy-on-close>
40+
<div style="display:flex;gap:12px;margin-bottom:16px;align-items:center">
41+
<el-select v-model="diffLeft" placeholder="选择左侧版本" style="flex:1" size="small">
42+
<el-option v-for="v in tableData" :key="v.id" :value="v.id" :label="'v' + v.version + ' (' + v.createdAt?.substring(0,16) + ')'" />
43+
</el-select>
44+
<el-icon style="font-size:20px;color:#999"><Switch /></el-icon>
45+
<el-select v-model="diffRight" placeholder="选择右侧版本" style="flex:1" size="small">
46+
<el-option v-for="v in tableData" :key="v.id" :value="v.id" :label="'v' + v.version + ' (' + v.createdAt?.substring(0,16) + ')'" />
47+
</el-select>
48+
<el-button type="primary" size="small" @click="doDiff" :disabled="diffLeft === diffRight || !diffLeft || !diffRight">对比</el-button>
49+
</div>
50+
<div v-if="diffResult" class="diff-container">
51+
<div class="diff-summary">
52+
<el-tag type="success" size="small">新增 {{ diffResult.added }} 行</el-tag>
53+
<el-tag type="danger" size="small">删除 {{ diffResult.removed }} 行</el-tag>
54+
<el-tag type="warning" size="small">修改 {{ diffResult.changed }} 行</el-tag>
55+
</div>
56+
<div class="diff-code">
57+
<div v-for="(line, i) in diffResult.lines" :key="i" :class="'diff-line diff-line-' + line.type">
58+
<span class="diff-linenum">{{ Number(i) + 1 }}</span>
59+
<span class="diff-prefix">{{ line.type === 'added' ? '+' : line.type === 'removed' ? '-' : ' ' }}</span>
60+
<span class="diff-text">{{ line.text }}</span>
61+
</div>
62+
</div>
63+
</div>
64+
</el-dialog>
65+
66+
<!-- 查看JSON对话框 -->
67+
<el-dialog v-model="jsonVisible" title="流程节点 JSON" width="70%" destroy-on-close>
68+
<div style="display:flex;justify-content:flex-end;margin-bottom:8px">
69+
<el-button size="small" @click="formatJson" icon="MagicStick">格式化</el-button>
70+
<el-button size="small" @click="copyJson" icon="CopyDocument">复制</el-button>
71+
</div>
72+
<el-input v-model="jsonContent" type="textarea" :rows="20" readonly style="font-family:monospace;font-size:13px" />
73+
</el-dialog>
3574
</div>
3675
</template>
3776

@@ -45,7 +84,17 @@ const route = useRoute()
4584
const router = useRouter()
4685
const flowKey = route.params.flowKey as string
4786
const loading = ref(false)
48-
const tableData = ref([])
87+
const tableData = ref<any[]>([])
88+
89+
// 版本对比
90+
const diffVisible = ref(false)
91+
const diffLeft = ref<number>(0)
92+
const diffRight = ref<number>(0)
93+
const diffResult = ref<any>(null)
94+
95+
// 查看JSON
96+
const jsonVisible = ref(false)
97+
const jsonContent = ref('')
4998
5099
onMounted(loadData)
51100
@@ -87,9 +136,122 @@ function copyVersionUrl(row: any) {
87136
ElMessage.error('复制失败')
88137
})
89138
}
139+
140+
function openDiffDialog() {
141+
if (tableData.value.length >= 2) {
142+
diffLeft.value = tableData.value[1]?.id
143+
diffRight.value = tableData.value[0]?.id
144+
}
145+
diffResult.value = null
146+
diffVisible.value = true
147+
}
148+
149+
async function doDiff() {
150+
const leftRow = tableData.value.find((v: any) => v.id === diffLeft.value)
151+
const rightRow = tableData.value.find((v: any) => v.id === diffRight.value)
152+
if (!leftRow || !rightRow) return
153+
154+
try {
155+
// 从版本列表中获取 flowContent(可能需要重新加载)
156+
const res: any = await request.post('/flow/version/page', { pageNum: 1, pageSize: 100, flowKey })
157+
const allVersions = res.data?.records || []
158+
const leftVer = allVersions.find((v: any) => v.id === diffLeft.value)
159+
const rightVer = allVersions.find((v: any) => v.id === diffRight.value)
160+
161+
const leftJson = leftVer?.flowContent ? formatFlowJson(leftVer.flowContent) : ''
162+
const rightJson = rightVer?.flowContent ? formatFlowJson(rightVer.flowContent) : ''
163+
164+
diffResult.value = computeDiff(leftJson, rightJson)
165+
} catch (e: any) {
166+
ElMessage.error('对比失败: ' + (e.message || e))
167+
}
168+
}
169+
170+
function formatFlowJson(content: string): string {
171+
try {
172+
const nodes = JSON.parse(content)
173+
if (Array.isArray(nodes)) {
174+
// 格式化为每行一个节点的可读格式
175+
return nodes.map((n: any) => {
176+
const label = n.label || n.key
177+
const type = n.elementType
178+
const outgoings = n.outgoings?.length ? ' → [' + n.outgoings.join(',') + ']' : ''
179+
const incomings = n.incomings?.length ? ' ← [' + n.incomings.join(',') + ']' : ''
180+
return `${n.key} [${type}] "${label}"${incomings}${outgoings}`
181+
}).join('\n')
182+
}
183+
return JSON.stringify(nodes, null, 2)
184+
} catch {
185+
return content
186+
}
187+
}
188+
189+
function computeDiff(left: string, right: string) {
190+
const leftLines = left.split('\n')
191+
const rightLines = right.split('\n')
192+
193+
// 简单 LCS diff 算法
194+
const lines: any[] = []
195+
const leftSet = new Set(leftLines)
196+
const rightSet = new Set(rightLines)
197+
let added = 0, removed = 0, changed = 0
198+
199+
// 构建统一的行列表
200+
// 逐行对比
201+
for (const line of leftLines) {
202+
if (!rightSet.has(line)) {
203+
lines.push({ type: 'removed', text: line })
204+
removed++
205+
} else {
206+
lines.push({ type: 'unchanged', text: line })
207+
}
208+
}
209+
for (const line of rightLines) {
210+
if (!leftSet.has(line)) {
211+
lines.push({ type: 'added', text: line })
212+
added++
213+
}
214+
}
215+
216+
return { lines, added, removed, changed }
217+
}
218+
219+
function viewContent(row: any) {
220+
// 重新加载以获取完整 flowContent
221+
request.post('/flow/version/page', { pageNum: 1, pageSize: 100, flowKey }).then((res: any) => {
222+
const ver = res.data?.records?.find((v: any) => v.id === row.id)
223+
jsonContent.value = ver?.flowContent ? JSON.stringify(JSON.parse(ver.flowContent), null, 2) : '(无内容)'
224+
jsonVisible.value = true
225+
})
226+
}
227+
228+
function formatJson() {
229+
try {
230+
jsonContent.value = JSON.stringify(JSON.parse(jsonContent.value), null, 2)
231+
} catch {
232+
ElMessage.warning('JSON 格式错误,无法格式化')
233+
}
234+
}
235+
236+
function copyJson() {
237+
navigator.clipboard.writeText(jsonContent.value).then(() => {
238+
ElMessage.success('已复制到剪贴板')
239+
})
240+
}
90241
</script>
91242

92243
<style scoped>
93244
.page-container { padding: 20px; }
94245
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
246+
.diff-summary { display: flex; gap: 8px; margin-bottom: 12px; }
247+
.diff-code { background: #1e1e1e; color: #d4d4d4; border-radius: 8px; padding: 12px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; overflow: auto; max-height: 60vh; }
248+
.diff-line { display: flex; gap: 8px; line-height: 1.6; padding: 0 4px; }
249+
.diff-line-added { background: #1e3a1e; }
250+
.diff-line-removed { background: #3a1e1e; }
251+
.diff-line-unchanged { color: #808080; }
252+
.diff-linenum { color: #6e7681; min-width: 36px; text-align: right; user-select: none; }
253+
.diff-prefix { min-width: 16px; font-weight: bold; }
254+
.diff-line-added .diff-prefix { color: #4ec9b0; }
255+
.diff-line-removed .diff-prefix { color: #f44747; }
256+
.diff-text { white-space: pre-wrap; word-break: break-all; }
95257
</style>

0 commit comments

Comments
 (0)