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媒体源
This commit is contained in:
2026-06-18 19:22:28 +08:00
parent e1c8eff946
commit e4c9ae8fc6
42 changed files with 2640 additions and 126 deletions

View File

@@ -0,0 +1,112 @@
// ============================================================
// DataManageSetting — 本地数据管理设置区块
//
// 功能:
// - 展示本地缓存统计(任务/订单/媒体数量)
// - [加载历史数据]各拉首批数据100 条任务、100 条充值订单、100 条 VIP 订单)
// - [清理缓存]:清空当前用户本地数据
// - [保存到磁盘]:手动触发加密落盘
//
// "下一页"逻辑在对应列表页组件中:
// - TaskHistory → 滚动到最后一页本地数据时调 fetchMoreTasks()
// - OrdersPage → 滚动到最后一页本地数据时调 fetchMoreOrders()
// ============================================================
import { useState, useCallback, useEffect } from 'react';
import { Card, Button, Typography, App, Divider } from 'antd';
import { CloudDownloadOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons';
import {
initStorage, getStorageStats, clearUserStorage, saveDatabase,
} from '@/infrastructure/storage';
import { fetchAndSyncTasks, fetchAndSyncRechargeOrders, fetchAndSyncVipOrders } from './data-sync';
const { Text } = Typography;
// ---------- 组件 ----------
export function DataManageSetting() {
const { message } = App.useApp();
const [loading, setLoading] = useState(false);
const [stats, setStats] = useState(() => getStorageStats());
const refreshStats = useCallback(() => setStats(getStorageStats()), []);
// 一键加载首批数据
const loadAll = useCallback(async () => {
setLoading(true);
try {
await Promise.allSettled([
fetchAndSyncTasks(),
fetchAndSyncRechargeOrders(),
fetchAndSyncVipOrders(),
]);
refreshStats();
} finally {
setLoading(false);
}
}, [refreshStats]);
// 清理缓存
const handleClear = useCallback(() => {
clearUserStorage().then(async () => {
await initStorage();
refreshStats();
message.success('本地缓存已清理');
});
}, [message, refreshStats]);
// 保存到磁盘
const handleSave = useCallback(() => {
saveDatabase().then(ok => {
message[ok ? 'success' : 'error'](ok ? '数据已保存到磁盘' : '保存失败');
});
}, [message]);
useEffect(() => { refreshStats(); }, [refreshStats]);
return (
<Card size="small" title="数据管理" className="rounded-md!">
<div className="flex flex-col gap-3">
{/* ---- 统计 ---- */}
<div className="flex flex-col gap-1">
<Text type="secondary" className="text-xs"></Text>
<div className="flex gap-4">
<Text className="text-xs">
<Text strong>{stats.taskCount}</Text>
</Text>
<Text className="text-xs">
<Text strong>{stats.orderCount}</Text>
</Text>
<Text className="text-xs">
<Text strong>{stats.mediaCount}</Text>
</Text>
</div>
</div>
<Divider style={{ margin: '4px 0' }} />
<Button
size="small"
type="primary"
icon={<CloudDownloadOutlined />}
onClick={loadAll}
loading={loading}
block
>
</Button>
<Divider style={{ margin: '4px 0' }} />
<div className="flex gap-2">
<Button size="small" icon={<ReloadOutlined />} onClick={handleSave}>
</Button>
<Button size="small" danger icon={<DeleteOutlined />} onClick={handleClear}>
</Button>
</div>
</div>
</Card>
);
}