/** * 解析结果预览裁剪(Task 88)。 * * 解析接口(similar-asin/parse)返回完整解析 Vo,其中 items 可能达数千行, * 直接放入 Vue 响应式 ref 会造成大量响应式代理与内存放大。本模块把 Vo * 裁剪为预览对象:只保留页面实际使用的摘要字段(taskId、行数统计、开关、 * prompt 等),items/groups 整包剔除;可按 previewRowLimit / previewGroupLimit * 有界保留前 N 条样本行用于表格展示(limit 省略或为 0 时不保留)。 * * 纯 TS 无副作用模块:输入对象永不修改;taskId 非法时 fail-fast 抛错; * 属性读取抛错(如 Proxy getter 故障)时调用失败,不产生部分结果。 */ export interface ParsePreviewOptions { /** 保留的样本行条数(0 或不传表示不保留),不能为负数 */ previewRowLimit?: number /** 保留的样本分组条数(0 或不传表示不保留),不能为负数 */ previewGroupLimit?: number } export function toParsePreview( result: T, options: ParsePreviewOptions = {}, ) { if (typeof result !== 'object' || result == null) { throw new Error('解析结果必须是对象') } const { previewRowLimit, previewGroupLimit } = options if (previewRowLimit != null && !(previewRowLimit >= 0)) { throw new Error('previewRowLimit 不能为负数: ' + previewRowLimit) } if (previewGroupLimit != null && !(previewGroupLimit >= 0)) { throw new Error('previewGroupLimit 不能为负数: ' + previewGroupLimit) } const record = result as Record const taskId = record.taskId if (typeof taskId !== 'number' || !Number.isFinite(taskId) || taskId <= 0) { throw new Error('taskId 必须是正整数: ' + taskId) } const summary: Record = {} for (const key of Object.keys(record)) { if (key === 'items' || key === 'groups') continue summary[key] = record[key] } if (previewRowLimit != null && previewRowLimit > 0) { const rows = record.items if (Array.isArray(rows)) { summary.previewItems = rows.slice(0, previewRowLimit) } } if (previewGroupLimit != null && previewGroupLimit > 0) { const groups = record.groups if (Array.isArray(groups)) { summary.previewGroups = groups.slice(0, previewGroupLimit) } } return summary as Omit & { previewItems?: unknown[] previewGroups?: unknown[] } }