备份与恢复是系统之家一级菜单下的二级页面,提供系统数据的备份、恢复和操作记录查询功能。
Page (备份与恢复)
├── Tabs Component (选项卡容器)
│ ├── Tab Panel 1: 操作记录
│ ├── Tab Panel 2: 备份
│ └── Tab Panel 3: 恢复
布局类型: Tabs Layout (选项卡布局)
排列方向: Horizontal Tabs (水平标签)
区域划分: 3个独立的Tab Panel,通过标签切换
Tab Panel (操作记录)
├── Section: 查询区域 (Query Section)
│ ├── Container: 时间范围筛选
│ │ ├── DatePicker: 开始时间
│ │ └── DatePicker: 结束时间
│ ├── Container: 条件筛选
│ │ ├── Input: 用户名称输入框
│ │ ├── Select: 对象下拉选择
│ │ ├── Select: 操作下拉选择
│ │ └── Select: 结果下拉选择
│ └── Button: 查询按钮
└── Section: 数据展示区域 (Data Display Section)
├── Table: 操作记录表格
│ ├── Column: 时间
│ ├── Column: 用户
│ ├── Column: 操作对象
│ ├── Column: 操作
│ └── Column: 结果
└── Pagination: 分页控件
布局特点:
| 组件类型 | 实例 | 参数配置 | 说明 |
|---|---|---|---|
| DatePicker | 开始时间、结束时间 | showTime, format | 支持精确到秒的时间选择 |
| Input | 用户名称 | placeholder="请输入用户名称" | 模糊搜索用户 |
| Select | 对象、操作、结果 | allowClear, placeholder | 支持清空选择 |
| Button | 查询 | type="primary" | 触发查询操作 |
| Table | 操作记录表格 | - | 展示操作历史 |
| Pagination | 分页 | pageSize=20, showSizeChanger | 支持切换每页条数 |
| Empty | 暂无数据 | - | 空状态展示 |
Tab Panel (备份)
├── Split View Layout (左右分割布局)
│ ├── Left Panel: 选择备份文件
│ │ ├── Section Header: "选择备份文件"
│ │ ├── List: 备份项目列表
│ │ │ ├── Checkbox + Label: 文件目录
│ │ │ ├── Checkbox + Label: 协议配置
│ │ │ ├── Checkbox + Label: 平板探测器
│ │ │ ├── Checkbox + Label: 发生器
│ │ │ ├── Checkbox + Label: 机架
│ │ │ ├── Checkbox + Label: 同步盒
│ │ │ ├── Checkbox + Label: PPACS节点
│ │ │ ├── Checkbox + Label: DICOM节点
│ │ │ ├── Checkbox + Label: 用户账户
│ │ │ ├── Checkbox + Label: 四角信息
│ │ │ └── Checkbox + Label: 配置数据库
│ │ └── Button: 备份
│ └── Right Panel: 数据回显
│ ├── Section Header: "数据回显"
│ └── Status Display: 备份状态信息
│ └── Text: "备份状态:未开始"
布局特点:
| 组件类型 | 实例 | 参数配置 | 说明 |
|---|---|---|---|
| Checkbox | 11个备份项目 | defaultChecked=true | 默认全选 |
| List | 备份项目列表 | - | 垂直列表布局 |
| Button | 备份 | type="primary", size="large" | 底部固定按钮 |
| Panel | 数据回显区域 | - | 展示备份进度和结果 |
Tab Panel (恢复)
├── Split View Layout (左右分割布局)
│ ├── Left Panel: 选择恢复文件
│ │ ├── Section: 选择文件包
│ │ │ ├── Section Header: "选择文件包"
│ │ │ ├── Input: 文件路径输入框
│ │ │ └── Button: 选择路径
│ │ ├── Divider: 分隔线
│ │ ├── Section: 选择恢复文件
│ │ │ ├── Section Header: "选择恢复文件"
│ │ │ └── List: 恢复项目列表
│ │ │ ├── Checkbox + Label: 文件目录
│ │ │ ├── Checkbox + Label: 协议配置
│ │ │ ├── Checkbox + Label: 平板探测器
│ │ │ ├── Checkbox + Label: 发生器
│ │ │ ├── Checkbox + Label: 机架
│ │ │ ├── Checkbox + Label: 同步盒
│ │ │ ├── Checkbox + Label: PPACS节点
│ │ │ ├── Checkbox + Label: DICOM节点
│ │ │ ├── Checkbox + Label: 用户账户
│ │ │ ├── Checkbox + Label: 四角信息
│ │ │ └── Checkbox + Label: 配置数据库
│ │ └── Button: 恢复
│ └── Right Panel: 数据回显
│ ├── Section Header: "数据回显"
│ └── Status Display: 恢复状态信息
│ └── Text: "恢复状态:未开始"
布局特点:
| 组件类型 | 实例 | 参数配置 | 说明 |
|---|---|---|---|
| Input | 文件路径 | placeholder="请选择文件包", readOnly | 只读输入框 |
| Button | 选择路径 | - | 打开文件选择对话框 |
| Checkbox | 11个恢复项目 | defaultChecked=false | 默认不选中 |
| List | 恢复项目列表 | - | 垂直列表布局 |
| Button | 恢复 | type="primary", size="large", disabled | 初始禁用状态 |
| Panel | 数据回显区域 | - | 展示恢复进度和结果 |
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
建议创建 backupRecoverySlice 来管理备份与恢复的状态。
// src/store/slices/backupRecoverySlice.ts
interface BackupRecoveryState {
// 当前激活的Tab
activeTab: 'backup' | 'recovery' | 'logs';
// 操作记录
logs: {
list: OperationLog[];
total: number;
loading: boolean;
filters: {
startTime: string;
endTime: string;
username: string;
object: string;
operation: string;
result: string;
};
pagination: {
current: number;
pageSize: number;
};
};
// 备份
backup: {
selectedItems: string[]; // 选中的备份项目ID
status: 'idle' | 'preparing' | 'backing-up' | 'success' | 'failed';
progress: {
current: number;
total: number;
currentItem: string;
};
result: {
success: boolean;
filePath?: string;
errorMessage?: string;
};
};
// 恢复
recovery: {
backupFilePath: string; // 选择的备份文件路径
availableItems: string[]; // 备份文件中可用的项目
selectedItems: string[]; // 选中要恢复的项目
status: 'idle' | 'parsing' | 'restoring' | 'success' | 'failed';
progress: {
current: number;
total: number;
currentItem: string;
itemResults: Array<{
item: string;
success: boolean;
error?: string;
}>;
};
result: {
success: boolean;
needRestart: boolean;
errorMessage?: string;
};
};
}
// 操作记录相关
- setLogsFilters: 设置筛选条件
- fetchLogs: 获取操作记录列表
- setLogsPagination: 设置分页参数
// 备份相关
- setBackupItems: 设置选中的备份项目
- startBackup: 开始备份
- updateBackupProgress: 更新备份进度
- backupComplete: 备份完成
- backupFailed: 备份失败
- resetBackup: 重置备份状态
// 恢复相关
- selectBackupFile: 选择备份文件
- parseBackupFile: 解析备份文件
- setRecoveryItems: 设置选中的恢复项目
- startRecovery: 开始恢复
- updateRecoveryProgress: 更新恢复进度
- recoveryComplete: 恢复完成
- recoveryFailed: 恢复失败
- resetRecovery: 重置恢复状态
// GET /api/system/operation-logs
interface FetchLogsParams {
startTime?: string;
endTime?: string;
username?: string;
object?: string;
operation?: string;
result?: string;
page: number;
pageSize: number;
}
interface FetchLogsResponse {
data: OperationLog[];
total: number;
}
// POST /api/system/backup
interface BackupRequest {
items: string[]; // 要备份的项目ID列表
}
interface BackupResponse {
success: boolean;
filePath?: string;
errorMessage?: string;
}
// WebSocket: /ws/backup-progress
// 实时推送备份进度
interface BackupProgressMessage {
current: number;
total: number;
currentItem: string;
status: 'backing-up' | 'success' | 'failed';
}
// POST /api/system/parse-backup
interface ParseBackupRequest {
filePath: string;
}
interface ParseBackupResponse {
success: boolean;
availableItems: string[];
errorMessage?: string;
}
// POST /api/system/recovery
interface RecoveryRequest {
filePath: string;
items: string[]; // 要恢复的项目ID列表
}
interface RecoveryResponse {
success: boolean;
needRestart: boolean;
errorMessage?: string;
}
// WebSocket: /ws/recovery-progress
// 实时推送恢复进度
interface RecoveryProgressMessage {
current: number;
total: number;
currentItem: string;
status: 'restoring' | 'success' | 'failed';
itemResults: Array<{
item: string;
success: boolean;
error?: string;
}>;
}
src/pages/system/BackupRecovery/
├── index.tsx // 主页面组件
├── OperationLogs.tsx // 操作记录Tab
├── Backup.tsx // 备份Tab
├── Recovery.tsx // 恢复Tab
└── components/
├── BackupItemList.tsx // 备份项目列表组件
├── ProgressPanel.tsx // 进度显示面板组件
└── LogsTable.tsx // 操作记录表格组件
// src/pages/system/BackupRecovery/index.tsx
const BackupRecovery: React.FC = () => {
const dispatch = useDispatch();
const { activeTab } = useSelector((state) => state.backupRecovery);
const handleTabChange = (key: string) => {
dispatch(setActiveTab(key));
};
return (
<div className="backup-recovery-page">
<Tabs
activeKey={activeTab}
onChange={handleTabChange}
items={[
{
key: 'logs',
label: '操作记录',
children: <OperationLogs />
},
{
key: 'backup',
label: '备份',
children: <Backup />
},
{
key: 'recovery',
label: '恢复',
children: <Recovery />
}
]}
/>
</div>
);
};
// src/pages/system/BackupRecovery/components/BackupItemList.tsx
interface BackupItemListProps {
selectedItems: string[];
onChange: (selectedItems: string[]) => void;
disabled?: boolean;
}
const BACKUP_ITEMS = [
{ id: 'file-directory', label: '文件目录' },
{ id: 'protocol-config', label: '协议配置' },
{ id: 'detector', label: '平板探测器' },
{ id: 'generator', label: '发生器' },
{ id: 'frame', label: '机架' },
{ id: 'sync-box', label: '同步盒' },
{ id: 'pacs-node', label: 'PPACS节点' },
{ id: 'dicom-node', label: 'DICOM节点' },
{ id: 'user-account', label: '用户账户' },
{ id: 'corner-info', label: '四角信息' },
{ id: 'config-db', label: '配置数据库' },
];
const BackupItemList: React.FC<BackupItemListProps> = ({
selectedItems,
onChange,
disabled = false
}) => {
return (
<Checkbox.Group
value={selectedItems}
onChange={onChange}
disabled={disabled}
>
<Space direction="vertical" style={{ width: '100%' }}>
{BACKUP_ITEMS.map(item => (
<Checkbox key={item.id} value={item.id}>
{item.label}
</Checkbox>
))}
</Space>
</Checkbox.Group>
);
};
// src/pages/system/BackupRecovery/components/ProgressPanel.tsx
interface ProgressPanelProps {
status: 'idle' | 'preparing' | 'backing-up' | 'restoring' | 'success' | 'failed';
progress?: {
current: number;
total: number;
currentItem: string;
};
result?: {
success: boolean;
message?: string;
};
}
const ProgressPanel: React.FC<ProgressPanelProps> = ({
status,
progress,
result
}) => {
const getStatusText = () => {
switch (status) {
case 'idle': return '未开始';
case 'preparing': return '准备中...';
case 'backing-up': return '备份中...';
case 'restoring': return '恢复中...';
case 'success': return '完成';
case 'failed': return '失败';
default: return '未知状态';
}
};
return (
<div className="progress-panel">
<div className="status-header">
<Text strong>
{status.includes('backing') ? '备份' : '恢复'}状态:
{getStatusText()}
</Text>
</div>
{progress && (
<div className="progress-content">
<Progress
percent={Math.round((progress.current / progress.total) * 100)}
status={status === 'failed' ? 'exception' : 'active'}
/>
<Text type="secondary">
正在处理:{progress.currentItem} ({progress.current}/{progress.total})
</Text>
</div>
)}
{result && (
<Alert
type={result.success ? 'success' : 'error'}
message={result.message}
showIcon
/>
)}
</div>
);
};
1. 用户选择备份项目
├─> 默认全选所有项目
└─> 用户可以取消选择某些项目
2. 用户点击"备份"按钮
├─> 验证是否选择了至少一个项目
├─> 弹出确认对话框,显示将要备份的项目
└─> 用户确认后开始备份
3. 执行备份
├─> dispatch(startBackup(selectedItems))
├─> 调用 API: POST /api/system/backup
├─> 建立 WebSocket 连接监听进度
└─> 实时更新进度面板
4. 备份完成
├─> 显示备份成功消息和文件路径
├─> 记录操作日志
└─> 重置备份状态(可选)
1. 用户选择备份文件
├─> 点击"选择路径"按钮
├─> 打开文件选择对话框
└─> 选择 .backup 文件
2. 解析备份文件
├─> dispatch(parseBackupFile(filePath))
├─> 调用 API: POST /api/system/parse-backup
├─> 显示备份包中可用的项目
└─> 启用恢复项目列表
3. 用户选择要恢复的项目
├─> 勾选需要恢复的项目
└─> 启用"恢复"按钮
4. 执行恢复
├─> 弹出风险确认对话框
├─> 列出将被覆盖的配置项
├─> 用户确认后开始恢复
├─> dispatch(startRecovery({ filePath, selectedItems }))
├─> 调用 API: POST /api/system/recovery
├─> 建立 WebSocket 连接监听进度
└─> 实时更新进度面板
5. 恢复完成
├─> 显示恢复结果(成功/失败项目列表)
├─> 如需重启,显示重启提示
├─> 记录操作日志
└─> 重置恢复状态
1. 页面加载
├─> 设置默认时间范围(最近7天)
├─> 自动执行一次查询
└─> 显示最近操作记录
2. 用户修改筛选条件
├─> 更新 filters state
└─> 不立即查询(等待用户点击查询按钮)
3. 用户点击"查询"按钮
├─> dispatch(fetchLogs(filters, pagination))
├─> 调用 API: GET /api/system/operation-logs
└─> 更新表格数据
4. 分页操作
├─> 用户切换页码或调整每页条数
├─> dispatch(setLogsPagination(newPagination))
└─> 重新查询数据
// src/pages/system/BackupRecovery/styles.scss
.backup-recovery-page {
height: 100%;
.ant-tabs {
height: 100%;
.ant-tabs-content {
height: calc(100% - 46px);
}
}
}
// 操作记录Tab
.operation-logs-tab {
display: flex;
flex-direction: column;
height: 100%;
gap: 16px;
.query-section {
background: #fff;
padding: 16px;
border-radius: 4px;
.filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
}
.data-section {
flex: 1;
background: #fff;
padding: 16px;
border-radius: 4px;
overflow: hidden;
display: flex;
flex-direction: column;
.ant-table-wrapper {
flex: 1;
}
}
}
// 备份/恢复Tab(左右分割布局)
.backup-tab,
.recovery-tab {
display: flex;
height: 100%;
gap: 16px;
.left-panel {
width: 480px;
background: #fff;
padding: 16px;
border-radius: 4px;
display: flex;
flex-direction: column;
.section-header {
font-size: 16px;
font-weight: 500;
margin-bottom: 16px;
}
.backup-items-list {
flex: 1;
overflow-y: auto;
margin-bottom: 16px;
}
.action-button {
width: 100%;
height: 48px;
}
}
.right-panel {
flex: 1;
background: #fff;
padding: 16px;
border-radius: 4px;
}
}
// 恢复Tab特殊样式
.recovery-tab {
.file-selection {
margin-bottom: 16px;
.ant-input-group {
display: flex;
gap: 8px;
}
}
.ant-divider {
margin: 16px 0;
}
}
// 进度面板
.progress-panel {
.status-header {
margin-bottom: 16px;
}
.progress-content {
margin: 16px 0;
.ant-progress {
margin-bottom: 8px;
}
}
}
// 备份错误处理
- 磁盘空间不足:提示用户清理磁盘空间或选择其他位置
- 权限不足:提示用户以管理员身份运行
- 部分项目备份失败:显示失败项目列表,询问是否继续
- 备份文件损坏:提示用户重新备份
// 恢复错误处理
- 备份文件不存在或损坏:提示用户选择有效的备份文件
- 版本不兼容:提示备份文件版本与当前系统版本不匹配
- 依赖项缺失:自动选择缺失的依赖项或提示用户
- 恢复失败回滚:自动恢复到备份前状态
// 操作记录错误处理
- 查询超时:提示用户缩小查询范围
- 无权限:提示用户权限不足
- 网络错误:提供重试选项
1. 操作记录表格虚拟滚动
- 使用 react-window 或 rc-virtual-list
- 仅渲染可见行,提升大数据集性能
2. 备份/恢复进度更新
- WebSocket 推送间隔不少于 500ms
- 避免过于频繁的状态更新
3. 文件选择对话框
- 使用 Electron 的 dialog.showOpenDialog
- 限制文件类型为 .backup
4. 缓存策略
- 操作记录查询结果缓存 5 分钟
- 备份项目列表缓存,避免重复请求
5. 懒加载
- 操作记录Tab首次打开时才加载数据
- 减少初始渲染负担
备份与恢复页面是系统管理的核心功能,设计时重点考虑:
通过合理的状态管理、API 设计和组件架构,可以实现一个功能完善、交互友好的备份与恢复系统。