feat(web): add Vue Flow editor and flow trace viewer
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user