Files
ele-HeiXiu/src/infrastructure/storage/task-store.ts
YoungestSongMo e4c9ae8fc6 feat: 三栏比例缩放 + SeedanceContent视频上传 + 本地存储基建 (0.0.22)
三栏布局
- ResizeObserver 等比例分配替代固定默认值恢复
- 修复拖拽+窗口调整竞态卡死(needsRecalcRef 延迟补算)
- containerRef.current 动态读取 DOM 替代闭包 el

Seedance2Content
- 从占位文本重构为内容编排控件(文本+图片/视频/音频上传)
- 根据 spec.image_files/video_files/audio_files 动态渲染上传区域
- 值 JSON 序列化 → 提交时转 ContentItem[](text/image_url/video_url/audio_url)

图片上传兼容视频文件
- buildAccept 根据 file_filter 区分 image/video 前缀
- createMediaBeforeUpload 动态放行 video/* MIME
- 公共模块 media-upload-utils.ts

本地存储基建
- sql.js (WASM SQLite) + safeStorage 加密持久化
- 8个IPC文件操作通道 + resolveSafe 路径穿越防护
- <userData>/heixiu-data/ 沙箱 + JWT用户隔离
- 7表DDL + 游标分页 + LRU媒体淘汰

其他
- 修复 errorMessageRef 渲染期写入警告
- 媒体预览回退直接URL(<img>无CORS限制)+ output_type类型补充
- CSP wasm-unsafe-eval + https: CDN媒体源
2026-06-18 19:22:28 +08:00

98 lines
3.3 KiB
TypeScript

// ============================================================
// infrastructure/storage/task-store.ts — 任务记录本地存储
//
// 职责:
// - 插入/更新/查询任务记录
// - 批量同步 API 数据
// - 按用户 + 状态/模型筛选
// ============================================================
import { execute, queryAll, queryOne } from './db-core';
import { getCurrentUserId } from './user-scope';
import type { TaskRecordEntry, TaskQueryFilter } from './types';
import type { TaskInfoItemResponseBody } from '@/services/modules/task';
import { logger } from '@/utils/logger';
// ---------- 序列化 ----------
function taskToRow(task: TaskInfoItemResponseBody): TaskRecordEntry {
return {
id: task.id,
user_id: getCurrentUserId(),
model_id: task.model_id,
status: task.status,
data_json: JSON.stringify(task),
created_at: typeof task.created_at === 'string' ? Date.parse(task.created_at) : Date.now(),
updated_at: Date.now(),
};
}
export function rowToTask(row: TaskRecordEntry): TaskInfoItemResponseBody {
try {
return JSON.parse(row.data_json) as TaskInfoItemResponseBody;
} catch {
return { id: row.id, model_id: row.model_id, status: row.status, created_at: new Date(row.created_at).toISOString() } as unknown as TaskInfoItemResponseBody;
}
}
// ---------- API ----------
export function upsertTask(task: TaskInfoItemResponseBody): void {
const r = taskToRow(task);
execute(
`INSERT OR REPLACE INTO task_records (id, user_id, model_id, status, data_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[r.id, r.user_id, r.model_id, r.status, r.data_json, r.created_at, r.updated_at],
);
}
export function syncTasks(tasks: TaskInfoItemResponseBody[]): number {
let n = 0;
for (const t of tasks) {
try { upsertTask(t); n++; } catch { /* skip */ }
}
logger.debug('storage', `任务同步:${n}/${tasks.length}`);
return n;
}
export function getTasks(filter: TaskQueryFilter = {}): TaskRecordEntry[] {
const uid = getCurrentUserId();
if (filter.status) {
return queryAll<TaskRecordEntry>(
`SELECT * FROM task_records WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT ?`,
[uid, filter.status, filter.limit || 100],
);
}
if (filter.model_id) {
return queryAll<TaskRecordEntry>(
`SELECT * FROM task_records WHERE user_id = ? AND model_id = ? ORDER BY created_at DESC LIMIT ?`,
[uid, filter.model_id, filter.limit || 100],
);
}
return queryAll<TaskRecordEntry>(
`SELECT * FROM task_records WHERE user_id = ? ORDER BY created_at DESC LIMIT ?`,
[uid, filter.limit || 100],
);
}
export function getTaskById(taskId: string): TaskRecordEntry | null {
return queryOne<TaskRecordEntry>(`SELECT * FROM task_records WHERE id = ?`, [taskId]);
}
export function deleteTask(taskId: string): void {
execute(`DELETE FROM task_records WHERE id = ?`, [taskId]);
}
export function getTaskCount(userId?: string): number {
const row = queryOne<{ cnt: number }>(
`SELECT COUNT(*) as cnt FROM task_records WHERE user_id = ?`,
[userId || getCurrentUserId()],
);
return row?.cnt ?? 0;
}
export function clearUserTasks(): void {
const uid = getCurrentUserId();
execute(`DELETE FROM task_records WHERE user_id = ?`, [uid]);
}