工作流功能完善:子例程弹窗角色分支与模板名称修复、模板子工作流递归补全、首页工作流名称省略+tooltip

- 引入子例程弹窗:按 isAdmin 分支读取 listFlowTemplateRes/listFlowUserRes,名称兼容 flowTemplateName
- 模板补全弹窗:递归展开子工作流模型节点,一次 PUT 更新整棵树(updateWorkflowFromTemplate)
- 首页工作流胶囊/占位卡片:名称单行省略 + el-tooltip 悬停显示全名

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-24 18:09:34 +08:00
co-authored by Claude
parent dbd41cc045
commit dd4e719f55
5 changed files with 296 additions and 114 deletions
+20 -1
View File
@@ -113,7 +113,7 @@ export interface SubFlowWorkflowItem {
}
// 获取"我的工作流"列表(IsOwn=true 只取本人创建,供 sub_flow 节点选择引入)
// 响应结构多形态容错在调用方处理(data.list ?? data.listFlowUserRes.list
// 响应结构多形态容错在调用方处理(扁平 list 优先,否则按 isAdmin 分支读取 listFlowTemplateRes / listFlowUserRes
export function getSubFlowWorkflowList(params?: {
pageNum: number;
pageSize: number;
@@ -148,3 +148,22 @@ export function downloadWorkspaceFile(data: { fileURL: string }, requestOptions?
requestOptions,
});
}
// 与后端 UpdateFlowUserReq 对齐的更新树:id/flowName/description/flowContent/subFlows
// subFlows 递归嵌套,一次 PUT 更新整棵「主流程 + 嵌套子流程」树);独立实现,不动内容创作的 API
export interface UpdateFlowUserTree {
id: string;
flowName: string;
description: string;
flowContent: any;
subFlows?: UpdateFlowUserTree[];
}
export function updateWorkflowFromTemplate(data: UpdateFlowUserTree, requestOptions?: RequestOptions) {
return request({
url: '/ai-agent/flow/user/update',
method: 'put',
data,
requestOptions,
});
}
+23 -8
View File
@@ -55,17 +55,23 @@
<!-- 快捷工作流胶囊输入框下方 -->
<Transition name="capsule-slide" @enter="onCapsuleEnter" @after-enter="onCapsuleAfterEnter" @leave="onCapsuleLeave" @after-leave="onCapsuleAfterLeave">
<div v-if="visibleWorkflows.length > 0 && !hideShortcuts" class="workflow-shortcuts">
<button
<el-tooltip
v-for="wf in visibleWorkflows"
:key="wf.id"
class="shortcut-pill"
:class="{ active: selectedWorkflowId === wf.id }"
@click="toggleWorkflow(wf.id)"
:content="wf.name"
placement="top"
:show-after="150"
>
<el-icon><Promotion /></el-icon>
{{ wf.name }}
<span v-if="wf.isTemplate" class="pill-tag">模板</span>
</button>
<button
class="shortcut-pill"
:class="{ active: selectedWorkflowId === wf.id }"
@click="toggleWorkflow(wf.id)"
>
<el-icon><Promotion /></el-icon>
<span class="pill-name">{{ wf.name }}</span>
<span v-if="wf.isTemplate" class="pill-tag">模板</span>
</button>
</el-tooltip>
<!-- 更多常驻入口点击跳转工作流管理页查看/选择全部不依赖工作流数量超出上限 -->
<button
class="shortcut-pill shortcut-more"
@@ -516,6 +522,15 @@ const onCapsuleAfterLeave = (el: Element) => {
}
}
/* 胶囊内工作流名称:单行省略显示(不撑宽胶囊),悬停经 el-tooltip 展示完整名称 */
.pill-name {
max-width: 140px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 「更多」入口:与普通胶囊区分,虚线边框 + 更轻的视觉重量,提示可进入管理页查看全部 */
.shortcut-more {
border-style: dashed;
+17 -9
View File
@@ -26,17 +26,23 @@
<p class="placeholder-desc">选择一个工作流,填写参数后一键生成</p>
<div v-if="placeWorkflows.length" class="workflow-cards">
<button
<el-tooltip
v-for="wf in placeWorkflows"
:key="wf.id"
type="button"
class="workflow-card"
@click="handlePlaceSelect(wf)"
:content="wf.name"
placement="top"
:show-after="150"
>
<el-icon class="wf-card-icon"><Promotion /></el-icon>
<span class="wf-card-name">{{ wf.name }}</span>
<span v-if="wf.isTemplate" class="wf-card-tag">模板</span>
</button>
<button
type="button"
class="workflow-card"
@click="handlePlaceSelect(wf)"
>
<el-icon class="wf-card-icon"><Promotion /></el-icon>
<span class="wf-card-name">{{ wf.name }}</span>
<span v-if="wf.isTemplate" class="wf-card-tag">模板</span>
</button>
</el-tooltip>
<!-- 「更多」常驻入口:点击跳转工作流管理页查看/选择全部,不依赖工作流数量超出上限 -->
<button
type="button"
@@ -229,7 +235,9 @@ onMounted(() => {
font-weight: 600;
color: #1e293b;
line-height: 1.4;
word-break: break-word;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.wf-card-tag {
font-size: 10px;
@@ -25,31 +25,36 @@
</el-form-item>
</el-form>
<el-empty v-if="modelNodes.length === 0" description="该模板无需补全模型参数" :image-size="60" />
<el-empty v-if="flowWorkflowGroups.length === 0" description="该模板无需补全模型参数" :image-size="60" />
<div v-for="node in modelNodes" :key="node.id" class="tc-node">
<div class="tc-node-title">
<span class="tc-node-name">{{ node.name || node.nodeCode }}</span>
<el-tag v-if="!node.modelConfig?.modelId" type="warning" size="small">未选择模型</el-tag>
<el-tag v-else-if="node.modelConfig?.modelName" type="success" size="small">{{ node.modelConfig.modelName }}</el-tag>
</div>
<!-- 主模板 / 子工作流 分组递归展开整棵工作流树每层模型节点单独补全 -->
<div v-for="group in flowWorkflowGroups" :key="group.wf.key" class="tc-group">
<div v-if="group.wf.key !== 'main'" class="tc-group-title">{{ group.wf.pathLabel }}</div>
<div class="tc-node-model">
<template v-if="node.modelConfig?.modelId">
<!-- 系统模型直接内联显示 API Key 输入框保存时统一转换 -->
<div v-if="systemModelNodeIds[node.id]" class="tc-api-key-inline">
<el-input
v-model="apiKeyInputs[node.id]"
type="password"
show-password
placeholder="该模型为系统内置模型,请输入你的 API Key"
size="small"
clearable
/>
</div>
<el-button type="primary" plain size="small" @click="openModelSelector(node)">重新选择模型</el-button>
</template>
<el-button v-else type="primary" plain size="small" @click="openModelSelector(node)">选择模型</el-button>
<div v-for="m in group.models" :key="m.nodeKey" class="tc-node">
<div class="tc-node-title">
<span class="tc-node-name">{{ m.node.name || m.node.nodeCode }}</span>
<el-tag v-if="!m.node.modelConfig?.modelId" type="warning" size="small">未选择模型</el-tag>
<el-tag v-else-if="m.node.modelConfig?.modelName" type="success" size="small">{{ m.node.modelConfig.modelName }}</el-tag>
</div>
<div class="tc-node-model">
<template v-if="m.node.modelConfig?.modelId">
<!-- 系统模型直接内联显示 API Key 输入框保存时统一转换 -->
<div v-if="systemModelNodeIds[m.nodeKey]" class="tc-api-key-inline">
<el-input
v-model="apiKeyInputs[m.nodeKey]"
type="password"
show-password
placeholder="该模型为系统内置模型,请输入你的 API Key"
size="small"
clearable
/>
</div>
<el-button type="primary" plain size="small" @click="openModelSelector(m)">重新选择模型</el-button>
</template>
<el-button v-else type="primary" plain size="small" @click="openModelSelector(m)">选择模型</el-button>
</div>
</div>
</div>
</template>
@@ -64,7 +69,7 @@
<ModelSelector
v-model="modelSelectorVisible"
:default-model="selectorDefaultModel"
:same-type-model-type="selectorTargetNode?.modelConfig?.modelType"
:same-type-model-type="selectorTargetRef?.node?.modelConfig?.modelType"
@confirm="handleModelConfirm"
/>
@@ -77,7 +82,8 @@ import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import ModelSelector from '/@/views/settings/workflow/component/ModelSelector.vue';
import { stripReadonlyFields, deepClone, buildModelRequestParamsPath, enrichResponseBodyMapping } from '/@/views/settings/workflow/component/modelParamUtils';
import { getWorkflowDetail, saveWorkflow } from '/@/api/settings/creation';
import { getWorkflowDetail } from '/@/api/settings/creation';
import { updateWorkflowFromTemplate, getSubFlowWorkflowDetail } from '/@/api/settings/workflow';
import { getModelManage, updateModelManage } from '/@/api/settings/modelConfigV2';
interface Props {
@@ -90,41 +96,129 @@ interface Emits {
(e: 'saved', newId: string, flowName: string): void;
}
// 工作流树节点:主模板与嵌套子工作流统一建模,递归展开 sub_flow 引用
interface FlowWorkflowNode {
key: string; // 唯一 key:主模板 'main',子工作流 'sub:{workflowId}'
id: string; // 工作流 id(保存时定位;主模板即来源模板 id)
flowName: string; // 工作流原名(子工作流保存时沿用原名,不可改)
flowContent: any; // 该工作流完整 flowContent(保存时随 subFlows 提交)
nodes: any[]; // flowContent.nodes 引用
startNodeId: string; // 开始节点 idbuildModelRequestParamsPath 契约需要)
pathLabel: string; // 展示:'主模板' / '子工作流『xxx』'
hasModel: boolean; // 该层是否存在 model 节点(决定子树是否放入 subFlows 提交)
children: FlowWorkflowNode[];
}
// 模型节点引用:携带全局唯一 nodeKey(避免不同工作流内节点 id 冲突)与所属工作流 key
interface ModelNodeRef {
wfKey: string;
nodeKey: string;
node: any;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const loading = ref(false);
const saving = ref(false);
const flowName = ref('');
const nodes = ref<any[]>([]);
const templateFlowContent = ref<any>(null);
const flowTree = ref<FlowWorkflowNode | null>(null);
const modelRefs = ref<ModelNodeRef[]>([]);
// 防环:已展开过的工作流 id(子工作流可能互相引用 / 引用主模板)
const visitedFlowIds = new Set<string>();
const modelSelectorVisible = ref(false);
const selectorTargetNode = ref<any>(null);
const selectorTargetRef = ref<ModelNodeRef | null>(null);
const selectorDefaultModel = ref<any>(null);
// 补全 API Key:内联输入框暂存 Key,保存时统一转换为用户模型
const apiKeyInputs = ref<Record<string, string>>({});
const systemModelNodeIds = ref<Record<string, boolean>>({});
const modelNodes = computed(() => nodes.value.filter((n) => String(n?.nodeCode || '').toLowerCase() === 'model'));
// 开始节点 id:模板 DSL 与工作流管理 DSL 结构一致(nodeCode === '__start__'),
// 供 buildModelRequestParamsPath 为「表单展示」字段补 valueSource 指向开始节点
const getStartNodeId = () => {
const sn = nodes.value.find((n) => String(n?.nodeCode || '').toLowerCase() === '__start__');
return sn?.id || '';
};
// 按工作流分组(主模板在前,子工作流按树序),供模板分组渲染
const flowWorkflowGroups = computed(() => {
const groups: { wf: FlowWorkflowNode; models: ModelNodeRef[] }[] = [];
const walk = (wf: FlowWorkflowNode) => {
groups.push({ wf, models: modelRefs.value.filter((m) => m.wfKey === wf.key) });
for (const c of wf.children) walk(c);
};
if (flowTree.value) walk(flowTree.value);
return groups;
});
const canSave = computed(() => {
if (!flowName.value.trim()) return false;
// 所有模型节点都必须已绑定模型
if (modelNodes.value.some((n) => !n.modelConfig?.modelId)) return false;
// 整棵树所有模型节点都必须已绑定模型
if (modelRefs.value.some((m) => !m.node.modelConfig?.modelId)) return false;
// 系统模型节点必须已填写 API Key(未补全不可保存)
if (modelNodes.value.some((n) => systemModelNodeIds.value[n.id] && !(apiKeyInputs.value[n.id] || '').trim())) return false;
if (modelRefs.value.some((m) => systemModelNodeIds.value[m.nodeKey] && !(apiKeyInputs.value[m.nodeKey] || '').trim())) return false;
return true;
});
// 递归展开:遍历当前工作流 nodes 收集 model 节点;遇 sub_flow 递归拉取子工作流详情
const expandSubFlows = async (wf: FlowWorkflowNode) => {
for (const n of wf.nodes) {
const code = String(n?.nodeCode || '').toLowerCase();
if (code === 'model') {
wf.hasModel = true;
modelRefs.value.push({ wfKey: wf.key, nodeKey: `${wf.key}|${n.id}`, node: n });
} else if (code === 'sub_flow') {
const subWfId = String(n?.subConfig?.workflowId || '');
if (!subWfId || visitedFlowIds.has(subWfId)) continue;
visitedFlowIds.add(subWfId);
try {
const detail = (await getSubFlowWorkflowDetail(subWfId))?.data;
const fc = detail?.flowContent || {};
const childNodes = Array.isArray(fc.nodes) ? fc.nodes : [];
const child: FlowWorkflowNode = {
key: 'sub:' + subWfId,
id: subWfId,
flowName: detail?.flowName || '',
flowContent: fc,
nodes: childNodes,
startNodeId: childNodes.find((x: any) => String(x?.nodeCode || '').toLowerCase() === '__start__')?.id || '',
pathLabel: '子工作流『' + (detail?.flowName || subWfId) + '』',
hasModel: false,
children: [],
};
wf.children.push(child);
await expandSubFlows(child);
} catch {
// 子工作流详情拉取失败:跳过该子流程,不阻塞主模板补全
}
}
}
};
// 构建主模板树节点:优先拉取模板完整 DSL(get 接口),失败则回退列表项 flowContent
const buildMainTree = async () => {
let fc: any = null;
let mainNodes: any[] = [];
let detailFlowName = '';
try {
const detail = (await getWorkflowDetail(props.template?.id))?.data;
fc = detail?.flowContent || null;
mainNodes = JSON.parse(JSON.stringify(fc?.nodes || detail?.nodeInputParams || []));
detailFlowName = detail?.flowName || '';
} catch {
fc = props.template?.raw?.flowContent || props.template?.flowContent;
mainNodes = JSON.parse(JSON.stringify(fc?.nodes || []));
}
flowTree.value = {
key: 'main',
id: String(props.template?.id || ''),
flowName: detailFlowName || props.template?.flowName || '',
flowContent: fc,
nodes: mainNodes,
startNodeId: mainNodes.find((n: any) => String(n?.nodeCode || '').toLowerCase() === '__start__')?.id || '',
pathLabel: '主模板',
hasModel: false,
children: [],
};
visitedFlowIds.add(flowTree.value.id);
if (detailFlowName) flowName.value = '我的-' + detailFlowName;
};
watch(
() => props.modelValue,
async (val) => {
@@ -132,21 +226,14 @@ watch(
loading.value = true;
saving.value = false;
flowName.value = '我的-' + (props.template?.flowName || '系统模板');
nodes.value = [];
templateFlowContent.value = null;
flowTree.value = null;
modelRefs.value = [];
visitedFlowIds.clear();
apiKeyInputs.value = {};
systemModelNodeIds.value = {};
try {
// 优先拉取模板完整 DSL(get 接口),失败则回退列表项 flowContent
const res = await getWorkflowDetail(props.template?.id);
const detail = res.data;
templateFlowContent.value = detail?.flowContent || null;
nodes.value = JSON.parse(JSON.stringify(detail?.flowContent?.nodes || detail?.nodeInputParams || []));
if (detail?.flowName) flowName.value = '我的-' + detail.flowName;
} catch {
const fc = props.template?.raw?.flowContent || props.template?.flowContent;
templateFlowContent.value = fc || null;
nodes.value = JSON.parse(JSON.stringify(fc?.nodes || []));
await buildMainTree();
if (flowTree.value) await expandSubFlows(flowTree.value);
} finally {
// 先判断各已绑定模型节点是否需要补 API Key(系统模型),完成后再结束 loading,避免竞态
await checkModelApiKey();
@@ -155,16 +242,31 @@ watch(
}
);
const openModelSelector = (node: any) => {
selectorTargetNode.value = node;
const openModelSelector = (m: ModelNodeRef) => {
selectorTargetRef.value = m;
selectorDefaultModel.value = null;
modelSelectorVisible.value = true;
};
// 取指定工作流(按 key)的开始节点 id,用于契约字段生成
const getWfStartNodeId = (wfKey: string): string => {
const walk = (wf: FlowWorkflowNode | null): string => {
if (!wf) return '';
if (wf.key === wfKey) return wf.startNodeId;
for (const c of wf.children) {
const r = walk(c);
if (r) return r;
}
return '';
};
return walk(flowTree.value);
};
// 选模型:复刻工作流管理 handleModelConfirm 的初始化逻辑
const handleModelConfirm = (model: any) => {
const node = selectorTargetNode.value;
if (node) {
const m = selectorTargetRef.value;
if (m) {
const node = m.node;
node.modelConfig = node.modelConfig || {};
node.modelConfig.modelId = model.id || '';
node.modelConfig.modelName = model.modelName;
@@ -173,41 +275,38 @@ const handleModelConfirm = (model: any) => {
const params = stripReadonlyFields(deepClone(model.requestBodyMapping ?? null));
node.modelConfig.modelRequestParams = params;
// 后端契约:基于编辑器参数树(含 __originIndex 原始索引)生成全部叶子字段扁平清单,与工作流管理保存一致
node.modelConfig.modelRequestParamsPath = buildModelRequestParamsPath(params, getStartNodeId());
node.modelConfig.modelRequestParamsPath = buildModelRequestParamsPath(params, getWfStartNodeId(m.wfKey));
// 用模型 responseMapping 的中文描述填充 value,供工作流管理「引用上级节点输出」展示中文
node.modelConfig.modelResponseBodyMapping = enrichResponseBodyMapping(model.responseBodyMapping, model.responseMapping);
// 选中为用户模型,无需再补 API Key
systemModelNodeIds.value[node.id] = false;
apiKeyInputs.value[node.id] = '';
systemModelNodeIds.value[m.nodeKey] = false;
apiKeyInputs.value[m.nodeKey] = '';
}
modelSelectorVisible.value = false;
selectorTargetNode.value = null;
selectorTargetRef.value = null;
};
// 打开弹窗后:判断每个已绑定模型节点是否为系统模型(需补 API Key)
const checkModelApiKey = async () => {
const targets = modelNodes.value.filter((n) => n.modelConfig?.modelId);
const targets = modelRefs.value.filter((m) => m.node.modelConfig?.modelId);
await Promise.all(
targets.map(async (node) => {
targets.map(async (m) => {
try {
const res = await getModelManage(node.modelConfig.modelId);
const res = await getModelManage(m.node.modelConfig.modelId);
const model = res?.data?.modelManage || res?.data;
if (model?.systemModel === true) {
systemModelNodeIds.value[node.id] = true;
} else {
systemModelNodeIds.value[node.id] = false;
apiKeyInputs.value[node.id] = '';
}
systemModelNodeIds.value[m.nodeKey] = model?.systemModel === true;
if (!systemModelNodeIds.value[m.nodeKey]) apiKeyInputs.value[m.nodeKey] = '';
} catch {
// 详情获取失败:不强制补 Key,避免阻塞保存
systemModelNodeIds.value[node.id] = false;
systemModelNodeIds.value[m.nodeKey] = false;
}
})
);
};
// 将系统模型克隆为用户模型并绑定到节点(保存时统一调用)
const convertSystemModel = async (node: any) => {
const convertSystemModel = async (m: ModelNodeRef) => {
const node = m.node;
const res = await getModelManage(node.modelConfig.modelId);
const src = res?.data?.modelManage || res?.data;
if (!src?.id) throw new Error('模型详情获取失败');
@@ -219,7 +318,7 @@ const convertSystemModel = async (node: any) => {
modelSupplier: src.modelSupplier,
baseUrl: src.baseUrl,
responseType: src.responseType ?? src.invokeType,
apiKey: (apiKeyInputs.value[node.id] || '').trim(),
apiKey: (apiKeyInputs.value[m.nodeKey] || '').trim(),
enabled: src.enabled,
chatModel: src.ChatModel ?? src.chatModel,
maxConcurrency: src.maxConcurrency,
@@ -243,43 +342,66 @@ const convertSystemModel = async (node: any) => {
if (!newModelManage?.id) throw new Error('接口未返回新的用户模型');
// 更新节点绑定为新用户模型,不再需要补 Key
node.modelConfig.modelId = newModelManage.id;
systemModelNodeIds.value[node.id] = false;
apiKeyInputs.value[node.id] = '';
systemModelNodeIds.value[m.nodeKey] = false;
apiKeyInputs.value[m.nodeKey] = '';
};
// 各工作流 DSL 缺 modelRequestParamsPath 时兜底生成(含嵌套子工作流)
const patchRequestParamsPath = (wf: FlowWorkflowNode) => {
for (const n of wf.nodes) {
if (String(n?.nodeCode || '').toLowerCase() !== 'model') continue;
const mc = n?.modelConfig;
if (mc?.modelRequestParams && !mc.modelRequestParamsPath) {
mc.modelRequestParamsPath = buildModelRequestParamsPath(mc.modelRequestParams, wf.startNodeId);
}
}
for (const c of wf.children) patchRequestParamsPath(c);
};
// 子树内是否含 model 节点(自身或其子孙):含模型的子树才提交更新,保证嵌套写回链路完整
const nodeHasModelInSubtree = (wf: FlowWorkflowNode): boolean => wf.hasModel || wf.children.some(nodeHasModelInSubtree);
// 组装嵌套 subFlows:仅含模型的工作流子树才提交(无模型的子流程无需更新)
const buildSubFlows = (wf: FlowWorkflowNode): any[] =>
wf.children
.filter((c) => nodeHasModelInSubtree(c))
.map((c) => ({
id: c.id,
flowName: c.flowName,
description: '',
flowContent: c.flowContent,
subFlows: buildSubFlows(c),
}));
const handleSave = async () => {
const name = flowName.value.trim();
if (!name) {
ElMessage.warning('请输入工作流名称');
return;
}
const missing = modelNodes.value.find((n) => !n.modelConfig?.modelId);
const missing = modelRefs.value.find((m) => !m.node.modelConfig?.modelId);
if (missing) {
ElMessage.warning(`节点「${missing.name || missing.nodeCode}」未选择模型,请先选择`);
ElMessage.warning(`节点「${missing.node.name || missing.node.nodeCode}」未选择模型,请先选择`);
return;
}
saving.value = true;
try {
// 保存前统一转换:系统模型节点先克隆为用户模型并绑定
for (const node of modelNodes.value.filter((n) => systemModelNodeIds.value[n.id])) {
await convertSystemModel(node);
}
// 模板 DSL 若缺 modelRequestParamsPath(旧版模板),用现有参数树兜底生成,保证后端拿到契约字段
const startNodeId = getStartNodeId();
for (const node of modelNodes.value) {
const mc = node?.modelConfig;
if (mc?.modelRequestParams && !mc.modelRequestParamsPath) {
mc.modelRequestParamsPath = buildModelRequestParamsPath(mc.modelRequestParams, startNodeId);
}
// 保存前统一转换:整棵树的系统模型节点先克隆为用户模型并绑定
for (const m of modelRefs.value.filter((mm) => systemModelNodeIds.value[mm.nodeKey])) {
await convertSystemModel(m);
}
// 旧模板缺 modelRequestParamsPath 的兜底生成(主模板 + 嵌套子工作流)
if (flowTree.value) patchRequestParamsPath(flowTree.value);
const flowContent = {
...(templateFlowContent.value || {}),
nodes: nodes.value,
...(flowTree.value?.flowContent || {}),
nodes: flowTree.value?.nodes || [],
};
const res = await saveWorkflow({
const res = await updateWorkflowFromTemplate({
id: flowTree.value?.id || String(props.template?.id || ''),
flowName: name,
description: '',
flowContent,
subFlows: flowTree.value ? buildSubFlows(flowTree.value) : [],
});
const data = res?.data;
const newId = data?.id ?? (typeof data === 'string' || typeof data === 'number' ? data : null);
@@ -317,6 +439,21 @@ const handleClose = () => {
}
}
/* 主模板 / 子工作流 分组:子工作流组带标题条,与主模板节点区分 */
.tc-group {
margin-bottom: 16px;
}
.tc-group-title {
font-size: 12px;
font-weight: 600;
color: #2563eb;
background: #eff6ff;
border-radius: 6px;
padding: 6px 10px;
margin-bottom: 10px;
}
.tc-node {
border: 1px solid #e2e8f0;
border-radius: 10px;
@@ -23,7 +23,7 @@
<el-icon v-if="selectedWorkflow?.id === workflow.id" class="check-icon" color="#67c23a"><CircleCheck /></el-icon>
</div>
<div class="workflow-card-body">
<h3 class="workflow-name">{{ workflow.flowName || workflow.name || '未命名工作流' }}</h3>
<h3 class="workflow-name">{{ workflow.flowName || workflow.flowTemplateName || workflow.name || '未命名工作流' }}</h3>
<p class="workflow-desc">{{ workflow.description || '暂无描述' }}</p>
</div>
</div>
@@ -104,16 +104,19 @@ const fetchWorkflowList = async () => {
};
const res = await getSubFlowWorkflowList(params);
const data = res.data || {};
// 响应结构多形态容错:扁平 list 或 listFlowUserRes 嵌套
const list = data.list ?? data.listFlowUserRes?.list ?? [];
// 按登录角色分支读取:isAdmin=true(管理员)工作流在 listFlowTemplateRes,普通用户在 listFlowUserRes
// 兼容扁平 list 结构
const isAdmin = !!data.isAdmin;
const source = isAdmin ? data.listFlowTemplateRes : data.listFlowUserRes;
const list = data.list ?? source?.list ?? [];
workflowList.value = list || [];
pagination.total = data.total ?? data.listFlowUserRes?.total ?? workflowList.value.length;
pagination.total = data.total ?? source?.total ?? workflowList.value.length;
// 预选项仅为部分信息(切换节点后无 id)时,按名称匹配当前页以高亮
// 名称字段兼容 flowName / name 形态
// 名称字段兼容 flowName / flowTemplateName / name 形态
if (selectedWorkflow.value && !selectedWorkflow.value.id) {
const selName = selectedWorkflow.value.flowName || selectedWorkflow.value.name;
const selName = selectedWorkflow.value.flowName || selectedWorkflow.value.flowTemplateName || selectedWorkflow.value.name;
if (selName) {
const matched = workflowList.value.find((w) => (w.flowName || w.name) === selName);
const matched = workflowList.value.find((w) => (w.flowName || w.flowTemplateName || w.name) === selName);
if (matched) selectedWorkflow.value = matched;
}
}