首页工作流表单卡片化闭环:执行进度、输入禁用与取消入口

- socket 节点事件推进表单卡片执行进度,失败详情展示详细原因
- 工作流执行时禁用输入框,取消入口移到卡片 footer,卡片定格可重试
- 工作流 ModelRequestParams 按详情原样透传后端
- 会话内产出文件卡片展示(预览/下载/删除),移除顶部结果卡片区

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-15 21:36:33 +08:00
co-authored by Claude
parent dc20a6a829
commit bb15c053e6
9 changed files with 495 additions and 376 deletions
+64 -4
View File
@@ -141,11 +141,23 @@
<!-- 底部操作区:editing 提交 / running 执行中 / done·failed 定格状态 -->
<div class="workflow-form-footer">
<div v-if="isFailed" class="form-error-text">{{ formError }}</div>
<el-button v-if="submitting" type="primary" loading disabled>执行中...</el-button>
<!-- 执行中:节点文本进度推进(后端 node_start/node_complete 事件),无进度时兜底「执行中...」 -->
<!-- 取消执行:工作流执行中的终止入口(InputBar 停止按钮对工作流禁用,改由卡片 footer 提供) -->
<div v-if="submitting" class="executing-progress">
<span class="exec-spinner" />
<span class="exec-text">{{ progressText }}</span>
<el-button size="small" class="exec-cancel-btn" @click="emit('cancel')">取消执行</el-button>
</div>
<el-button v-else-if="!readonly" type="primary" :disabled="submitting" @click="handleSubmit">提交执行</el-button>
<el-tag v-else :type="isFailed ? 'danger' : 'success'" size="small" effect="light">
{{ isFailed ? '执行失败' : '执行完成' }}
</el-tag>
<template v-else>
<el-tag :type="isFailed ? 'danger' : 'success'" size="small" effect="light">
{{ isFailed ? '执行失败' : '执行完成' }}
</el-tag>
<!-- 失败卡片可「重新编辑并执行」:保留已填值切回可编辑,改完重新提交 -->
<el-button v-if="isFailed" size="small" @click="emit('re-edit')">
<el-icon><RefreshRight /></el-icon>重新编辑并执行
</el-button>
</template>
</div>
</div>
</template>
@@ -153,6 +165,7 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { RefreshRight } from '@element-plus/icons-vue';
import PatchTemplateEditor from '/@/components/patchTemplate/PatchTemplateEditor.vue';
import { uploadFile } from '/@/api/common/upload';
import { collectHomeFormFields } from '../utils/flowDsl';
@@ -166,16 +179,23 @@ interface Props {
submitting?: boolean;
// 执行失败信息(有值即 failed 态)
formError?: string;
// 执行进度(后端 node_start/node_complete 事件推进):当前节点序 / 总节点数 / 节点名
progress?: { current: number; total: number; nodeName: string };
}
interface Emits {
(e: 'submit', payload: { formValues: Record<string, any>; formFileNames: Record<string, string | string[]>; templates: any[] }): void;
// 失败卡片「重新编辑并执行」:由父组件把 formStatus 切回 editing,保留已填值
(e: 're-edit'): void;
// 执行中取消:工作流取消入口(InputBar 停止按钮对工作流禁用)
(e: 'cancel'): void;
}
const props = withDefaults(defineProps<Props>(), {
readonly: false,
submitting: false,
formError: '',
progress: () => ({ current: 0, total: 0, nodeName: '' }),
});
const emit = defineEmits<Emits>();
@@ -190,6 +210,16 @@ const templates = ref<any[]>([]);
const isFailed = computed(() => !!props.formError);
const isDisabled = computed(() => props.readonly || props.submitting);
// 执行进度文本:有节点进度(current/total>0)显示「正在执行 N/M:节点名」,否则兜底「执行中...」
const progressText = computed(() => {
const p = props.progress;
if (p && p.total > 0 && p.current > 0) {
const name = p.nodeName ? `${p.nodeName}` : '';
return `正在执行 ${p.current}/${p.total}${name}...`;
}
return '执行中...';
});
const getFieldKey = (node: any, field: any): string => {
const id = node.id || node.nodeCode;
return `${id}|${field.path || field.field || field.label}`;
@@ -551,10 +581,40 @@ const handleSubmit = () => {
font-size: 12px;
color: #ef4444;
line-height: 1.5;
white-space: pre-wrap; /* 失败详情可能含换行(error 字段多行原因),允许换行展示 */
word-break: break-word;
}
}
/* 执行进度:spinner + 文本,节点事件推进 */
.executing-progress {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.exec-spinner {
width: 14px;
height: 14px;
flex-shrink: 0;
border: 2px solid #bfdbfe;
border-top-color: #2563eb;
border-radius: 50%;
animation: exec-spin 0.8s linear infinite;
}
.exec-text {
font-size: 13px;
color: #1e293b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.exec-cancel-btn {
margin-left: auto;
flex-shrink: 0;
}
}
@keyframes exec-spin {
to { transform: rotate(360deg); }
}
.w100 { width: 100%; }