import { nextTick, type Reactive, type Ref } from 'vue' import { ElMessage } from 'element-plus' import type { ExternalDataSource, ProcessType, StepId, StructuredProcessOptions, UnstructuredProcessOptions, } from './types' export const DATA_PROCESS_DRAFT_STORAGE_KEY = 'yg-data-process-create-draft' export const DATA_PROCESS_DRAFT_SCHEMA_VERSION = 6 interface DraftSnapshot { schemaVersion?: number currentStepId?: StepId task?: { name?: string; description?: string } processType?: ProcessType structuredOptions?: Partial unstructuredOptions?: Partial externalSource?: Partial } interface DraftBindings { currentStepId: Readonly> task: Reactive<{ name: string; description: string }> processType: Ref structuredOptions: Ref unstructuredOptions: Ref externalSource: Reactive restoringDraft: Ref dirty: Ref goToStep: (stepId: StepId) => void } function sanitizeExternalSource(source: Partial) { return { type: typeof source.type === 'string' ? source.type : 'mysql', url: typeof source.url === 'string' ? source.url : '', authMode: typeof source.authMode === 'string' ? source.authMode : 'none', username: typeof source.username === 'string' ? source.username : '', limit: Number.isFinite(source.limit) ? Number(source.limit) : 1000, } } function isDraftSnapshot(value: unknown): value is DraftSnapshot { return Boolean(value && typeof value === 'object') } /** * 只持久化可重建的配置。密码、令牌、文件正文、预览与结果都不进入 localStorage。 */ export function useDataProcessDraft(bindings: DraftBindings) { function draftSnapshot(): DraftSnapshot { return { schemaVersion: DATA_PROCESS_DRAFT_SCHEMA_VERSION, currentStepId: bindings.currentStepId.value, task: { ...bindings.task }, processType: bindings.processType.value, structuredOptions: { ...bindings.structuredOptions.value, preprocessOptions: [...bindings.structuredOptions.value.preprocessOptions], datasetSplit: { ...bindings.structuredOptions.value.datasetSplit }, }, unstructuredOptions: { ...bindings.unstructuredOptions.value, preprocessOptions: [...bindings.unstructuredOptions.value.preprocessOptions], datasetSplit: { ...bindings.unstructuredOptions.value.datasetSplit }, }, externalSource: sanitizeExternalSource(bindings.externalSource), } } function writeDraft(showWarning: boolean) { try { localStorage.setItem(DATA_PROCESS_DRAFT_STORAGE_KEY, JSON.stringify(draftSnapshot())) } catch { if (showWarning) ElMessage.warning('草稿保存失败,请检查浏览器存储空间') } } function persistDraft() { if (!bindings.restoringDraft.value) writeDraft(true) } function restoreDraft() { try { const raw = localStorage.getItem(DATA_PROCESS_DRAFT_STORAGE_KEY) if (!raw) return const snapshot: unknown = JSON.parse(raw) if (!isDraftSnapshot(snapshot)) return bindings.restoringDraft.value = true bindings.goToStep('create') bindings.task.name = snapshot.task?.name || '' bindings.task.description = snapshot.task?.description || '' bindings.processType.value = snapshot.processType === 'structured' || snapshot.processType === 'external' ? snapshot.processType : 'unstructured' if (snapshot.structuredOptions) { bindings.structuredOptions.value = { ...bindings.structuredOptions.value, ...snapshot.structuredOptions, preprocessOptions: Array.isArray(snapshot.structuredOptions.preprocessOptions) ? snapshot.structuredOptions.preprocessOptions : bindings.structuredOptions.value.preprocessOptions, datasetSplit: { ...bindings.structuredOptions.value.datasetSplit, ...snapshot.structuredOptions.datasetSplit, }, } } if (snapshot.unstructuredOptions) { bindings.unstructuredOptions.value = { ...bindings.unstructuredOptions.value, ...snapshot.unstructuredOptions, preprocessOptions: Array.isArray(snapshot.unstructuredOptions.preprocessOptions) ? snapshot.unstructuredOptions.preprocessOptions : bindings.unstructuredOptions.value.preprocessOptions, datasetSplit: { ...bindings.unstructuredOptions.value.datasetSplit, ...snapshot.unstructuredOptions.datasetSplit, }, } } Object.assign(bindings.externalSource, sanitizeExternalSource(snapshot.externalSource || {}), { password: '', token: '', }) bindings.dirty.value = false nextTick(() => { bindings.restoringDraft.value = false // 立即覆盖 v5 及更早草稿,清除其中可能存在的敏感值和大段正文。 writeDraft(false) }) ElMessage.info('已恢复上次的任务配置,请重新上传或拉取源数据') } catch { localStorage.removeItem(DATA_PROCESS_DRAFT_STORAGE_KEY) } } return { draftSnapshot, persistDraft, restoreDraft } }