feat(data): 业务数据与系统数据分离的导入导出

数据迁移不再整体替换 trade.db —— 那条路会把账号、权限组、组织、设置(含大模型密钥)、
登录会话与审计日志一起搬到目标部署。改为按类别搬运业务数据,系统数据永远是目标部署自己那份。

- backend/lib/dataTransfer:表目录(30 张业务表,依赖顺序)、gzip JSONL 数据包(每表 sha256)、导入内核
- 归属统一给导入人;冲突跳过不覆盖;父记录按 __refs 自然键快照补壳
- 流水表(price_history)按整行内容判重:用 (sku, changed_at) 当键实测 643 行丢 186 行
- 预览在库副本上真跑一遍导入,报告与真导同源;导入前 VACUUM INTO 备份,单事务写入
- 新权限点 data.transfer(仅超管组),导出/导入均写审计
- 前端「数据迁移」页:按类别勾选导出、上传后预览逐表明细、确认导入;移除设置里的静态占位「数据交换」
- 测试:单测 7 项(含目录外键顺序、BLOB、重复导入、包被改坏)、API 端到端 1 项(两个真实实例互传)
- 用真实库副本彩排:3854 行 0 失败,29 张业务表行数全部一致,包 1.22 MB
This commit is contained in:
Drsakura
2026-10-23 13:46:26 +08:00
parent 1ffcc1ea07
commit 1a759ef37d
17 changed files with 2516 additions and 19 deletions
+5 -15
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef, Suspense } from 'react'
import axios from 'axios'
import { MoonStarsIcon, SunIcon, TuneIcon, CheckIcon, CloseIcon, ExchangeIcon, SparkIcon, RefreshIcon, BuildingIcon } from './Icons'
import { MoonStarsIcon, SunIcon, TuneIcon, CheckIcon, CloseIcon, SparkIcon, RefreshIcon, BuildingIcon } from './Icons'
import { useAuth, hasPermission } from '../auth'
import { useBranding } from '../branding'
@@ -10,11 +10,12 @@ const AIConfigSettings = React.lazy(() => import('./AIConfigSettings'))
const OrgBrandingSettings = React.lazy(() => import('./OrgBrandingSettings'))
/* 设置分类。带 permission 的项只对有该权限点的人显示 ——
智能配置里有接口地址与密钥,组织品牌是系统管理动作,都不该摆在业务员眼前。 */
智能配置里有接口地址与密钥,组织品牌是系统管理动作,都不该摆在业务员眼前。
数据导入导出已从设置里移出,做成独立页面「数据迁移」(侧栏 · 系统管理),
因为那里要放类别勾选和逐表明细,塞进这个 1000px 的模态框里看不清。 */
const CATEGORIES = [
{ key: 'appearance', label: '外观与主题', icon: TuneIcon },
{ key: 'organization', label: '组织与品牌', icon: BuildingIcon, permission: 'branding.manage' },
{ key: 'import-export', label: '数据交换', icon: ExchangeIcon },
{ key: 'ai-config', label: '智能配置', icon: SparkIcon, permission: 'ai.manage' },
{ key: 'system-update', label: '系统与更新', icon: RefreshIcon }
]
@@ -89,7 +90,6 @@ function Settings({ isOpen, onClose, theme, onThemeChange, reducedMotion, onRedu
<Suspense fallback={<div className="settings-section"><p className="settings-description">正在载入…</p></div>}>
{current === 'appearance' && <AppearanceSettings theme={theme} onThemeChange={onThemeChange} reducedMotion={reducedMotion} onReducedMotionChange={onReducedMotionChange} />}
{current === 'organization' && <OrgBrandingSettings />}
{current === 'import-export' && <ImportExportSettings />}
{current === 'ai-config' && <AIConfigSettings />}
{current === 'system-update' && <SystemUpdateSettings />}
</Suspense>
@@ -128,17 +128,7 @@ function AppearanceSettings({ theme, onThemeChange, reducedMotion, onReducedMoti
)
}
function ImportExportSettings() {
return (
<div className="settings-section">
<h3>导入 / 导出</h3>
<p className="settings-description">管理业务数据的导入、导出与本地备份。</p>
<div className="settings-group"><h4>导出数据</h4><div className="settings-actions"><button className="btn btn-secondary">导出供应商数据</button><button className="btn btn-secondary">导出产品数据</button><button className="btn btn-secondary">导出客户数据</button><button className="btn btn-secondary">导出订单数据</button></div></div>
<div className="settings-group"><h4>导入数据</h4><div className="settings-actions"><label className="btn btn-secondary file-label">导入供应商数据<input type="file" accept=".csv,.xlsx" /></label><label className="btn btn-secondary file-label">导入产品数据<input type="file" accept=".csv,.xlsx" /></label><label className="btn btn-secondary file-label">导入客户数据<input type="file" accept=".csv,.xlsx" /></label></div><p className="help-text">支持 CSV 和 Excel 格式文件</p></div>
<div className="settings-group"><h4>备份与恢复</h4><div className="settings-actions"><button className="btn btn-primary">创建完整备份</button><label className="btn btn-secondary file-label">恢复备份<input type="file" accept=".db,.backup" /></label></div><p className="help-text">建议定期备份数据库</p></div>
</div>
)
}
/* 数据导入导出已移出设置:见 pages/DataTransfer.jsx(侧栏 · 系统管理 · 数据迁移) */
/* 智能配置已拆到 ./AIConfigSettings(多模型接入 + 用途分配)与 ./OrgBrandingSettings(组织与品牌) */