feat: 添加删除选中元素功能与保存工作流对话框组件
- 新增删除选中元素的按钮,支持对节点的删除操作,并处理下级节点引用的清理。 - 将保存工作流对话框重构为独立组件,提升代码可读性与复用性。 - 优化了预览功能的代码结构,确保视频展示的样式一致性。
This commit is contained in:
40
src/views/settings/creation/component/SaveWorkflowDialog.vue
Normal file
40
src/views/settings/creation/component/SaveWorkflowDialog.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" :close-on-click-modal="false">
|
||||
<el-form :model="saveForm" label-position="top">
|
||||
<el-form-item label="工作流名称" required>
|
||||
<el-input v-model="saveForm.flowName" placeholder="请输入工作流名称" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作流描述">
|
||||
<el-input v-model="saveForm.description" type="textarea" :rows="4" placeholder="请输入工作流描述(选填)" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="emit('confirm')">{{ confirmText }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean;
|
||||
saveForm: { flowName: string; description: string };
|
||||
currentEditingWorkflowId: string | null;
|
||||
saving: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'confirm'): void;
|
||||
}>();
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value: boolean) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
const dialogTitle = computed(() => (props.currentEditingWorkflowId ? '编辑工作流' : '保存工作流'));
|
||||
const confirmText = computed(() => (props.currentEditingWorkflowId ? '确定更新' : '确定保存'));
|
||||
</script>
|
||||
@@ -449,6 +449,18 @@
|
||||
</div>
|
||||
<div class="meta-actions">
|
||||
<el-button size="small" @click="resetFlow">清空画布</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
:disabled="
|
||||
!selectedElement ||
|
||||
(selectedElement.kind === 'node' &&
|
||||
(selectedElement.properties?.nodeCode === START_NODE_CODE || selectedElement.text === START_NODE_TEXT))
|
||||
"
|
||||
@click="deleteSelectedElement"
|
||||
>
|
||||
删除选中
|
||||
</el-button>
|
||||
<el-button type="primary" size="small" @click="saveWorkflowAction" :loading="saving">保存工作流</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -564,25 +576,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 保存工作流对话框 -->
|
||||
<el-dialog
|
||||
<SaveWorkflowDialog
|
||||
v-model="saveDialogVisible"
|
||||
:title="currentEditingWorkflowId ? '编辑工作流' : '保存工作流'"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form :model="saveForm" label-position="top">
|
||||
<el-form-item label="工作流名称" required>
|
||||
<el-input v-model="saveForm.flowName" placeholder="请输入工作流名称" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作流描述">
|
||||
<el-input v-model="saveForm.description" type="textarea" :rows="4" placeholder="请输入工作流描述(选填)" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="saveDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmSaveWorkflow" :loading="saving">{{ currentEditingWorkflowId ? '确定更新' : '确定保存' }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
:save-form="saveForm"
|
||||
:current-editing-workflow-id="currentEditingWorkflowId"
|
||||
:saving="saving"
|
||||
@confirm="confirmSaveWorkflow"
|
||||
/>
|
||||
|
||||
<!-- 技能选择器 -->
|
||||
<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkill" @confirm="handleSkillConfirm" />
|
||||
@@ -770,7 +770,12 @@
|
||||
<el-dialog v-model="previewDialogVisible" title="预览" width="95%" top="2vh" :close-on-click-modal="false" destroy-on-close>
|
||||
<div class="preview-container">
|
||||
<el-image v-if="previewUrl && previewMode === 'image'" :src="previewUrl" fit="contain" style="width: 100%; height: 100%" />
|
||||
<video v-else-if="previewUrl && previewMode === 'video'" :src="previewUrl" controls style="width: 100%; height: 100%; background: #000"></video>
|
||||
<video
|
||||
v-else-if="previewUrl && previewMode === 'video'"
|
||||
:src="previewUrl"
|
||||
controls
|
||||
style="width: 100%; height: 100%; background: #000"
|
||||
></video>
|
||||
<audio v-else-if="previewUrl && previewMode === 'audio'" :src="previewUrl" controls style="width: 100%"></audio>
|
||||
<iframe v-else-if="previewUrl" :src="previewUrl" class="preview-iframe" frameborder="0"></iframe>
|
||||
<el-empty v-else description="无法加载预览内容" />
|
||||
@@ -789,6 +794,7 @@ import '@logicflow/core/dist/index.css';
|
||||
import '@logicflow/extension/lib/style/index.css';
|
||||
import SkillSelector from '/@/components/skill/NodeSkillSelector.vue';
|
||||
import ModelSelector from '/@/components/model/ModelSelector.vue';
|
||||
import SaveWorkflowDialog from './component/SaveWorkflowDialog.vue';
|
||||
import type { SkillItem } from '/@/api/settings/skill';
|
||||
import {
|
||||
downloadToFile,
|
||||
@@ -3236,14 +3242,14 @@ watch(
|
||||
currentHttpBodyField.value = '';
|
||||
showHttpBodyDialog.value = false;
|
||||
|
||||
// 重置 dynamicFormValues(不删除固定字段键,动态 expand 键按节点切换清理)
|
||||
for (const key in dynamicFormValues) {
|
||||
if (key.includes('_responseType_expand_')) {
|
||||
delete dynamicFormValues[key];
|
||||
continue;
|
||||
}
|
||||
dynamicFormValues[key] = '';
|
||||
// 重置 dynamicFormValues(不删除固定字段键,动态 expand 键按节点切换清理)
|
||||
for (const key in dynamicFormValues) {
|
||||
if (key.includes('_responseType_expand_')) {
|
||||
delete dynamicFormValues[key];
|
||||
continue;
|
||||
}
|
||||
dynamicFormValues[key] = '';
|
||||
}
|
||||
const currentNodeCode = formState.nodeCode;
|
||||
const baseFormFields = nodeSchemaMap.value[currentNodeCode] || [];
|
||||
const baseFieldNames = new Set(baseFormFields.map((f) => f.field));
|
||||
@@ -3779,7 +3785,100 @@ const resetFlow = () => {
|
||||
selectedElement.value = null;
|
||||
syncDsl();
|
||||
};
|
||||
// 从后端 DSL 恢复工作流
|
||||
const cleanupReferencesToNode = (deletedNodeId: string) => {
|
||||
const lf = logicFlowInstance.value;
|
||||
if (!lf) return 0;
|
||||
|
||||
const graphData = lf.getGraphData() as { nodes?: Item[] };
|
||||
const nodes = graphData.nodes || [];
|
||||
let affectedCount = 0;
|
||||
|
||||
nodes.forEach((node: any) => {
|
||||
if (node.id === deletedNodeId) return;
|
||||
const props = node.properties || {};
|
||||
const inputSource = Array.isArray(props.inputSource) ? props.inputSource : [];
|
||||
const nextInputSource = inputSource.filter((item: any) => item?.nodeId !== deletedNodeId);
|
||||
|
||||
if (nextInputSource.length === inputSource.length) return;
|
||||
|
||||
affectedCount += 1;
|
||||
const normalizedInputSource = nextInputSource.length > 0 ? nextInputSource : null;
|
||||
lf.setProperties(node.id, {
|
||||
...props,
|
||||
inputSource: normalizedInputSource,
|
||||
});
|
||||
|
||||
if (selectedElement.value?.id === node.id) {
|
||||
selectedElement.value.properties = {
|
||||
...props,
|
||||
inputSource: normalizedInputSource,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return affectedCount;
|
||||
};
|
||||
const getAffectedDownstreamNodeNames = (deletedNodeId: string) => {
|
||||
const lf = logicFlowInstance.value;
|
||||
if (!lf) return [] as string[];
|
||||
|
||||
const graphData = lf.getGraphData() as { nodes?: Item[] };
|
||||
const nodes = graphData.nodes || [];
|
||||
const names: string[] = [];
|
||||
|
||||
nodes.forEach((node: any) => {
|
||||
if (node.id === deletedNodeId) return;
|
||||
const props = node.properties || {};
|
||||
const inputSource = Array.isArray(props.inputSource) ? props.inputSource : [];
|
||||
const referenced = inputSource.some((item: any) => item?.nodeId === deletedNodeId);
|
||||
if (!referenced) return;
|
||||
|
||||
const nodeName = typeof node.text === 'string' ? node.text : node.text?.value || node.id;
|
||||
names.push(String(nodeName));
|
||||
});
|
||||
|
||||
return names;
|
||||
};
|
||||
const deleteSelectedElement = async () => {
|
||||
const lf = logicFlowInstance.value;
|
||||
const cur = selectedElement.value;
|
||||
if (!lf || !cur) return;
|
||||
|
||||
if (cur.kind === 'node' && (cur.properties?.nodeCode === START_NODE_CODE || cur.text === START_NODE_TEXT)) {
|
||||
ElMessage.warning('开始节点不能删除');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let affectedCount = 0;
|
||||
if (cur.kind === 'node') {
|
||||
const affectedNodeNames = getAffectedDownstreamNodeNames(cur.id);
|
||||
if (affectedNodeNames.length > 0) {
|
||||
const previewNames = affectedNodeNames.slice(0, 8);
|
||||
const overflowText = affectedNodeNames.length > 8 ? `\n...等 ${affectedNodeNames.length} 个节点` : '';
|
||||
await ElMessageBox.confirm(
|
||||
`删除该节点将清理以下下级节点中的引用:\n${previewNames.join('、')}${overflowText}`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '继续删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
affectedCount = cleanupReferencesToNode(cur.id);
|
||||
lf.deleteNode(cur.id);
|
||||
} else {
|
||||
lf.deleteEdge(cur.id);
|
||||
}
|
||||
selectedElement.value = null;
|
||||
ElMessage.success(affectedCount > 0 ? `删除成功,已清理 ${affectedCount} 个下级节点引用` : '删除成功');
|
||||
} catch (error) {
|
||||
if (error === 'cancel') return;
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
};// 从后端 DSL 恢复工作流
|
||||
const loadWorkflowFromDsl = (dsl: any) => {
|
||||
const lf = logicFlowInstance.value;
|
||||
if (!lf || !dsl) return;
|
||||
@@ -5527,3 +5626,7 @@ onBeforeUnmount(() => {
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user