# 系统之家-日志清理 - 布局与组件结构描述 ## 页面概览 **日志清理**是系统之家一级菜单下的二级页面,提供系统日志的自动清理和手动清理功能,帮助管理员维护系统日志,防止日志文件过度占用磁盘空间。 --- ## 1. 布局与组件结构 ### 1.1 整体布局结构 ``` Page (日志清理) ├── Tabs Component (选项卡容器) │ ├── Tab Panel 1: 自动清理 │ └── Tab Panel 2: 手动清理 └── Footer: 操作按钮区域 ├── Button: 取消 └── Button: 保存/开始清理 ``` **布局类型**: Tabs Layout (选项卡布局) **排列方向**: Horizontal Tabs (水平标签) **区域划分**: 2个独立的Tab Panel,通过标签切换 **固定底部**: Footer区域固定在页面底部,包含操作按钮 --- ### 1.2 Tab 1: 手动清理页面 #### 1.2.1 层级结构 ``` Tab Panel (手动清理) ├── Section: 状态提示区域 │ └── Alert/Text: 上一次清理时间提示 (红色高亮显示) ├── Section: 快捷选择区域 │ ├── Section Header: "快捷选择" │ └── Radio.Group: 时间范围选项 │ ├── Radio: 全部日志 │ ├── Radio: 保留近3天 │ └── Radio: 保留近10天 ├── Section: 选择时间范围区域 │ ├── Section Header: "选择时间范围" │ └── Container: 时间选择器组 │ ├── Form.Item: 起始时间 │ │ ├── Label: "起始时间" │ │ └── DatePicker: 开始日期选择器 │ └── Form.Item: 截止时间 │ ├── Label: "截止时间" │ └── DatePicker: 结束日期选择器 └── Footer: 操作按钮区域 ├── Button: 取消 └── Button: 开始清理 (Primary) ``` **布局特点**: - **Vertical Stack Layout**: 各区域垂直堆叠排列 - **Section分组**: 功能区域通过标题分组,视觉层次清晰 - **Horizontal Form Layout**: 时间选择器水平排列 - **Fixed Footer**: 操作按钮固定在底部 - **Responsive**: 根据内容自适应高度 #### 1.2.2 组件详情 | 组件类型 | 实例 | 参数配置 | 说明 | |---------|------|---------|------| | **Alert/Text** | 上一次清理时间提示 | type="info" 或使用红色文字 | 显示最近一次清理时间,红色高亮提醒 | | **Radio.Group** | 快捷选择 | defaultValue="all" | 互斥选择,提供快捷时间范围 | | **Radio** | 全部日志 | value="all" | 选中时清理所有日志 | | **Radio** | 保留近3天 | value="recent3" | 选中时保留最近3天日志 | | **Radio** | 保留近10天 | value="recent10" | 选中时保留最近10天日志 | | **DatePicker** | 起始时间 | placeholder="请选择开始日期", format="YYYY-MM-DD" | 自定义起始日期 | | **DatePicker** | 截止时间 | placeholder="请选择结束日期", format="YYYY-MM-DD" | 自定义结束日期 | | **Button** | 取消 | - | 取消操作,关闭或返回 | | **Button** | 开始清理 | type="primary" | 执行手动清理操作 | --- ### 1.3 Tab 2: 自动清理页面 #### 1.3.1 层级结构 ``` Tab Panel (自动清理) ├── Section: 按照时间周期清理 │ ├── Section Header: "按照时间周期清理" │ ├── Form.Item: 开关控制 │ │ ├── Label: "按照时间周期清理" │ │ └── Switch: 启用/禁用时间周期清理 │ ├── Form.Item: 清理周期 │ │ ├── Label: "清理周期(天)" + Required Mark │ │ └── InputNumber: 周期天数输入 │ │ └── Suffix: 字符计数 (1/3) │ └── Form.Item: 保存周期 │ ├── Label: "保存周期(天)" + Required Mark │ └── InputNumber: 保存天数输入 │ └── Suffix: 字符计数 (2/3) ├── Divider: 分隔线 ├── Section: 按照磁盘空间清理 │ ├── Section Header: "按照磁盘空间清理" │ ├── Form.Item: 开关控制 │ │ ├── Label: "按照磁盘空间清理" │ │ └── Switch: 启用/禁用空间清理 │ ├── Form.Item: 磁盘阈值 │ │ ├── Label: "磁盘小于该值时进行清理(G)" + Required Mark │ │ └── InputNumber: 磁盘空间阈值输入 │ │ └── Suffix: 字符计数 (2/3) │ └── Form.Item: 保存周期 │ ├── Label: "保存周期(天)" + Required Mark │ └── InputNumber: 保存天数输入 │ └── Suffix: 字符计数 (1/3) └── Footer: 操作按钮区域 ├── Button: 取消 └── Button: 保存 (Primary) ``` **布局特点**: - **Vertical Stack Layout**: 两个配置区域垂直堆叠 - **Form Layout**: 每个配置项使用Form.Item统一管理 - **Horizontal Label**: 标签与控件水平排列 - **Divider分隔**: 使用分隔线区分不同清理策略 - **Conditional Display**: 开关关闭时,相关输入框可能禁用 - **Input Validation**: 输入框带字符计数,限制输入范围 #### 1.3.2 组件详情 | 组件类型 | 实例 | 参数配置 | 说明 | |---------|------|---------|------| | **Switch** | 按照时间周期清理开关 | defaultChecked=true | 启用/禁用时间周期清理功能 | | **InputNumber** | 清理周期(天) | min=1, max=999, precision=0, required | 设置自动清理的时间间隔 | | **InputNumber** | 保存周期(天) | min=1, max=999, precision=0, required | 设置日志保留天数 | | **Divider** | 分隔线 | - | 分隔不同的清理策略配置 | | **Switch** | 按照磁盘空间清理开关 | defaultChecked=true | 启用/禁用空间清理功能 | | **InputNumber** | 磁盘阈值(G) | min=1, max=999, precision=0, required | 设置触发清理的磁盘空间阈值 | | **InputNumber** | 保存周期(天) | min=1, max=999, precision=0, required | 设置日志保留天数 | | **Button** | 取消 | - | 取消修改,恢复到上次保存的值 | | **Button** | 保存 | type="primary" | 保存自动清理配置 | --- ## 2. Ant Design 组件选择理由 ### 2.1 Tabs 组件 **选择理由**: - **功能分离**: 自动清理和手动清理是两种不同的使用场景,Tabs可以清晰分离,避免界面混乱 - **空间利用**: 在有限的页面空间内组织多个功能模块,避免页面过长 - **用户习惯**: 符合用户对配置页面的常见认知,自动化配置和手动操作分开管理 - **状态隔离**: 两个Tab的表单状态相互独立,互不影响 ### 2.2 Radio.Group 组件 **选择理由**: - **互斥选择**: 快捷选择功能需要用户从预设选项中选择一个,Radio是最合适的组件 - **视觉清晰**: 单选框让用户清楚当前选择的是哪个时间范围 - **快速操作**: 预设的常用选项(全部/近3天/近10天)提供快捷操作,提升效率 - **与自定义互斥**: 选择快捷选项后,自定义时间范围可以禁用或清空 ### 2.3 DatePicker 组件 **选择理由**: - **精确控制**: 自定义时间范围需要精确到日期,DatePicker提供日历选择界面 - **格式统一**: 自动格式化日期显示,保证日期格式的一致性 - **范围验证**: 可以设置起始时间不能晚于截止时间的验证规则 - **用户友好**: 日历界面比直接输入日期字符串更直观易用 ### 2.4 Switch 组件 **选择理由**: - **开关控制**: 自动清理功能的启用/禁用是典型的开关场景 - **视觉反馈**: Switch的开/关状态视觉清晰,用户一眼就能看出功能是否启用 - **即时效果**: 切换时可以立即禁用或启用相关的输入框,提供即时反馈 - **节省空间**: 相比Checkbox,Switch更简洁,适合功能开关场景 ### 2.5 InputNumber 组件 **选择理由**: - **数值输入**: 清理周期、保存周期、磁盘阈值都是数值型参数 - **范围限制**: 可以设置min/max,防止用户输入无效值(如负数、过大的数) - **精度控制**: precision=0确保只能输入整数,符合天数和GB的语义 - **增减按钮**: 内置的增减按钮方便用户微调数值 - **输入验证**: 自动拒绝非数字输入,减少前端验证代码 ### 2.6 Button 组件 **选择理由**: - **操作触发**: 保存配置、开始清理等操作需要明确的触发按钮 - **视觉层级**: type="primary"区分主要操作(保存/开始清理)和次要操作(取消) - **状态反馈**: 支持loading状态,清理过程中可以显示加载动画 - **禁用控制**: 可以根据表单验证结果禁用按钮,防止提交无效数据 ### 2.7 Form 组件 **选择理由**: - **表单管理**: 自动清理Tab是典型的表单场景,Form组件提供统一的表单管理 - **验证机制**: 内置表单验证,必填项、数值范围等规则易于配置 - **布局统一**: Form.Item提供统一的标签和控件布局,视觉一致 - **数据收集**: 方便收集和提交表单数据 ### 2.8 Divider 组件 **选择理由**: - **视觉分隔**: 将"时间周期清理"和"磁盘空间清理"两个策略清晰分隔 - **层次清晰**: 帮助用户理解这是两个独立的配置模块 - **简洁美观**: 比空白间距更有视觉引导性 ### 2.9 Alert/Text 组件 **选择理由**: - **信息提示**: 显示上一次清理时间,给用户提供上下文信息 - **视觉强调**: 红色文字或Alert组件可以突出重要信息 - **非阻塞式**: 不会打断用户操作,信息自然融入页面 --- ## 3. 页面功能描述 ### 3.1 手动清理功能 #### 3.1.1 快捷选择功能 - **全部日志**: 选中后将清理所有系统日志,不保留任何历史记录 - **保留近3天**: 清理3天以前的日志,保留最近3天的日志记录 - **保留近10天**: 清理10天以前的日志,保留最近10天的日志记录 - **互斥关系**: 快捷选择与自定义时间范围互斥,选择快捷选项后,时间选择器应禁用或清空 #### 3.1.2 自定义时间范围 - **起始时间**: 用户可以选择清理的开始日期 - **截止时间**: 用户可以选择清理的结束日期 - **范围验证**: 起始时间不能晚于截止时间,系统应提示或自动调整 - **与快捷选择互斥**: 当用户选择自定义时间时,快捷选择的单选框应取消选中 #### 3.1.3 清理执行 - **确认对话框**: 点击"开始清理"前应弹出确认对话框,明确告知将清理的日志范围和大致数量 - **风险提示**: 提示用户清理操作不可恢复,建议先备份重要日志 - **进度显示**: 清理过程中显示进度条和当前清理状态 - **结果反馈**: 清理完成后显示清理的日志数量、释放的磁盘空间 - **日志记录**: 记录清理操作到操作记录中 #### 3.1.4 上一次清理时间显示 - **时间格式**: 显示格式为"YYYY.MM.DD HH:mm" - **首次使用**: 如果从未清理过,显示"尚未清理"或不显示此提示 - **数据来源**: 从系统配置或数据库中读取上次清理时间 ### 3.2 自动清理功能 #### 3.2.1 按照时间周期清理 - **功能开关**: 用户可以启用或禁用此功能 - **清理周期**: 设置每隔多少天自动执行一次清理 - 有效范围: 1-999天 - 默认值: 建议1天(每天清理一次) - 输入限制: 仅允许输入正整数 - **保存周期**: 设置保留多少天内的日志 - 有效范围: 1-999天 - 默认值: 建议10天 - 输入限制: 仅允许输入正整数 - **执行逻辑**: 系统定时任务每天检查,如果距离上次清理超过"清理周期"天数,则自动清理超过"保存周期"天数的日志 - **字符计数**: 输入框右侧显示"已输入字符数/最大字符数",提示用户输入限制 #### 3.2.2 按照磁盘空间清理 - **功能开关**: 用户可以启用或禁用此功能 - **磁盘阈值**: 设置触发清理的磁盘剩余空间阈值(单位:GB) - 有效范围: 1-999 GB - 默认值: 建议20 GB - 输入限制: 仅允许输入正整数 - **保存周期**: 设置清理时保留多少天内的日志 - 有效范围: 1-999天 - 默认值: 建议3天 - 输入限制: 仅允许输入正整数 - **执行逻辑**: 系统定时任务定期检查磁盘空间,当剩余空间小于设定阈值时,自动清理超过"保存周期"天数的日志 - **优先级**: 如果同时启用时间周期和磁盘空间清理,两个条件任一满足即触发清理 #### 3.2.3 配置保存 - **表单验证**: 保存前验证所有必填项和数值范围 - **实时生效**: 配置保存后立即生效,下次定时任务检查时使用新配置 - **配置持久化**: 配置保存到数据库或配置文件 - **取消操作**: 点击取消后,表单恢复到上次保存的值 #### 3.2.4 开关联动 - **禁用状态**: 当开关关闭时,对应的输入框应禁用(disabled),呈现灰色不可编辑状态 - **启用状态**: 当开关打开时,输入框恢复可编辑状态 - **保存验证**: 如果两个开关都关闭,保存时应提示用户至少启用一种清理策略(或允许全部禁用,取决于产品需求) --- ## 4. 功能思考与需求描述 ### 4.1 日志清理范围 #### 4.1.1 日志类型分类 系统日志可能包含多种类型,需要明确哪些日志可以被清理: - **应用日志**: 系统运行日志、错误日志、调试日志 - **操作日志**: 用户操作记录、系统配置变更记录 - **审计日志**: 安全相关的审计记录 - **访问日志**: API访问日志、文件访问日志 - **硬件日志**: 硬件设备的运行日志 **需求建议**: - 操作日志和审计日志可能有法规要求,不应随意清理,建议单独管理 - 应用日志、调试日志可以根据时间或磁盘空间清理 - 提供"日志类型选择"功能,让管理员明确选择要清理的日志类型 #### 4.1.2 日志存储位置 - **日志目录**: 明确日志文件存储的目录路径 - **多目录支持**: 不同类型的日志可能存储在不同目录 - **网络存储**: 考虑日志可能存储在网络驱动器的情况 ### 4.2 自动清理调度机制 #### 4.2.1 定时任务设计 - **调度频率**: 建议每小时检查一次是否需要清理 - **执行时间**: 建议在系统空闲时段(如凌晨)执行清理,避免影响正常使用 - **任务队列**: 如果上次清理未完成,应等待完成后再启动新的清理任务 - **失败重试**: 如果清理失败,应有重试机制,但限制重试次数 #### 4.2.2 触发条件判断 - **时间周期判断**: ``` if (启用时间周期清理 && 当前时间 - 上次清理时间 >= 清理周期) { 执行清理(保留周期内的日志); } ``` - **磁盘空间判断**: ``` if (启用磁盘空间清理 && 磁盘剩余空间 < 磁盘阈值) { 执行清理(保留周期内的日志); } ``` - **组合条件**: 两个条件是OR关系,任一满足即触发 #### 4.2.3 清理策略优化 - **渐进式清理**: 如果磁盘空间仍然不足,可以逐步减少保存周期,先清理较旧的日志 - **智能清理**: 优先清理体积较大的日志文件 - **保留机制**: 即使超过保存周期,对于错误日志、异常日志可以考虑额外保留 ### 4.3 用户体验优化 #### 4.3.1 手动清理的交互改进 - **预览功能**: 在执行清理前,显示将要清理的日志文件列表和总大小 - **部分清理**: 允许用户从预览列表中排除某些不想清理的日志 - **分批清理**: 如果日志量很大,可以分批清理,避免长时间阻塞 - **后台任务**: 清理作为后台任务执行,用户可以继续其他操作 #### 4.3.2 自动清理的通知机制 - **清理通知**: 自动清理执行后,应通知管理员清理结果 - **空间告警**: 即使启用自动清理,如果磁盘空间持续不足,应发出告警 - **失败告警**: 自动清理失败时应立即通知管理员 #### 4.3.3 配置建议 - **推荐配置**: 在界面上提供推荐的配置值,引导新用户 - **配置模板**: 提供"标准模式"、"节省空间模式"、"保留详细日志模式"等预设模板 - **智能建议**: 根据磁盘空间大小和系统使用情况,智能推荐合理的配置 ### 4.4 安全与合规考虑 #### 4.4.1 权限控制 - **操作权限**: 只有系统管理员可以执行日志清理 - **配置权限**: 普通管理员可以查看配置,但不能修改 - **审计要求**: 所有清理操作(手动和自动)都应记录到审计日志 #### 4.4.2 数据保护 - **清理前备份**: 提供选项,在清理前自动备份要删除的日志 - **回收站机制**: 清理的日志先移动到回收站,保留一段时间后再真正删除 - **加密日志**: 对于包含敏感信息的日志,清理时应安全删除(覆盖写入) #### 4.4.3 合规要求 - **保留期限**: 某些行业(如医疗、金融)对日志保留期限有法规要求 - **不可篡改**: 审计日志应保证不被清理或篡改 - **法规提醒**: 在界面上提示管理员注意法规要求 ### 4.5 性能与稳定性 #### 4.5.1 大数据量处理 - **日志文件数量**: 可能有数千个日志文件需要清理 - **文件大小**: 单个日志文件可能很大(几GB) - **清理时间**: 清理过程可能需要几分钟甚至更长时间 - **性能影响**: 清理过程应避免占用过多系统资源 **优化建议**: - 使用异步任务处理清理操作 - 批量删除文件,每批处理100个文件 - 清理过程中定期休眠,避免CPU占用过高 - 提供"暂停"和"恢复"功能 #### 4.5.2 错误处理 - **文件被占用**: 某些日志文件可能正在被写入,无法删除 - **权限不足**: 某些日志文件可能由系统进程创建,权限不足 - **磁盘错误**: 磁盘故障可能导致删除失败 - **部分成功**: 部分文件清理成功,部分失败的情况 **处理策略**: - 记录失败的文件列表,下次清理时重试 - 对于被占用的文件,先关闭日志写入,清理后再恢复 - 提供"强制清理"选项,尝试强制删除被占用的文件 - 清理失败时不回滚已清理的文件 --- ## 5. 后续实现建议 ### 5.1 状态管理 (Redux Slice) 建议创建 `logCleanupSlice` 来管理日志清理的状态。 #### 5.1.1 Slice 结构 ```typescript // src/store/slices/logCleanupSlice.ts interface LogCleanupState { // 当前激活的Tab activeTab: 'auto' | 'manual'; // 手动清理 manual: { lastCleanupTime: string | null; // 上次清理时间 quickSelect: 'all' | 'recent3' | 'recent10' | 'custom'; dateRange: { startDate: string | null; endDate: string | null; }; status: 'idle' | 'confirming' | 'cleaning' | 'success' | 'failed'; progress: { current: number; total: number; currentFile: string; } | null; result: { filesDeleted: number; spaceFreed: number; // 单位:字节 errorFiles: string[]; } | null; }; // 自动清理 auto: { config: { timeBased: { enabled: boolean; cleanupInterval: number; // 清理周期(天) retentionPeriod: number; // 保存周期(天) }; spaceBased: { enabled: boolean; threshold: number; // 磁盘阈值(GB) retentionPeriod: number; // 保存周期(天) }; }; originalConfig: any; // 用于取消时恢复 hasChanges: boolean; saving: boolean; }; } ``` #### 5.1.2 Actions ```typescript // Tab切换 - setActiveTab: 设置当前激活的Tab // 手动清理相关 - setQuickSelect: 设置快捷选择选项 - setDateRange: 设置自定义时间范围 - startManualCleanup: 开始手动清理 - updateCleanupProgress: 更新清理进度 - manualCleanupComplete: 手动清理完成 - manualCleanupFailed: 手动清理失败 - resetManualCleanup: 重置手动清理状态 // 自动清理相关 - fetchAutoCleanupConfig: 获取自动清理配置 - updateAutoCleanupConfig: 更新自动清理配置(本地状态) - toggleTimeBased: 切换时间周期清理开关 - toggleSpaceBased: 切换磁盘空间清理开关 - saveAutoCleanupConfig: 保存自动清理配置到服务器 - cancelAutoCleanupConfig: 取消修改,恢复原始配置 - resetAutoCleanupConfig: 重置配置 ``` ### 5.2 API 接口设计 #### 5.2.1 获取上次清理时间 ```typescript // GET /api/system/log-cleanup/last-time interface GetLastCleanupTimeResponse { lastCleanupTime: string | null; // ISO 8601格式 } ``` #### 5.2.2 手动清理接口 ```typescript // POST /api/system/log-cleanup/manual interface ManualCleanupRequest { mode: 'all' | 'recent3' | 'recent10' | 'custom'; dateRange?: { startDate: string; // YYYY-MM-DD endDate: string; // YYYY-MM-DD }; } interface ManualCleanupResponse { success: boolean; taskId: string; // 用于WebSocket订阅进度 errorMessage?: string; } // WebSocket: /ws/log-cleanup/progress/{taskId} // 实时推送清理进度 interface CleanupProgressMessage { current: number; total: number; currentFile: string; status: 'cleaning' | 'success' | 'failed'; result?: { filesDeleted: number; spaceFreed: number; errorFiles: string[]; }; } ``` #### 5.2.3 自动清理配置接口 ```typescript // GET /api/system/log-cleanup/auto-config interface GetAutoCleanupConfigResponse { timeBased: { enabled: boolean; cleanupInterval: number; retentionPeriod: number; }; spaceBased: { enabled: boolean; threshold: number; retentionPeriod: number; }; } // POST /api/system/log-cleanup/auto-config interface SaveAutoCleanupConfigRequest { timeBased: { enabled: boolean; cleanupInterval: number; retentionPeriod: number; }; spaceBased: { enabled: boolean; threshold: number; retentionPeriod: number; }; } interface SaveAutoCleanupConfigResponse { success: boolean; errorMessage?: string; } ``` ### 5.3 组件实现建议 #### 5.3.1 页面组件结构 ``` src/pages/system/LogCleanup/ ├── index.tsx // 主页面组件 ├── ManualCleanup.tsx // 手动清理Tab ├── AutoCleanup.tsx // 自动清理Tab └── components/ ├── QuickSelectRadio.tsx // 快捷选择单选组件 ├── DateRangeSelector.tsx // 时间范围选择组件 ├── CleanupProgressModal.tsx // 清理进度弹窗组件 └── AutoConfigForm.tsx // 自动清理配置表单组件 ``` #### 5.3.2 主页面组件 ```typescript // src/pages/system/LogCleanup/index.tsx import { useState } from 'react'; import { Tabs } from 'antd'; import ManualCleanup from './ManualCleanup'; import AutoCleanup from './AutoCleanup'; const LogCleanup: React.FC = () => { const [activeTab, setActiveTab] = useState<'manual' | 'auto'>('manual'); return (
setActiveTab(key as 'manual' | 'auto')} items={[ { key: 'manual', label: '手动清理', children: }, { key: 'auto', label: '自动清理', children: } ]} />
); }; export default LogCleanup; ``` #### 5.3.3 手动清理组件 ```typescript // src/pages/system/LogCleanup/ManualCleanup.tsx import { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Radio, DatePicker, Button, Space, Typography, Modal } from 'antd'; import { ExclamationCircleOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import CleanupProgressModal from './components/CleanupProgressModal'; const { Text } = Typography; const ManualCleanup: React.FC = () => { const dispatch = useDispatch(); const { lastCleanupTime, quickSelect, dateRange, status } = useSelector( (state) => state.logCleanup.manual ); const [localQuickSelect, setLocalQuickSelect] = useState(quickSelect); const [localDateRange, setLocalDateRange] = useState(dateRange); const handleQuickSelectChange = (value: string) => { setLocalQuickSelect(value); if (value !== 'custom') { setLocalDateRange({ startDate: null, endDate: null }); } }; const handleDateChange = (dates: any, field: 'startDate' | 'endDate') => { setLocalQuickSelect('custom'); setLocalDateRange({ ...localDateRange, [field]: dates ? dates.format('YYYY-MM-DD') : null }); }; const handleStartCleanup = () => { Modal.confirm({ title: '确认清理', icon: , content: '日志清理操作不可恢复,确定要继续吗?', onOk: () => { dispatch(startManualCleanup({ mode: localQuickSelect, dateRange: localQuickSelect === 'custom' ? localDateRange : undefined })); } }); }; const isCleanupDisabled = () => { if (localQuickSelect === 'custom') { return !localDateRange.startDate || !localDateRange.endDate; } return false; }; return (
{/* 上次清理时间提示 */} {lastCleanupTime && (
提示: 上一次清理时间 {dayjs(lastCleanupTime).format('YYYY.MM.DD HH:mm')}
)} {/* 快捷选择 */}
快捷选择
handleQuickSelectChange(e.target.value)} > 全部日志 保留近3天 保留近10天
{/* 选择时间范围 */}
选择时间范围
handleDateChange(date, 'startDate')} placeholder="请选择开始日期" format="YYYY-MM-DD" disabled={localQuickSelect !== 'custom'} />
handleDateChange(date, 'endDate')} placeholder="请选择结束日期" format="YYYY-MM-DD" disabled={localQuickSelect !== 'custom'} disabledDate={(current) => { if (!localDateRange.startDate) return false; return current && current < dayjs(localDateRange.startDate); }} />
{/* 操作按钮 */}
{/* 清理进度弹窗 */} {status !== 'idle' && }
); }; export default ManualCleanup; ``` #### 5.3.4 自动清理配置组件 ```typescript // src/pages/system/LogCleanup/AutoCleanup.tsx import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Form, Switch, InputNumber, Button, Divider, message } from 'antd'; const AutoCleanup: React.FC = () => { const dispatch = useDispatch(); const { config, hasChanges, saving } = useSelector( (state) => state.logCleanup.auto ); const [form] = Form.useForm(); useEffect(() => { dispatch(fetchAutoCleanupConfig()); }, [dispatch]); useEffect(() => { form.setFieldsValue(config); }, [config, form]); const handleSave = async () => { try { const values = await form.validateFields(); dispatch(saveAutoCleanupConfig(values)); } catch (error) { message.error('请检查表单输入'); } }; const handleCancel = () => { form.resetFields(); dispatch(cancelAutoCleanupConfig()); }; const handleValuesChange = (changedValues: any, allValues: any) => { dispatch(updateAutoCleanupConfig(allValues)); }; return (
{/* 按照时间周期清理 */}
按照时间周期清理
{/* 按照磁盘空间清理 */}
按照磁盘空间清理
{/* 操作按钮 */}
); }; export default AutoCleanup; ``` #### 5.3.5 清理进度弹窗组件 ```typescript // src/pages/system/LogCleanup/components/CleanupProgressModal.tsx import { Modal, Progress, Typography, Alert } from 'antd'; import { useSelector, useDispatch } from 'react-redux'; const { Text } = Typography; const CleanupProgressModal: React.FC = () => { const dispatch = useDispatch(); const { status, progress, result } = useSelector( (state) => state.logCleanup.manual ); const handleClose = () => { dispatch(resetManualCleanup()); }; const getTitle = () => { switch (status) { case 'cleaning': return '正在清理日志...'; case 'success': return '清理完成'; case 'failed': return '清理失败'; default: return '日志清理'; } }; const formatSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`; if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; }; return ( {status === 'cleaning' && progress && (
正在清理: {progress.currentFile} ({progress.current}/{progress.total})
)} {status === 'success' && result && (

已清理 {result.filesDeleted} 个日志文件

释放磁盘空间: {formatSize(result.spaceFreed)}

{result.errorFiles.length > 0 && (

{result.errorFiles.length} 个文件清理失败

)} } showIcon /> )} {status === 'failed' && ( )}
); }; export default CleanupProgressModal; ``` ### 5.4 关键交互流程 #### 5.4.1 手动清理流程 ```typescript 1. 用户选择清理范围 ├─> 选择快捷选项(全部/近3天/近10天) └─> 或自定义时间范围 2. 用户点击"开始清理" ├─> 验证选择是否有效(自定义模式需要起止时间都填写) ├─> 弹出确认对话框 └─> 用户确认后开始清理 3. 执行清理 ├─> dispatch(startManualCleanup({ mode, dateRange })) ├─> 调用 API: POST /api/system/log-cleanup/manual ├─> 获取taskId,建立WebSocket连接 ├─> 显示进度弹窗 └─> 实时更新清理进度 4. 清理完成 ├─> 显示清理结果(文件数、释放空间) ├─> 更新上次清理时间 ├─> 记录到操作日志 └─> 用户关闭弹窗 ``` #### 5.4.2 自动清理配置流程 ```typescript 1. 页面加载 ├─> dispatch(fetchAutoCleanupConfig()) ├─> 调用 API: GET /api/system/log-cleanup/auto-config └─> 填充表单默认值 2. 用户修改配置 ├─> 切换开关/修改输入框 ├─> dispatch(updateAutoCleanupConfig(newConfig)) ├─> 设置hasChanges=true └─> 启用保存按钮 3. 用户保存配置 ├─> 验证表单 ├─> dispatch(saveAutoCleanupConfig(config)) ├─> 调用 API: POST /api/system/log-cleanup/auto-config ├─> 显示保存成功消息 └─> 设置hasChanges=false 4. 用户取消修改 ├─> dispatch(cancelAutoCleanupConfig()) ├─> form.resetFields() └─> 恢复到原始配置 5. 开关联动 ├─> 监听开关值变化 ├─> 开关关闭时禁用相关输入框 └─> 开关打开时启用相关输入框 ``` ### 5.5 样式建议 ```scss // src/pages/system/LogCleanup/styles.scss .log-cleanup-page { height: 100%; padding: 16px; .ant-tabs { height: 100%; .ant-tabs-content { height: calc(100% - 46px); } } } // 手动清理Tab .manual-cleanup-tab { display: flex; flex-direction: column; height: 100%; gap: 24px; .last-cleanup-info { padding: 12px; background: #fff7e6; border: 1px solid #ffd591; border-radius: 4px; } .section { background: #fff; padding: 16px; border-radius: 4px; .section-header { font-size: 16px; font-weight: 500; margin-bottom: 16px; color: #262626; } } .form-item { display: flex; flex-direction: column; gap: 8px; label { font-size: 14px; color: #595959; } } .footer-actions { margin-top: auto; display: flex; justify-content: flex-end; gap: 12px; padding: 16px; background: #fff; border-top: 1px solid #f0f0f0; } } // 自动清理Tab .auto-cleanup-tab { display: flex; flex-direction: column; height: 100%; .section { background: #fff; padding: 16px; border-radius: 4px; margin-bottom: 16px; .section-header { font-size: 16px; font-weight: 500; margin-bottom: 16px; color: #262626; } } .ant-divider { margin: 24px 0; } .footer-actions { margin-top: auto; display: flex; justify-content: flex-end; gap: 12px; padding: 16px; background: #fff; border-top: 1px solid #f0f0f0; } } ``` ### 5.6 WebSocket 连接管理 ```typescript // src/services/logCleanupWebSocket.ts import { store } from '@/store'; import { updateCleanupProgress, manualCleanupComplete, manualCleanupFailed } from '@/store/slices/logCleanupSlice'; class LogCleanupWebSocket { private ws: WebSocket | null = null; private reconnectAttempts = 0; private maxReconnectAttempts = 3; connect(taskId: string) { const wsUrl = `ws://localhost:8080/ws/log-cleanup/progress/${taskId}`; this.ws = new WebSocket(wsUrl); this.ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message.status === 'cleaning') { store.dispatch(updateCleanupProgress({ current: message.current, total: message.total, currentFile: message.currentFile })); } else if (message.status === 'success') { store.dispatch(manualCleanupComplete(message.result)); this.close(); } else if (message.status === 'failed') { store.dispatch(manualCleanupFailed()); this.close(); } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.reconnect(taskId); }; this.ws.onclose = () => { console.log('WebSocket closed'); }; } reconnect(taskId: string) { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; setTimeout(() => this.connect(taskId), 2000 * this.reconnectAttempts); } } close() { if (this.ws) { this.ws.close(); this.ws = null; } this.reconnectAttempts = 0; } } export default new LogCleanupWebSocket(); ``` ### 5.7 错误处理建议 ```typescript // 表单验证错误 - 自定义时间范围: 起始时间晚于截止时间 - 输入值超出范围: min=1, max=999 - 必填项未填写: 保存配置时检查 // API调用错误 - 网络错误: 显示"网络连接失败,请检查网络" - 服务器错误: 显示"服务器错误,请稍后重试" - 权限错误: 显示"权限不足,无法执行操作" // 清理操作错误 - 文件占用: 记录失败文件,显示在结果中 - 磁盘错误: 中止清理,显示错误信息 - 部分成功: 显示成功和失败的文件数 // WebSocket错误 - 连接失败: 自动重连(最多3次) - 消息丢失: 使用轮询API补充进度 - 连接断开: 提示用户刷新页面或重试 ``` ### 5.8 性能优化建议 ```typescript 1. 配置表单防抖 - 输入框onChange事件使用防抖(300ms) - 减少频繁的状态更新 2. WebSocket消息节流 - 进度更新消息限制频率(最多500ms一次) - 避免UI频繁重绘 3. 大文件清理优化 - 批量删除文件(每批100个) - 在批次间添加延迟,避免阻塞 4. 配置缓存 - 自动清理配置缓存5分钟 - 减少重复请求 5. 惰性加载 - Tab内容按需渲染 - 减少初始加载时间 ``` --- ## 6. 总结 **日志清理**页面是系统维护的重要功能,设计时重点考虑: 1. **双模式设计**: 手动清理和自动清理分离,满足不同场景需求 2. **快捷操作**: 预设常用时间范围,提升手动清理效率 3. **灵活配置**: 支持时间周期和磁盘空间双重自动清理策略 4. **实时反馈**: WebSocket实时推送清理进度,提升用户体验 5. **安全保障**: 清理前确认、进度可视化、结果详细反馈 6. **配置管理**: 自动清理配置可取消、可重置,防止误操作 通过合理的表单设计、状态管理和WebSocket通信,可以实现一个功能完善、交互流畅的日志清理系统。