更新数字人创作页面功能

- 在树节点中添加点击事件处理,支持工作流节点的详细信息获取和会话ID管理。
- 修改节点操作逻辑,优化预览和下载功能,支持标题节点的操作。
- 引入会话ID管理,确保在工作流切换时正确处理会话状态。
- 更新相关状态和逻辑,提升用户交互体验和数据一致性。
This commit is contained in:
2026-05-12 00:19:15 +08:00
parent 4e407675a2
commit 41a40cc6ee

View File

@@ -15,11 +15,12 @@
default-expand-all default-expand-all
:highlight-current="true" :highlight-current="true"
:expand-on-click-node="false" :expand-on-click-node="false"
@node-click="handleTreeNodeClick"
> >
<template #default="{ data }"> <template #default="{ data }">
<div class="tree-node"> <div class="tree-node">
<span class="ellipsis">{{ data.label }}</span> <span class="ellipsis">{{ data.label }}</span>
<div v-if="data.nodeType === 'html' || data.nodeType === 'image'" class="tree-node-actions"> <div v-if="data.nodeType === 'title' && data.fileUrl" class="tree-node-actions">
<el-button type="primary" link size="small" @click.stop="previewNode(data)"> 预览 </el-button> <el-button type="primary" link size="small" @click.stop="previewNode(data)"> 预览 </el-button>
<el-button type="primary" link size="small" @click.stop="downloadNode(data)"> 下载 </el-button> <el-button type="primary" link size="small" @click.stop="downloadNode(data)"> 下载 </el-button>
</div> </div>
@@ -497,7 +498,9 @@
<div class="chat-model-selector"> <div class="chat-model-selector">
<div class="chat-model-search"> <div class="chat-model-search">
<el-input v-model="chatModelSearchKeyword" placeholder="搜索模型名称" clearable @clear="handleChatModelSearch"> <el-input v-model="chatModelSearchKeyword" placeholder="搜索模型名称" clearable @clear="handleChatModelSearch">
<template #prefix><el-icon><Search /></el-icon></template> <template #prefix
><el-icon><Search /></el-icon
></template>
</el-input> </el-input>
<el-button type="primary" @click="handleChatModelSearch">搜索</el-button> <el-button type="primary" @click="handleChatModelSearch">搜索</el-button>
</div> </div>
@@ -584,6 +587,8 @@ interface TreeNode {
nodeType: NodeType; nodeType: NodeType;
children?: TreeNode[]; children?: TreeNode[];
fileUrl?: string; fileUrl?: string;
flowId?: number | string;
sessionId?: string;
} }
interface SelectedState { interface SelectedState {
id: string; id: string;
@@ -632,6 +637,7 @@ const userInput = ref('');
const selectedFiles = ref<File[]>([]); const selectedFiles = ref<File[]>([]);
const selectedCreationSkill = ref<SkillItem | null>(null); const selectedCreationSkill = ref<SkillItem | null>(null);
const showCreationSkillSelector = ref(false); const showCreationSkillSelector = ref(false);
const currentSessionId = ref<string | null>(null); // 当前会话的 sessionId从工作空间进入时使用
const isCreating = ref(false); const isCreating = ref(false);
// 预览相关状态 // 预览相关状态
const previewDialogVisible = ref(false); const previewDialogVisible = ref(false);
@@ -654,13 +660,14 @@ const chatModelPagination = reactive({
const filteredChatModels = computed(() => { const filteredChatModels = computed(() => {
return chatModelList.value; return chatModelList.value;
}); });
// 会话ID管理存储在 sessionStorage 中 // 会话ID管理每次使用工作流时生成新的 sessionId
const getSessionId = () => { const getSessionId = () => {
let sessionId = sessionStorage.getItem('ai_creation_session_id'); // 如果从工作空间进入,使用当前会话的 sessionId
if (!sessionId) { if (currentSessionId.value) {
sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; return currentSessionId.value;
sessionStorage.setItem('ai_creation_session_id', sessionId);
} }
// 否则生成新的 sessionId
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
return sessionId; return sessionId;
}; };
// 格式化参数引用显示 // 格式化参数引用显示
@@ -850,18 +857,13 @@ const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] =>
id: `flow-${di}-${fi}`, id: `flow-${di}-${fi}`,
label: f.flowName || '未命名工作流', label: f.flowName || '未命名工作流',
nodeType: 'contentType', nodeType: 'contentType',
flowId: f.flowId,
sessionId: f.sessionId, // 添加 sessionId
children: (f.items || []).map((item, ii) => ({ children: (f.items || []).map((item, ii) => ({
id: `item-${di}-${fi}-${ii}`, id: `item-${di}-${fi}-${ii}`,
label: item.label || `作品${ii + 1}`, label: item.label || `作品${ii + 1}`,
nodeType: 'title', nodeType: 'title',
children: [ fileUrl: item.content, // 直接在作品层添加 fileUrl
{
id: `html-${di}-${fi}-${ii}`,
label: 'HTML',
nodeType: 'html' as const,
fileUrl: item.content,
},
],
})), })),
})), })),
})); }));
@@ -934,6 +936,7 @@ const createNewWorkflow = () => {
// 切换回画布编辑模式 // 切换回画布编辑模式
isCreationMode.value = false; isCreationMode.value = false;
currentWorkflowForCreation.value = null; currentWorkflowForCreation.value = null;
currentSessionId.value = null; // 清空会话 ID
// 清空当前编辑状态 // 清空当前编辑状态
currentEditingWorkflowId.value = null; currentEditingWorkflowId.value = null;
@@ -1006,7 +1009,7 @@ const handleChatModelSearch = () => {
// 设置对话模型 // 设置对话模型
const handleSetChatModel = async () => { const handleSetChatModel = async () => {
if (!selectedChatModel.value) return; if (!selectedChatModel.value) return;
settingChatModel.value = true; settingChatModel.value = true;
try { try {
await updateChatModel({ await updateChatModel({
@@ -1085,6 +1088,7 @@ const editWorkflow = async (workflow: WorkflowItem) => {
// 切换回画布编辑模式 // 切换回画布编辑模式
isCreationMode.value = false; isCreationMode.value = false;
currentWorkflowForCreation.value = null; currentWorkflowForCreation.value = null;
currentSessionId.value = null; // 清空会话 ID
// 等待 DOM 更新后再加载工作流 // 等待 DOM 更新后再加载工作流
await nextTick(); await nextTick();
@@ -1105,6 +1109,7 @@ const editWorkflow = async (workflow: WorkflowItem) => {
const backToCanvas = async () => { const backToCanvas = async () => {
isCreationMode.value = false; isCreationMode.value = false;
currentWorkflowForCreation.value = null; currentWorkflowForCreation.value = null;
currentSessionId.value = null; // 清空会话 ID
// 等待 DOM 更新后重新渲染画布 // 等待 DOM 更新后重新渲染画布
await nextTick(); await nextTick();
@@ -1172,9 +1177,11 @@ const sendMessage = async () => {
ElMessageBox.alert('请先设置对话模型后再进行创作', '提示', { ElMessageBox.alert('请先设置对话模型后再进行创作', '提示', {
confirmButtonText: '去设置', confirmButtonText: '去设置',
type: 'warning', type: 'warning',
}).then(() => { })
showChatModelSelector.value = true; .then(() => {
}).catch(() => {}); showChatModelSelector.value = true;
})
.catch(() => {});
return; return;
} }
} catch (error) { } catch (error) {
@@ -1281,9 +1288,66 @@ const getFieldClass = (type: string) => {
if (type === 'number' || type === 'switch') return 'form-item-small'; if (type === 'number' || type === 'switch') return 'form-item-small';
return 'form-item-medium'; return 'form-item-medium';
}; };
// 处理树节点点击
const handleTreeNodeClick = async (data: TreeNode) => {
// 只处理工作流节点contentType
if (data.nodeType === 'contentType' && data.flowId) {
try {
// 调用详情接口获取工作流数据,使用 flowId
const res = await getWorkflowDetail(data.flowId);
if (res.data) {
// 设置当前会话的 sessionId从工作空间进入
currentSessionId.value = data.sessionId || null;
// 切换到创作模式
isCreationMode.value = true;
currentWorkflowForCreation.value = res.data;
// 初始化创作表单的值
Object.keys(creationFormValues).forEach((key) => delete creationFormValues[key]);
// 根据 nodeInputParams 初始化表单默认值
if (res.data.nodeInputParams && Array.isArray(res.data.nodeInputParams)) {
res.data.nodeInputParams.forEach((node: any) => {
// 从节点根级别的 formConfig 读取
if (node.formConfig && Array.isArray(node.formConfig)) {
node.formConfig.forEach((field: any) => {
const fieldKey = `${node.id}_${field.label}`;
// 根据字段类型转换值
if (field.type === 'number') {
creationFormValues[fieldKey] = field.value ? Number(field.value) : null;
} else if (field.type === 'switch') {
creationFormValues[fieldKey] = Boolean(field.value);
} else {
creationFormValues[fieldKey] = field.value || '';
}
});
}
// 初始化其他配置字段(从 config 中读取)
if (node.config) {
Object.keys(node.config).forEach((key) => {
if (!['nodeCode', 'width', 'height', 'x', 'y', 'formConfig', 'inputSource', 'fieldMetadata', 'selectedModel'].includes(key)) {
const fieldKey = `${node.id}_${key}`;
creationFormValues[fieldKey] = node.config[key];
}
});
}
});
}
ElMessage.success(`已进入创作模式`);
} else {
ElMessage.warning('该工作流没有内容');
}
} catch (error) {
// 后端错误会自动显示
}
}
};
// 预览节点 // 预览节点
const previewNode = (d: TreeNode) => { const previewNode = (d: TreeNode) => {
if (d.nodeType !== 'html' && d.nodeType !== 'image') return; if (d.nodeType !== 'html' && d.nodeType !== 'image' && d.nodeType !== 'title') return;
const url = buildAssetUrl(d.fileUrl); const url = buildAssetUrl(d.fileUrl);
if (!url) return ElMessage.warning('当前节点没有可用预览地址'); if (!url) return ElMessage.warning('当前节点没有可用预览地址');
previewUrl.value = url; previewUrl.value = url;
@@ -1291,14 +1355,16 @@ const previewNode = (d: TreeNode) => {
}; };
// 下载节点 // 下载节点
const downloadNode = async (d: TreeNode) => { const downloadNode = async (d: TreeNode) => {
if (d.nodeType !== 'html' && d.nodeType !== 'image') return; if (d.nodeType !== 'html' && d.nodeType !== 'image' && d.nodeType !== 'title') return;
if (!d.fileUrl) return ElMessage.warning('当前节点没有可下载地址'); if (!d.fileUrl) return ElMessage.warning('当前节点没有可下载地址');
try { try {
// 下载失败时希望展示更贴近页面语义的提示,因此改为 page 模式。 // 下载失败时希望展示更贴近页面语义的提示,因此改为 page 模式。
const r = await downloadToFile({ fileURL: d.fileUrl }); const r = await downloadToFile({ fileURL: d.fileUrl });
const blob = r instanceof Blob ? r : r?.data; const blob = r instanceof Blob ? r : r?.data;
if (!(blob instanceof Blob)) throw new Error('invalid blob'); if (!(blob instanceof Blob)) throw new Error('invalid blob');
const name = decodeURIComponent(d.fileUrl.split('/').pop() || `${d.label}.${d.nodeType === 'html' ? 'html' : 'png'}`); const fileName = d.fileUrl.split('/').pop() || '';
const fileExt = fileName.split('.').pop()?.toLowerCase() || 'html';
const name = decodeURIComponent(fileName || `${d.label}.${fileExt}`);
const u = URL.createObjectURL(blob); const u = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = u; a.href = u;
@@ -2268,7 +2334,7 @@ onBeforeUnmount(() => {
.creation-page { .creation-page {
height: calc(100vh - 100px); height: calc(100vh - 100px);
display: grid; display: grid;
grid-template-columns: 320px minmax(0, 1fr) 280px; grid-template-columns: 320px minmax(0, 1fr) 340px;
gap: 14px; gap: 14px;
padding: 14px; padding: 14px;
background: #f6f8fb; background: #f6f8fb;