首页工作空间适配新结构并支持加载失败重试、切换独立删除接口

- 工作空间结果树适配 createDate→flows→items 两级结构(flows 下文件平铺,兼容旧平铺结构)
- 删除接口切换:工作空间树删除改走 session 域独立接口 resultDelete(deleteWorkspaceResult),不依赖内容创作域
- 会话记录/工作空间列表加载失败:区分失败态与空态,提供重试按钮;工作空间懒加载失败后切回 tab 自动重试

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-20 14:01:30 +08:00
co-authored by Claude
parent 63ac39cd4a
commit abc5b47f72
3 changed files with 123 additions and 23 deletions
+24 -3
View File
@@ -68,16 +68,27 @@ export function deleteSessionRecord(ids: { id: string; type: string }[], request
// ===== 工作空间结果列表(/ai-agent/session/resultList=====
// 与内容创作 execution/list 同结构:data.tree(按 createDate 分组)+ data.imgAddressPrefix
export interface ExecutionItem {
id: string;
timestamp: string;
// 兼容旧平铺结构;resultList 新结构下 items 无独立 id,删除按流程记录 flow.Id
id?: string;
timestamp?: string;
content: string;
label: string;
type?: string;
}
// resultList 新两级结构:createDate → flows(流程分组)→ items(文件)
export interface ExecutionFlow {
flowName: string;
Id: string; // 流程结果记录 id(雪花)
sessionId: string;
items: ExecutionItem[];
}
export interface ExecutionTreeItem {
createDate: string;
items: ExecutionItem[];
// 新结构:日期下按流程分组;兼容旧平铺结构(无 flows 时回落 items
flows?: ExecutionFlow[];
items?: ExecutionItem[];
}
export interface ExecutionListData {
@@ -100,3 +111,13 @@ export function getWorkspaceResultList(params?: Record<string, any>, requestOpti
requestOptions,
}) as Promise<ExecutionListResponse>;
}
/** 删除工作空间结果(单个文件)——工作流域独立接口,不依赖内容创作域 */
export function deleteWorkspaceResult(data: { id: string; content: string }, requestOptions?: RequestOptions) {
return request({
url: '/ai-agent/session/resultDelete',
method: 'delete',
data,
requestOptions,
});
}
+52 -6
View File
@@ -47,12 +47,20 @@
</div>
</div>
<div v-else-if="historyFailed" class="panel-error">
<span class="panel-error-text">加载失败</span>
<button class="panel-retry-btn" @click="emit('retry-history')">重试</button>
</div>
<div v-else class="panel-empty">暂无会话记录</div>
</div>
<!-- 工作空间面板 -->
<div v-show="activeTab === 'workspace'" class="panel" v-loading="treeLoading">
<el-empty v-if="!treeLoading && treeNodes.length === 0" :image-size="48" description="暂无内容" />
<div v-if="!treeLoading && treeNodes.length === 0 && workspaceFailed" class="panel-error">
<span class="panel-error-text">加载失败</span>
<button class="panel-retry-btn" @click="emit('retry-workspace')">重试</button>
</div>
<el-empty v-else-if="!treeLoading && treeNodes.length === 0" :image-size="48" description="暂无内容" />
<div v-else class="panel-list">
<div v-for="dateNode in treeNodes" :key="dateNode.id" class="tree-date-group">
<div class="tree-date-label">{{ dateNode.label }}</div>
@@ -98,6 +106,11 @@ interface Props {
activeMenu: string;
activeHistoryId: string | null;
historyList: HistoryItem[];
// 会话记录 / 工作空间加载失败标记:失败时显示「加载失败 + 重试」错误态
historyFailed?: boolean;
workspaceFailed?: boolean;
// 工作空间是否已成功加载:懒加载成功后才不再触发;失败保持 false → 切回 tab 自动重试
workspaceLoaded?: boolean;
treeNodes: TreeNode[];
treeLoading: boolean;
}
@@ -111,18 +124,23 @@ interface Emits {
(e: 'download-node', data: TreeNode): void;
(e: 'delete-node', data: TreeNode): void;
(e: 'load-workspace'): void;
(e: 'retry-history'): void;
(e: 'retry-workspace'): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
historyFailed: false,
workspaceFailed: false,
workspaceLoaded: false,
});
const emit = defineEmits<Emits>();
const activeTab = ref<'history' | 'workspace'>('history');
// 工作空间懒加载标记:首次切换到「工作空间」tab 时才通知父组件加载结果树,避免进入页面即拉取
const workspaceLoaded = ref(false);
const handleTabChange = (tab: 'history' | 'workspace') => {
activeTab.value = tab;
if (tab === 'workspace' && !workspaceLoaded.value) {
workspaceLoaded.value = true;
// 懒加载:仅在「工作空间」tab 且尚未成功加载过时通知父组件拉取结果树,避免进入页面即请求;
// 失败时父组件不置 workspaceLoaded,切回 tab 自动重试
if (tab === 'workspace' && !props.workspaceLoaded) {
emit('load-workspace');
}
};
@@ -282,6 +300,34 @@ const handleDeleteNode = (data: TreeNode) => emit('delete-node', data);
padding: 32px 0;
}
/* 加载失败错误态:可区分「失败」与「空数据」,并提供重试入口 */
.panel-error {
padding: 32px 0;
text-align: center;
}
.panel-error-text {
font-size: 12px;
color: #94a3b8;
margin-right: 8px;
}
.panel-retry-btn {
border: 1px solid #dbeafe;
background: #eff6ff;
color: #2563eb;
font-size: 12px;
padding: 4px 14px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s;
outline: none;
&:hover {
background: #dbeafe;
}
}
/* 历史记录条目 */
.history-item {
display: flex;
+47 -14
View File
@@ -4,8 +4,11 @@
:active-menu="activeMenu"
:active-history-id="activeHistoryId"
:history-list="historyList"
:history-failed="sessionHistoryFailed"
:tree-nodes="treeNodes"
:tree-loading="treeLoading"
:workspace-failed="workspaceTreeFailed"
:workspace-loaded="workspaceTreeLoaded"
@menu-change="handleMenuChange"
@new-chat="handleCreateHistory"
@select-history="handleSelectHistory"
@@ -14,6 +17,8 @@
@download-node="downloadNode"
@delete-node="handleDeleteNode"
@load-workspace="loadWorkspaceTree"
@retry-history="loadSessionHistory"
@retry-workspace="loadWorkspaceTree"
/>
<div class="main-wrapper">
<MainContent
@@ -101,7 +106,7 @@ import {
deleteExecutionResult,
downloadToFile,
} from '/@/api/settings/creation';
import { getSessionListV2, getSessionResults, deleteSessionV2, deleteSessionRecord, getWorkspaceResultList } from '/@/api/settings/workflow/session';
import { getSessionListV2, getSessionResults, deleteSessionV2, deleteSessionRecord, getWorkspaceResultList, deleteWorkspaceResult } from '/@/api/settings/workflow/session';
import type { ExecutionTreeItem, VOSessionInfoResult } from '/@/api/settings/workflow/session';
interface HistoryItem {
@@ -149,6 +154,11 @@ const activeMenu = ref('chat');
const activeHistoryId = ref<string | null>(null);
const treeLoading = ref(false);
const treeNodes = ref<TreeNode[]>([]);
// 会话记录 / 工作空间结果树加载失败标记:失败态可区分、提供重试入口
const sessionHistoryFailed = ref(false);
const workspaceTreeFailed = ref(false);
// 工作空间已成功加载标记:懒加载成功后才不再触发;失败保持 false → 切回 tab 自动重试
const workspaceTreeLoaded = ref(false);
const imgAddressPrefix = ref('');
const previewDialogVisible = ref(false);
const previewUrl = ref('');
@@ -176,14 +186,32 @@ const buildTreeNodes = (tree: ExecutionTreeItem[]): TreeNode[] =>
id: 'date-' + di,
label: d.createDate,
nodeType: 'date',
children: (d.items || []).map((item, ii) => ({
id: 'item-' + di + '-' + ii,
label: item.label || '作品' + (ii + 1),
nodeType: 'title',
fileUrl: item.content,
fileType: item.type,
backendId: item.id,
})),
children: (() => {
// 新结构(createDate → flows → items):flows 下所有文件平铺到日期分组;
// 兼容旧平铺结构(createDate → items
const flows = Array.isArray(d.flows) ? d.flows : [];
if (flows.length > 0) {
return flows.flatMap((flow, fi) =>
(flow.items || []).map((item, ii) => ({
id: 'item-' + di + '-' + fi + '-' + ii,
label: item.label || '作品' + (ii + 1),
nodeType: 'title',
fileUrl: item.content,
fileType: item.type,
// 新结构 items 无独立 id,删除按流程结果记录 id(flow.Id)
backendId: flow.Id ?? item.id,
})),
);
}
return (d.items || []).map((item, ii) => ({
id: 'item-' + di + '-' + ii,
label: item.label || '作品' + (ii + 1),
nodeType: 'title',
fileUrl: item.content,
fileType: item.type,
backendId: item.id,
}));
})(),
}));
const getPreviewMode = (fileType?: string): 'image' | 'video' | 'audio' | 'iframe' => {
@@ -245,10 +273,10 @@ const handleDeleteNode = async (data: TreeNode) => {
} catch {
return;
}
// 调后端接口删除
// 调后端接口删除(工作空间结果:走 session 域独立接口)
if (data.backendId && data.fileUrl) {
try {
await deleteExecutionResult({ id: data.backendId, content: data.fileUrl });
await deleteWorkspaceResult({ id: data.backendId, content: data.fileUrl });
} catch {
// 后端已提示错误,继续本地删除
}
@@ -288,9 +316,12 @@ const loadWorkspaceTree = async () => {
const execRes = await getWorkspaceResultList();
imgAddressPrefix.value = execRes.data?.imgAddressPrefix || '';
treeNodes.value = buildTreeNodes(execRes.data?.tree || []);
workspaceTreeFailed.value = false;
// 成功后才标记已加载:失败保持 false,切回「工作空间」tab 时自动重试
workspaceTreeLoaded.value = true;
} catch {
treeNodes.value = [];
imgAddressPrefix.value = '';
// 失败保留已有树数据(首次为空则显示错误态);不置 workspaceTreeLoaded 以便下次切 tab 重试
workspaceTreeFailed.value = true;
} finally {
treeLoading.value = false;
}
@@ -325,6 +356,7 @@ const loadSessionHistory = async () => {
};
});
historyList.value = sessions;
sessionHistoryFailed.value = false;
// 为每个后端会话初始化消息存储
sessions.forEach((s) => {
if (!sessionMessages.value.has(s.id)) {
@@ -332,7 +364,8 @@ const loadSessionHistory = async () => {
}
});
} catch {
historyList.value = [];
// 失败保留已有会话数据(首次为空则显示错误态),标记失败供 UI 提供重试入口
sessionHistoryFailed.value = true;
}
};