日志清理是系统之家一级菜单下的二级页面,提供系统日志的自动清理和手动清理功能,帮助管理员维护系统日志,防止日志文件过度占用磁盘空间。
Page (日志清理)
├── Tabs Component (选项卡容器)
│ ├── Tab Panel 1: 自动清理
│ └── Tab Panel 2: 手动清理
└── Footer: 操作按钮区域
├── Button: 取消
└── Button: 保存/开始清理
布局类型: Tabs Layout (选项卡布局)
排列方向: Horizontal Tabs (水平标签)
区域划分: 2个独立的Tab Panel,通过标签切换
固定底部: Footer区域固定在页面底部,包含操作按钮
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)
布局特点:
| 组件类型 | 实例 | 参数配置 | 说明 |
|---|---|---|---|
| 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" | 执行手动清理操作 |
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)
布局特点:
| 组件类型 | 实例 | 参数配置 | 说明 |
|---|---|---|---|
| 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" | 保存自动清理配置 |
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
选择理由:
系统日志可能包含多种类型,需要明确哪些日志可以被清理:
需求建议:
时间周期判断:
if (启用时间周期清理 && 当前时间 - 上次清理时间 >= 清理周期) {
执行清理(保留周期内的日志);
}
磁盘空间判断:
if (启用磁盘空间清理 && 磁盘剩余空间 < 磁盘阈值) {
执行清理(保留周期内的日志);
}
组合条件: 两个条件是OR关系,任一满足即触发
优化建议:
处理策略:
建议创建 logCleanupSlice 来管理日志清理的状态。
// 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;
};
}
// Tab切换
- setActiveTab: 设置当前激活的Tab
// 手动清理相关
- setQuickSelect: 设置快捷选择选项
- setDateRange: 设置自定义时间范围
- startManualCleanup: 开始手动清理
- updateCleanupProgress: 更新清理进度
- manualCleanupComplete: 手动清理完成
- manualCleanupFailed: 手动清理失败
- resetManualCleanup: 重置手动清理状态
// 自动清理相关
- fetchAutoCleanupConfig: 获取自动清理配置
- updateAutoCleanupConfig: 更新自动清理配置(本地状态)
- toggleTimeBased: 切换时间周期清理开关
- toggleSpaceBased: 切换磁盘空间清理开关
- saveAutoCleanupConfig: 保存自动清理配置到服务器
- cancelAutoCleanupConfig: 取消修改,恢复原始配置
- resetAutoCleanupConfig: 重置配置
// GET /api/system/log-cleanup/last-time
interface GetLastCleanupTimeResponse {
lastCleanupTime: string | null; // ISO 8601格式
}
// 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[];
};
}
// 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;
}
src/pages/system/LogCleanup/
├── index.tsx // 主页面组件
├── ManualCleanup.tsx // 手动清理Tab
├── AutoCleanup.tsx // 自动清理Tab
└── components/
├── QuickSelectRadio.tsx // 快捷选择单选组件
├── DateRangeSelector.tsx // 时间范围选择组件
├── CleanupProgressModal.tsx // 清理进度弹窗组件
└── AutoConfigForm.tsx // 自动清理配置表单组件
// 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 (
<div className="log-cleanup-page">
<Tabs
activeKey={activeTab}
onChange={(key) => setActiveTab(key as 'manual' | 'auto')}
items={[
{
key: 'manual',
label: '手动清理',
children: <ManualCleanup />
},
{
key: 'auto',
label: '自动清理',
children: <AutoCleanup />
}
]}
/>
</div>
);
};
export default LogCleanup;
// 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: <ExclamationCircleOutlined />,
content: '日志清理操作不可恢复,确定要继续吗?',
onOk: () => {
dispatch(startManualCleanup({
mode: localQuickSelect,
dateRange: localQuickSelect === 'custom' ? localDateRange : undefined
}));
}
});
};
const isCleanupDisabled = () => {
if (localQuickSelect === 'custom') {
return !localDateRange.startDate || !localDateRange.endDate;
}
return false;
};
return (
<div className="manual-cleanup-tab">
{/* 上次清理时间提示 */}
{lastCleanupTime && (
<div className="last-cleanup-info">
<Text type="danger">
提示: 上一次清理时间 {dayjs(lastCleanupTime).format('YYYY.MM.DD HH:mm')}
</Text>
</div>
)}
{/* 快捷选择 */}
<div className="section">
<div className="section-header">快捷选择</div>
<Radio.Group
value={localQuickSelect}
onChange={(e) => handleQuickSelectChange(e.target.value)}
>
<Space direction="vertical">
<Radio value="all">全部日志</Radio>
<Radio value="recent3">保留近3天</Radio>
<Radio value="recent10">保留近10天</Radio>
</Space>
</Radio.Group>
</div>
{/* 选择时间范围 */}
<div className="section">
<div className="section-header">选择时间范围</div>
<Space size="large">
<div className="form-item">
<label>起始时间</label>
<DatePicker
value={localDateRange.startDate ? dayjs(localDateRange.startDate) : null}
onChange={(date) => handleDateChange(date, 'startDate')}
placeholder="请选择开始日期"
format="YYYY-MM-DD"
disabled={localQuickSelect !== 'custom'}
/>
</div>
<div className="form-item">
<label>截止时间</label>
<DatePicker
value={localDateRange.endDate ? dayjs(localDateRange.endDate) : null}
onChange={(date) => handleDateChange(date, 'endDate')}
placeholder="请选择结束日期"
format="YYYY-MM-DD"
disabled={localQuickSelect !== 'custom'}
disabledDate={(current) => {
if (!localDateRange.startDate) return false;
return current && current < dayjs(localDateRange.startDate);
}}
/>
</div>
</Space>
</div>
{/* 操作按钮 */}
<div className="footer-actions">
<Button>取消</Button>
<Button
type="primary"
onClick={handleStartCleanup}
disabled={isCleanupDisabled()}
loading={status === 'cleaning'}
>
开始清理
</Button>
</div>
{/* 清理进度弹窗 */}
{status !== 'idle' && <CleanupProgressModal />}
</div>
);
};
export default ManualCleanup;
// 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 (
<div className="auto-cleanup-tab">
<Form
form={form}
layout="horizontal"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
onValuesChange={handleValuesChange}
>
{/* 按照时间周期清理 */}
<div className="section">
<div className="section-header">按照时间周期清理</div>
<Form.Item
label="按照时间周期清理"
name={['timeBased', 'enabled']}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="清理周期(天)"
name={['timeBased', 'cleanupInterval']}
rules={[{ required: true, message: '请输入清理周期' }]}
>
<InputNumber
min={1}
max={999}
precision={0}
disabled={!form.getFieldValue(['timeBased', 'enabled'])}
style={{ width: '100%' }}
/>
</Form.Item>
<Form.Item
label="保存周期(天)"
name={['timeBased', 'retentionPeriod']}
rules={[{ required: true, message: '请输入保存周期' }]}
>
<InputNumber
min={1}
max={999}
precision={0}
disabled={!form.getFieldValue(['timeBased', 'enabled'])}
style={{ width: '100%' }}
/>
</Form.Item>
</div>
<Divider />
{/* 按照磁盘空间清理 */}
<div className="section">
<div className="section-header">按照磁盘空间清理</div>
<Form.Item
label="按照磁盘空间清理"
name={['spaceBased', 'enabled']}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label="磁盘小于该值时进行清理(G)"
name={['spaceBased', 'threshold']}
rules={[{ required: true, message: '请输入磁盘阈值' }]}
>
<InputNumber
min={1}
max={999}
precision={0}
disabled={!form.getFieldValue(['spaceBased', 'enabled'])}
style={{ width: '100%' }}
/>
</Form.Item>
<Form.Item
label="保存周期(天)"
name={['spaceBased', 'retentionPeriod']}
rules={[{ required: true, message: '请输入保存周期' }]}
>
<InputNumber
min={1}
max={999}
precision={0}
disabled={!form.getFieldValue(['spaceBased', 'enabled'])}
style={{ width: '100%' }}
/>
</Form.Item>
</div>
</Form>
{/* 操作按钮 */}
<div className="footer-actions">
<Button onClick={handleCancel} disabled={!hasChanges}>
取消
</Button>
<Button
type="primary"
onClick={handleSave}
loading={saving}
disabled={!hasChanges}
>
保存
</Button>
</div>
</div>
);
};
export default AutoCleanup;
// 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 (
<Modal
title={getTitle()}
open={status !== 'idle'}
onCancel={handleClose}
footer={status === 'cleaning' ? null : undefined}
closable={status !== 'cleaning'}
maskClosable={false}
>
{status === 'cleaning' && progress && (
<div>
<Progress
percent={Math.round((progress.current / progress.total) * 100)}
status="active"
/>
<Text type="secondary" style={{ marginTop: 8, display: 'block' }}>
正在清理: {progress.currentFile} ({progress.current}/{progress.total})
</Text>
</div>
)}
{status === 'success' && result && (
<Alert
type="success"
message="清理成功"
description={
<div>
<p>已清理 {result.filesDeleted} 个日志文件</p>
<p>释放磁盘空间: {formatSize(result.spaceFreed)}</p>
{result.errorFiles.length > 0 && (
<p style={{ color: '#ff4d4f' }}>
{result.errorFiles.length} 个文件清理失败
</p>
)}
</div>
}
showIcon
/>
)}
{status === 'failed' && (
<Alert
type="error"
message="清理失败"
description="清理过程中发生错误,请稍后重试"
showIcon
/>
)}
</Modal>
);
};
export default CleanupProgressModal;
1. 用户选择清理范围
├─> 选择快捷选项(全部/近3天/近10天)
└─> 或自定义时间范围
2. 用户点击"开始清理"
├─> 验证选择是否有效(自定义模式需要起止时间都填写)
├─> 弹出确认对话框
└─> 用户确认后开始清理
3. 执行清理
├─> dispatch(startManualCleanup({ mode, dateRange }))
├─> 调用 API: POST /api/system/log-cleanup/manual
├─> 获取taskId,建立WebSocket连接
├─> 显示进度弹窗
└─> 实时更新清理进度
4. 清理完成
├─> 显示清理结果(文件数、释放空间)
├─> 更新上次清理时间
├─> 记录到操作日志
└─> 用户关闭弹窗
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. 开关联动
├─> 监听开关值变化
├─> 开关关闭时禁用相关输入框
└─> 开关打开时启用相关输入框
// 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;
}
}
// 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();
// 表单验证错误
- 自定义时间范围: 起始时间晚于截止时间
- 输入值超出范围: min=1, max=999
- 必填项未填写: 保存配置时检查
// API调用错误
- 网络错误: 显示"网络连接失败,请检查网络"
- 服务器错误: 显示"服务器错误,请稍后重试"
- 权限错误: 显示"权限不足,无法执行操作"
// 清理操作错误
- 文件占用: 记录失败文件,显示在结果中
- 磁盘错误: 中止清理,显示错误信息
- 部分成功: 显示成功和失败的文件数
// WebSocket错误
- 连接失败: 自动重连(最多3次)
- 消息丢失: 使用轮询API补充进度
- 连接断开: 提示用户刷新页面或重试
1. 配置表单防抖
- 输入框onChange事件使用防抖(300ms)
- 减少频繁的状态更新
2. WebSocket消息节流
- 进度更新消息限制频率(最多500ms一次)
- 避免UI频繁重绘
3. 大文件清理优化
- 批量删除文件(每批100个)
- 在批次间添加延迟,避免阻塞
4. 配置缓存
- 自动清理配置缓存5分钟
- 减少重复请求
5. 惰性加载
- Tab内容按需渲染
- 减少初始加载时间
日志清理页面是系统维护的重要功能,设计时重点考虑:
通过合理的表单设计、状态管理和WebSocket通信,可以实现一个功能完善、交互流畅的日志清理系统。