更新数字人创作页面功能

- 在树节点中添加点击事件处理,支持工作流节点的详细信息获取和会话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
:highlight-current="true"
:expand-on-click-node="false"
@node-click="handleTreeNodeClick"
>
<template #default="{ data }">
<div class="tree-node">
<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="downloadNode(data)"> 下载 </el-button>
</div>
@@ -497,7 +498,9 @@
<div class="chat-model-selector">
<div class="chat-model-search">
<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-button type="primary" @click="handleChatModelSearch">搜索</el-button>
</div>
@@ -584,6 +587,8 @@ interface TreeNode {
nodeType: NodeType;
children?: TreeNode[];
fileUrl?: string;
flowId?: number | string;
sessionId?: string;
}
interface SelectedState {
id: string;
@@ -632,6 +637,7 @@ const userInput = ref('');
const selectedFiles = ref<File[]>([]);
const selectedCreationSkill = ref<SkillItem | null>(null);
const showCreationSkillSelector = ref(false);
const currentSessionId = ref<string | null>(null); // 当前会话的 sessionId从工作空间进入时使用
const isCreating = ref(false);
// 预览相关状态
const previewDialogVisible = ref(false);
@@ -654,13 +660,14 @@ const chatModelPagination = reactive({
const filteredChatModels = computed(() => {
return chatModelList.value;
});
// 会话ID管理存储在 sessionStorage 中
// 会话ID管理每次使用工作流时生成新的 sessionId
const getSessionId = () => {
let sessionId = sessionStorage.getItem('ai_creation_session_id');
if (!sessionId) {
sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
sessionStorage.setItem('ai_creation_session_id', sessionId);
// 如果从工作空间进入,使用当前会话的 sessionId
if (currentSessionId.value) {
return currentSessionId.value;
}
// 否则生成新的 sessionId
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
return sessionId;
};
// 格式化参数引用显示
@@ -850,18 +857,13 @@ const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] =>
id: `flow-${di}-${fi}`,
label: f.flowName || '未命名工作流',
nodeType: 'contentType',
flowId: f.flowId,
sessionId: f.sessionId, // 添加 sessionId
children: (f.items || []).map((item, ii) => ({
id: `item-${di}-${fi}-${ii}`,
label: item.label || `作品${ii + 1}`,
nodeType: 'title',
children: [
{
id: `html-${di}-${fi}-${ii}`,
label: 'HTML',
nodeType: 'html' as const,
fileUrl: item.content,
},
],
fileUrl: item.content, // 直接在作品层添加 fileUrl
})),
})),
}));
@@ -934,6 +936,7 @@ const createNewWorkflow = () => {
// 切换回画布编辑模式
isCreationMode.value = false;
currentWorkflowForCreation.value = null;
currentSessionId.value = null; // 清空会话 ID
// 清空当前编辑状态
currentEditingWorkflowId.value = null;
@@ -1006,7 +1009,7 @@ const handleChatModelSearch = () => {
// 设置对话模型
const handleSetChatModel = async () => {
if (!selectedChatModel.value) return;
settingChatModel.value = true;
try {
await updateChatModel({
@@ -1085,6 +1088,7 @@ const editWorkflow = async (workflow: WorkflowItem) => {
// 切换回画布编辑模式
isCreationMode.value = false;
currentWorkflowForCreation.value = null;
currentSessionId.value = null; // 清空会话 ID
// 等待 DOM 更新后再加载工作流
await nextTick();
@@ -1105,6 +1109,7 @@ const editWorkflow = async (workflow: WorkflowItem) => {
const backToCanvas = async () => {
isCreationMode.value = false;
currentWorkflowForCreation.value = null;
currentSessionId.value = null; // 清空会话 ID
// 等待 DOM 更新后重新渲染画布
await nextTick();
@@ -1172,9 +1177,11 @@ const sendMessage = async () => {
ElMessageBox.alert('请先设置对话模型后再进行创作', '提示', {
confirmButtonText: '去设置',
type: 'warning',
}).then(() => {
showChatModelSelector.value = true;
}).catch(() => {});
})
.then(() => {
showChatModelSelector.value = true;
})
.catch(() => {});
return;
}
} catch (error) {
@@ -1281,9 +1288,66 @@ const getFieldClass = (type: string) => {
if (type === 'number' || type === 'switch') return 'form-item-small';
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) => {
if (d.nodeType !== 'html' && d.nodeType !== 'image') return;
if (d.nodeType !== 'html' && d.nodeType !== 'image' && d.nodeType !== 'title') return;
const url = buildAssetUrl(d.fileUrl);
if (!url) return ElMessage.warning('当前节点没有可用预览地址');
previewUrl.value = url;
@@ -1291,14 +1355,16 @@ const previewNode = (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('当前节点没有可下载地址');
try {
// 下载失败时希望展示更贴近页面语义的提示,因此改为 page 模式。
const r = await downloadToFile({ fileURL: d.fileUrl });
const blob = r instanceof Blob ? r : r?.data;
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 a = document.createElement('a');
a.href = u;
@@ -2268,7 +2334,7 @@ onBeforeUnmount(() => {
.creation-page {
height: calc(100vh - 100px);
display: grid;
grid-template-columns: 320px minmax(0, 1fr) 280px;
grid-template-columns: 320px minmax(0, 1fr) 340px;
gap: 14px;
padding: 14px;
background: #f6f8fb;