flowable-devops/frontend/apps/web-antd/src/store/workflow.ts
dengqichen d42166d2c0 提交
2025-10-13 16:25:13 +08:00

491 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 工作流 Store
* 管理工作流状态、节点类型、字段映射等
*/
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type {
NodeTypeMetadata,
WorkflowNode,
WorkflowEdge,
WorkflowDefinition,
UpstreamNodeOutput,
FieldPathNode,
OutputParameter,
HistoryItem,
} from '#/types/workflow'
import { MOCK_NODE_TYPES, getNodeTypeMetadata } from '#/views/workflow/vue-flow-design/mock/nodeTypes'
export const useWorkflowStore = defineStore('workflow', () => {
// ============== 状态 ==============
/** 所有可用的节点类型 */
const nodeTypes = ref<NodeTypeMetadata[]>(MOCK_NODE_TYPES)
/** 当前工作流的节点 */
const nodes = ref<WorkflowNode[]>([])
/** 当前工作流的边 */
const edges = ref<WorkflowEdge[]>([])
/** 选中的节点 ID */
const selectedNodeId = ref<string | null>(null)
/** 选中的边 ID */
const selectedEdgeId = ref<string | null>(null)
/** 历史记录 */
const history = ref<HistoryItem[]>([])
/** 历史记录索引 */
const historyIndex = ref(-1)
/** 最大历史记录数 */
const maxHistorySize = 50
/** 工作流元数据 */
const workflowMeta = ref({
name: '未命名工作流',
description: '',
id: '',
})
// ============== 计算属性 ==============
/** 选中的节点 */
const selectedNode = computed(() => {
if (!selectedNodeId.value) return null
return nodes.value.find((n) => n.id === selectedNodeId.value)
})
/** 选中的边 */
const selectedEdge = computed(() => {
if (!selectedEdgeId.value) return null
return edges.value.find((e) => e.id === selectedEdgeId.value)
})
/** 是否可以撤销 */
const canUndo = computed(() => historyIndex.value > 0)
/** 是否可以重做 */
const canRedo = computed(() => historyIndex.value < history.value.length - 1)
/** 节点统计 */
const statistics = computed(() => ({
nodeCount: nodes.value.length,
edgeCount: edges.value.length,
nodesByType: nodes.value.reduce((acc, node) => {
acc[node.type] = (acc[node.type] || 0) + 1
return acc
}, {} as Record<string, number>),
}))
// ============== 节点操作 ==============
/**
* 添加节点
*/
function addNode(node: WorkflowNode) {
nodes.value.push(node)
saveHistory()
}
/**
* 删除节点
*/
function removeNode(nodeId: string) {
const index = nodes.value.findIndex((n) => n.id === nodeId)
if (index > -1) {
nodes.value.splice(index, 1)
// 同时删除相关的边
edges.value = edges.value.filter(
(e) => e.source !== nodeId && e.target !== nodeId
)
saveHistory()
}
}
/**
* 更新节点数据
*/
function updateNode(nodeId: string, data: Partial<WorkflowNode>) {
const node = nodes.value.find((n) => n.id === nodeId)
if (node) {
Object.assign(node, data)
saveHistory()
}
}
/**
* 更新节点的 data 字段
*/
function updateNodeData(nodeId: string, data: any) {
const node = nodes.value.find((n) => n.id === nodeId)
if (node) {
node.data = { ...node.data, ...data }
saveHistory()
}
}
// ============== 边操作 ==============
/**
* 添加边
*/
function addEdge(edge: WorkflowEdge) {
edges.value.push(edge)
saveHistory()
}
/**
* 删除边
*/
function removeEdge(edgeId: string) {
const index = edges.value.findIndex((e) => e.id === edgeId)
if (index > -1) {
edges.value.splice(index, 1)
saveHistory()
}
}
// ============== 选择操作 ==============
/**
* 选择节点
*/
function selectNode(nodeId: string | null) {
selectedNodeId.value = nodeId
selectedEdgeId.value = null
}
/**
* 选择边
*/
function selectEdge(edgeId: string | null) {
selectedEdgeId.value = edgeId
selectedNodeId.value = null
}
/**
* 清除选择
*/
function clearSelection() {
selectedNodeId.value = null
selectedEdgeId.value = null
}
// ============== 历史记录 ==============
/**
* 保存历史记录
*/
function saveHistory() {
const currentState: HistoryItem = {
nodes: JSON.parse(JSON.stringify(nodes.value)),
edges: JSON.parse(JSON.stringify(edges.value)),
timestamp: Date.now(),
}
// 移除当前索引之后的历史记录
history.value = history.value.slice(0, historyIndex.value + 1)
// 添加新的历史记录
history.value.push(currentState)
// 限制历史记录大小
if (history.value.length > maxHistorySize) {
history.value.shift()
} else {
historyIndex.value++
}
}
/**
* 撤销
*/
function undo() {
if (canUndo.value) {
historyIndex.value--
const state = history.value[historyIndex.value]
nodes.value = JSON.parse(JSON.stringify(state.nodes))
edges.value = JSON.parse(JSON.stringify(state.edges))
}
}
/**
* 重做
*/
function redo() {
if (canRedo.value) {
historyIndex.value++
const state = history.value[historyIndex.value]
nodes.value = JSON.parse(JSON.stringify(state.nodes))
edges.value = JSON.parse(JSON.stringify(state.edges))
}
}
// ============== 字段映射相关 ==============
/**
* 获取节点的上游节点输出(用于字段映射)
*/
function getUpstreamOutputs(nodeId: string): UpstreamNodeOutput[] {
const upstreamNodes: UpstreamNodeOutput[] = []
// 找到所有连接到当前节点的边
const incomingEdges = edges.value.filter((e) => e.target === nodeId)
incomingEdges.forEach((edge) => {
const sourceNode = nodes.value.find((n) => n.id === edge.source)
if (sourceNode) {
const nodeType = getNodeTypeMetadata(sourceNode.type)
if (nodeType) {
const fieldTree = buildFieldTree(nodeType.outputs, edge.source)
upstreamNodes.push({
nodeId: sourceNode.id,
nodeName: sourceNode.data.label,
outputs: nodeType.outputs,
fieldTree,
})
}
}
})
return upstreamNodes
}
/**
* 从输出参数构建字段树
*/
function buildFieldTree(
outputs: OutputParameter[],
nodeId: string
): FieldPathNode[] {
const tree: FieldPathNode[] = []
outputs.forEach((output) => {
const rootPath = `nodes.${nodeId}.output.${output.id}`
if (output.schema) {
// 有 schema构建详细的字段树
const children = buildFieldTreeFromSchema(output.schema, rootPath)
tree.push({
key: output.id,
path: rootPath,
type: output.type,
description: output.description,
children: children.length > 0 ? children : undefined,
isLeaf: children.length === 0,
})
} else {
// 没有 schema只显示顶级字段
tree.push({
key: output.id,
path: rootPath,
type: output.type,
description: output.description,
isLeaf: true,
})
}
})
return tree
}
/**
* 从 Schema 递归构建字段树
*/
function buildFieldTreeFromSchema(
schema: any,
basePath: string
): FieldPathNode[] {
const nodes: FieldPathNode[] = []
if (schema.type === 'object' && schema.properties) {
Object.entries(schema.properties).forEach(([key, propSchema]: [string, any]) => {
const path = `${basePath}.${key}`
const children = buildFieldTreeFromSchema(propSchema, path)
nodes.push({
key,
path,
type: propSchema.type || 'any',
description: propSchema.description,
example: propSchema.example,
children: children.length > 0 ? children : undefined,
isLeaf: children.length === 0,
})
})
} else if (schema.type === 'array' && schema.items) {
// 数组类型,添加 [0] 示例
const path = `${basePath}[0]`
const children = buildFieldTreeFromSchema(schema.items, path)
if (children.length > 0) {
nodes.push({
key: '[0]',
path,
type: 'object',
description: '数组元素(示例)',
children,
isLeaf: false,
})
}
}
return nodes
}
/**
* 生成字段表达式
*/
function generateFieldExpression(fieldPath: string): string {
return `\${${fieldPath}}`
}
// ============== 工作流操作 ==============
/**
* 加载工作流
*/
function loadWorkflow(workflow: WorkflowDefinition) {
nodes.value = workflow.nodes
edges.value = workflow.edges
workflowMeta.value = {
name: workflow.name,
description: workflow.description || '',
id: workflow.id || '',
}
// 重置历史记录
history.value = [{
nodes: JSON.parse(JSON.stringify(nodes.value)),
edges: JSON.parse(JSON.stringify(edges.value)),
timestamp: Date.now(),
}]
historyIndex.value = 0
}
/**
* 导出工作流定义
*/
function exportWorkflow(): WorkflowDefinition {
return {
id: workflowMeta.value.id,
name: workflowMeta.value.name,
description: workflowMeta.value.description,
nodes: nodes.value,
edges: edges.value,
version: '1.0',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
}
/**
* 清空工作流
*/
function clearWorkflow() {
nodes.value = []
edges.value = []
selectedNodeId.value = null
selectedEdgeId.value = null
history.value = []
historyIndex.value = -1
workflowMeta.value = {
name: '未命名工作流',
description: '',
id: '',
}
}
// ============== 节点类型查询 ==============
/**
* 根据类型获取节点元数据
*/
function getNodeType(type: string): NodeTypeMetadata | undefined {
return nodeTypes.value.find((t) => t.type === type)
}
/**
* 创建节点实例
*/
function createNodeInstance(
type: string,
position: { x: number; y: number }
): WorkflowNode | null {
const nodeType = getNodeType(type)
if (!nodeType) return null
const nodeId = `${type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
return {
id: nodeId,
type: nodeType.type,
position,
data: {
label: nodeType.label,
type: nodeType.type,
description: nodeType.description,
inputs: JSON.parse(JSON.stringify(nodeType.inputs)),
outputs: JSON.parse(JSON.stringify(nodeType.outputs)),
config: {},
},
}
}
return {
// 状态
nodeTypes,
nodes,
edges,
selectedNodeId,
selectedEdgeId,
workflowMeta,
// 计算属性
selectedNode,
selectedEdge,
canUndo,
canRedo,
statistics,
// 节点操作
addNode,
removeNode,
updateNode,
updateNodeData,
// 边操作
addEdge,
removeEdge,
// 选择操作
selectNode,
selectEdge,
clearSelection,
// 历史记录
saveHistory,
undo,
redo,
// 字段映射
getUpstreamOutputs,
buildFieldTree,
generateFieldExpression,
// 工作流操作
loadWorkflow,
exportWorkflow,
clearWorkflow,
// 节点类型
getNodeType,
createNodeInstance,
}
})