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>
|
||||||
<div class="meta-actions">
|
<div class="meta-actions">
|
||||||
<el-button size="small" @click="resetFlow">清空画布</el-button>
|
<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>
|
<el-button type="primary" size="small" @click="saveWorkflowAction" :loading="saving">保存工作流</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -564,25 +576,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 保存工作流对话框 -->
|
<!-- 保存工作流对话框 -->
|
||||||
<el-dialog
|
<SaveWorkflowDialog
|
||||||
v-model="saveDialogVisible"
|
v-model="saveDialogVisible"
|
||||||
:title="currentEditingWorkflowId ? '编辑工作流' : '保存工作流'"
|
:save-form="saveForm"
|
||||||
width="500px"
|
:current-editing-workflow-id="currentEditingWorkflowId"
|
||||||
:close-on-click-modal="false"
|
:saving="saving"
|
||||||
>
|
@confirm="confirmSaveWorkflow"
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- 技能选择器 -->
|
<!-- 技能选择器 -->
|
||||||
<SkillSelector v-model="showSkillSelector" :default-skill="selectedSkill" @confirm="handleSkillConfirm" />
|
<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>
|
<el-dialog v-model="previewDialogVisible" title="预览" width="95%" top="2vh" :close-on-click-modal="false" destroy-on-close>
|
||||||
<div class="preview-container">
|
<div class="preview-container">
|
||||||
<el-image v-if="previewUrl && previewMode === 'image'" :src="previewUrl" fit="contain" style="width: 100%; height: 100%" />
|
<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>
|
<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>
|
<iframe v-else-if="previewUrl" :src="previewUrl" class="preview-iframe" frameborder="0"></iframe>
|
||||||
<el-empty v-else description="无法加载预览内容" />
|
<el-empty v-else description="无法加载预览内容" />
|
||||||
@@ -789,6 +794,7 @@ import '@logicflow/core/dist/index.css';
|
|||||||
import '@logicflow/extension/lib/style/index.css';
|
import '@logicflow/extension/lib/style/index.css';
|
||||||
import SkillSelector from '/@/components/skill/NodeSkillSelector.vue';
|
import SkillSelector from '/@/components/skill/NodeSkillSelector.vue';
|
||||||
import ModelSelector from '/@/components/model/ModelSelector.vue';
|
import ModelSelector from '/@/components/model/ModelSelector.vue';
|
||||||
|
import SaveWorkflowDialog from './component/SaveWorkflowDialog.vue';
|
||||||
import type { SkillItem } from '/@/api/settings/skill';
|
import type { SkillItem } from '/@/api/settings/skill';
|
||||||
import {
|
import {
|
||||||
downloadToFile,
|
downloadToFile,
|
||||||
@@ -3236,14 +3242,14 @@ watch(
|
|||||||
currentHttpBodyField.value = '';
|
currentHttpBodyField.value = '';
|
||||||
showHttpBodyDialog.value = false;
|
showHttpBodyDialog.value = false;
|
||||||
|
|
||||||
// 重置 dynamicFormValues(不删除固定字段键,动态 expand 键按节点切换清理)
|
// 重置 dynamicFormValues(不删除固定字段键,动态 expand 键按节点切换清理)
|
||||||
for (const key in dynamicFormValues) {
|
for (const key in dynamicFormValues) {
|
||||||
if (key.includes('_responseType_expand_')) {
|
if (key.includes('_responseType_expand_')) {
|
||||||
delete dynamicFormValues[key];
|
delete dynamicFormValues[key];
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
dynamicFormValues[key] = '';
|
|
||||||
}
|
}
|
||||||
|
dynamicFormValues[key] = '';
|
||||||
|
}
|
||||||
const currentNodeCode = formState.nodeCode;
|
const currentNodeCode = formState.nodeCode;
|
||||||
const baseFormFields = nodeSchemaMap.value[currentNodeCode] || [];
|
const baseFormFields = nodeSchemaMap.value[currentNodeCode] || [];
|
||||||
const baseFieldNames = new Set(baseFormFields.map((f) => f.field));
|
const baseFieldNames = new Set(baseFormFields.map((f) => f.field));
|
||||||
@@ -3779,7 +3785,100 @@ const resetFlow = () => {
|
|||||||
selectedElement.value = null;
|
selectedElement.value = null;
|
||||||
syncDsl();
|
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 loadWorkflowFromDsl = (dsl: any) => {
|
||||||
const lf = logicFlowInstance.value;
|
const lf = logicFlowInstance.value;
|
||||||
if (!lf || !dsl) return;
|
if (!lf || !dsl) return;
|
||||||
@@ -5527,3 +5626,7 @@ onBeforeUnmount(() => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user