feat(web): add Vue Flow editor and flow trace viewer

This commit is contained in:
2026-07-07 15:39:14 +08:00
parent 6921f0d3e4
commit 2180fc4354
2 changed files with 129 additions and 36 deletions

View File

@@ -1,5 +1,56 @@
<template>
<div>
<h2>流程追踪</h2>
<el-form inline>
<el-form-item label="事件ID">
<el-input v-model="eventId" placeholder="请输入 eventId" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">查询</el-button>
</el-form-item>
</el-form>
<el-timeline v-if="traces.length > 0">
<el-timeline-item
v-for="trace in traces"
:key="trace.id"
:type="trace.status === 'success' ? 'success' : 'danger'"
>
<p>节点类型: {{ trace.nodeType }}</p>
<p>状态: {{ trace.status }}</p>
<p>耗时: {{ trace.durationMs }} ms</p>
<p v-if="trace.errorMsg" class="error-msg">错误: {{ trace.errorMsg }}</p>
</el-timeline-item>
</el-timeline>
<el-empty v-else description="暂无追踪数据" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getFlowTraces } from '@/api/inbound'
const eventId = ref('')
const traces = ref<any[]>([])
async function handleQuery() {
if (!eventId.value) {
ElMessage.warning('请输入事件ID')
return
}
try {
const res: any = await getFlowTraces(eventId.value)
traces.value = res.data || []
} catch (e: any) {
ElMessage.error(e.message)
}
}
</script>
<style scoped>
.error-msg {
color: #f56c6c;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div>
<div class="inbound-flow">
<el-form :model="form" label-width="100px">
<el-form-item label="流程编码">
<el-input v-model="form.flowCode" placeholder="flow_code" />
@@ -7,63 +7,105 @@
<el-form-item label="流程名称">
<el-input v-model="form.flowName" placeholder="流程名称" />
</el-form-item>
<el-form-item label="节点配置">
<el-input v-model="nodesJson" type="textarea" :rows="8" placeholder='[{"nodeType":"receive"},{"nodeType":"intent","intent":"rescue_request"},{"nodeType":"webhook","webhookUrl":"http://..."}]' />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleCreate">保存流程</el-button>
</el-form-item>
</el-form>
<el-divider />
<el-table :data="flows" border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="flowCode" label="流程编码" />
<el-table-column prop="flowName" label="名称" />
<el-table-column prop="status" label="状态" />
</el-table>
<div class="flow-canvas">
<VueFlow v-model="elements" fit-view-on-init>
<Background pattern-color="#aaa" :gap="16" />
<Controls />
</VueFlow>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, computed, onMounted } from 'vue'
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { createFlowDefinition, listFlowDefinitions } from '@/api/flow'
import { VueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import { createFlowDefinition } from '@/api/flow'
import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css'
const form = reactive({
flowCode: '',
flowName: '',
nodes: [] as any[],
})
const nodesJson = computed({
get: () => JSON.stringify(form.nodes, null, 2),
set: (val) => {
try { form.nodes = JSON.parse(val) } catch {}
const elements = ref<any[]>([
{
id: '1',
type: 'input',
label: '接收消息',
position: { x: 250, y: 5 },
data: { nodeType: 'receive' },
},
})
const flows = ref<any[]>([])
async function loadFlows() {
try {
const res: any = await listFlowDefinitions()
flows.value = res.data || []
} catch (e: any) {
ElMessage.error(e.message)
}
}
{
id: '2',
label: '意图识别',
position: { x: 250, y: 120 },
data: { nodeType: 'intent', intent: 'rescue_request' },
},
{
id: '3',
label: 'Webhook 推送',
position: { x: 250, y: 240 },
data: { nodeType: 'webhook', webhookUrl: '' },
},
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' },
])
async function handleCreate() {
try {
const res: any = await createFlowDefinition(form)
flows.value.unshift(res.data)
const nodes = elements.value
.filter((el) => !('source' in el))
.map((el) => {
const { nodeType, ...config } = el.data || {}
return {
nodeType,
config,
position: el.position,
}
})
const edges = elements.value
.filter((el) => 'source' in el)
.map((el) => ({
source: el.source,
target: el.target,
}))
const res: any = await createFlowDefinition({
flowCode: form.flowCode,
flowName: form.flowName,
flowType: 'inbound',
definitionJson: { nodes, edges },
})
ElMessage.success('保存成功')
console.log(res)
} catch (e: any) {
ElMessage.error(e.message)
}
}
onMounted(loadFlows)
</script>
<style scoped>
.inbound-flow {
display: flex;
flex-direction: column;
height: calc(100vh - 120px);
}
.flow-canvas {
flex: 1;
border: 1px solid #e4e7ed;
border-radius: 4px;
min-height: 400px;
}
</style>