automated_task_monitor/monitor/static/js/monitor.js

928 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 在文件开头添加调试日志
console.log('monitor.js 加载完成');
// 全局变量声明
let currentDirectory = null;
let selectedPid = null;
let updateInterval = null;
let cpuChart = null;
let memoryChart = null;
let gpuChart = null;
// 更新监控数据
function updateMonitorData(pid) {
// 使用保存的监控类型
const type = currentMonitorType;
console.log('正在更新数据,类型:', type); // 调试日志
// 构建 URL确保包含 type 参数
const url = new URL(`${window.location.origin}/api/process/${pid}/status/`);
url.searchParams.append('type', type);
console.log('请求 URL:', url.toString()); // 调试日志
fetch(url)
.then(response => response.json())
.then(response => {
if (response.status === 'success') {
const data = response.data;
updateUI(data, type);
}
})
.catch(error => handleError(error, '获取监控数据失败'));
}
// 获取所有监控按钮和停止按钮
const monitorButtons = document.querySelectorAll('[data-type]');
const stopBtn = document.getElementById('stopMonitor');
const pidInput = document.getElementById('pidInput');
// 保存当前监控类型的全局变量
let currentMonitorType = null;
// 开始监控
function startMonitoring(pid) {
console.log('开始监控,类型:', currentMonitorType); // 调试日志
updateMonitorData(pid); // 立即执行一次更新
monitorInterval = setInterval(() => {
updateMonitorData(pid);
}, 60000);
}
// 停止监控
function stopMonitoring() {
if (!currentDirectory) {
console.error('没有正在监控的目录');
showMessage('没有正在监控的目录', 'error');
return;
}
const stopBtn = document.getElementById('stopDirectoryMonitor');
if (!stopBtn) return;
stopBtn.disabled = true;
stopBtn.textContent = '正在停止...';
// 先清理定时器
if (updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
}
const directoryToStop = currentDirectory;
fetch('/monitor/stop-directory-monitor/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify({ directory: directoryToStop })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
cleanupMonitoringState();
showMessage('监控已停止', 'success');
} else {
throw new Error(data.message || '停止监控失败');
}
})
.catch(error => {
console.error('停止监控失败:', error);
showMessage(error.message || '停止监控失败', 'error');
stopBtn.disabled = false;
})
.finally(() => {
stopBtn.textContent = '停止监控';
});
}
// 初始化图表
function initCharts() {
console.log('初始化图表');
cpuChart = echarts.init(document.getElementById('cpuChart'));
memoryChart = echarts.init(document.getElementById('memoryChart'));
gpuChart = echarts.init(document.getElementById('gpuChart'));
const baseOption = {
tooltip: {
trigger: 'axis',
formatter: function(params) {
const time = new Date(params[0].value[0]).toLocaleTimeString();
return `${time}<br/>${params[0].seriesName}: ${params[0].value[1].toFixed(2)}%`;
}
},
xAxis: {
type: 'time',
splitLine: { show: false }
},
yAxis: {
type: 'value',
min: 0,
max: 100,
splitLine: { show: true }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
series: [{
type: 'line',
showSymbol: false,
data: [],
smooth: true,
areaStyle: {
opacity: 0.1
}
}]
};
cpuChart.setOption({
...baseOption,
series: [{
...baseOption.series[0],
name: 'CPU使用率',
itemStyle: { color: '#2196F3' }
}]
});
memoryChart.setOption({
...baseOption,
series: [{
...baseOption.series[0],
name: '内存使用量',
itemStyle: { color: '#4CAF50' }
}]
});
gpuChart.setOption({
...baseOption,
series: [{
...baseOption.series[0],
name: 'GPU使用率',
itemStyle: { color: '#FF5722' }
}]
});
}
// 更新图表数据
function updateCharts(process) {
console.log('更新图表:', process);
const now = new Date();
// 更新CPU图表
if (cpuChart) {
updateChartData(cpuChart, now, process.cpu_usage);
}
// 更新内存图表
if (memoryChart) {
updateChartData(memoryChart, now, process.memory_usage);
}
// 更新GPU图表
if (gpuChart && process.gpu_info) {
updateChartData(gpuChart, now, process.gpu_info.usage);
}
}
// 辅助函数:更新图表数据
function updateChartData(chart, time, value) {
if (!chart) return;
try {
const option = chart.getOption();
const data = option.series[0].data || [];
data.push([time, value]);
// 保持最近60个数据点
if (data.length > 60) {
data.shift();
}
chart.setOption({
series: [{
data: data
}]
});
} catch (error) {
console.error('更新图表数据失败:', error);
}
}
// 修改更新进程表格函数
function updateProcessTable(processes) {
console.log('更新进程表格:', processes); // 调试日志
const table = document.getElementById('processTable');
if (!table) {
console.error('找不到进程表格元素');
return;
}
if (!processes || processes.length === 0) {
table.innerHTML = `
<tr>
<td colspan="7" class="text-center">暂无进程数据</td>
</tr>
`;
return;
}
table.innerHTML = processes.map(proc => `
<tr class="${selectedPid && selectedPid == proc.pid ? 'table-primary' : ''}">
<td>${proc.pid}</td>
<td>${proc.name}</td>
<td>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1" style="height: 6px;">
<div class="progress-bar" role="progressbar"
style="width: ${proc.cpu_usage}%;"
aria-valuenow="${proc.cpu_usage}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="ms-2">${proc.cpu_usage.toFixed(1)}%</span>
</div>
</td>
<td>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
style="width: ${(proc.memory_usage/1000)*100}%;"
aria-valuenow="${proc.memory_usage}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="ms-2">${proc.memory_usage.toFixed(1)} MB</span>
</div>
</td>
<td>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar"
style="width: ${proc.gpu_info.usage}%;"
aria-valuenow="${proc.gpu_info.usage}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="ms-2">${proc.gpu_info.usage}%</span>
</div>
</td>
<td>${proc.gpu_info.memory.toFixed(1)} MB</td>
<td>
<span class="badge bg-success">运行中</span>
<button class="btn btn-sm btn-outline-primary ms-2"
onclick="selectProcess(${proc.pid})">
查看详情
</button>
</td>
</tr>
`).join('');
}
// 修改选择进程的函数
function selectProcess(pid) {
console.log('选择进程:', pid);
selectedPid = pid.toString(); // 确保转换为字符串
// 更新选择器
const selector = document.getElementById('processSelector');
if (selector) {
selector.value = selectedPid;
}
// 更新表格中的选中状态
const rows = document.querySelectorAll('#processTable tr');
rows.forEach(row => {
if (row.cells && row.cells[0] && row.cells[0].textContent === selectedPid) {
row.classList.add('table-primary');
} else {
row.classList.remove('table-primary');
}
});
// 获取最新数据并更新图表
if (currentDirectory) {
fetch(`/monitor/directory-status/?directory=${encodeURIComponent(currentDirectory)}`)
.then(response => response.json())
.then(data => {
if (data.status === 'success' && data.processes) {
const selectedProcess = data.processes.find(p => p.pid.toString() === selectedPid);
if (selectedProcess) {
// 更新图表
updateCharts(selectedProcess);
// 更新详细信息
updateProcessDetails(selectedProcess);
}
}
})
.catch(error => console.error('获取进程详情失败:', error));
}
}
// 修改进程选择器更新函数
function updateProcessSelector(processes) {
console.log('更新进程选择器,进程列表:', processes); // 调试日志
const selector = document.getElementById('processSelector');
const processInfo = document.getElementById('selectedProcessInfo');
if (!selector) {
console.error('找不到进程选择器元素');
return;
}
// 启用选择器
selector.disabled = false;
// 获取当前的进程列表
const currentPids = processes.map(p => p.pid.toString());
console.log('当前PID列表:', currentPids); // 调试日志
// 如果当前选中的进程不在列表中,清除选择
if (selectedPid && !currentPids.includes(selectedPid.toString())) {
console.log('选中的进程不再存在,清除选择'); // 调试日志
selectedPid = null;
clearCharts();
}
// 移除旧的事件监听器
const newSelector = selector.cloneNode(true);
selector.parentNode.replaceChild(newSelector, selector);
// 更新选择器选项
newSelector.innerHTML = `
<option value="">选择要监控的进程</option>
${processes.map(proc => `
<option value="${proc.pid}" ${proc.pid.toString() === selectedPid ? 'selected' : ''}>
PID: ${proc.pid} - ${proc.name} (CPU: ${proc.cpu_usage.toFixed(2)}%)
</option>
`).join('')}
`;
// 添加新的事件监听器
newSelector.addEventListener('change', function() {
console.log('进程选择变更:', this.value);
selectedPid = this.value;
if (selectedPid) {
const selected = processes.find(p => p.pid === parseInt(selectedPid));
if (selected) {
processInfo.textContent = `监控中: PID ${selected.pid} - ${selected.name}`;
updateProcessDetails(selected); // 更新详细信息
clearCharts();
}
} else {
processInfo.textContent = '未选择进程';
clearCharts();
// 清空详细信息
['basicInfo', 'resourceInfo', 'networkInfo', 'fileInfo'].forEach(id => {
document.getElementById(id).innerHTML = '<p>选择进程查看详细信息</p>';
});
}
});
console.log('进程选择器更新完成'); // 调试日志
}
// 添加清除图表数据函数
function clearCharts() {
const emptyOption = {
series: [{
data: []
}]
};
cpuChart.setOption(emptyOption);
memoryChart.setOption(emptyOption);
gpuChart.setOption(emptyOption);
}
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM加载完成开始初始化');
const startBtn = document.getElementById('startDirectoryMonitor');
const stopBtn = document.getElementById('stopDirectoryMonitor');
if (!startBtn || !stopBtn) {
console.error('找不到监控按钮元素');
return;
}
// 初始化图表
initCharts();
// 开始监控按钮事件
startBtn.addEventListener('click', function() {
console.log('点击了开始监控按钮');
const directory = document.getElementById('directoryInput').value;
const statusDiv = document.getElementById('directoryMonitorStatus');
if (!directory) {
statusDiv.style.display = 'block';
statusDiv.className = 'alert alert-warning';
statusDiv.textContent = '请输入目录路径';
return;
}
// 显示加载状态
startBtn.disabled = true;
startBtn.textContent = '启动中...';
statusDiv.style.display = 'block';
statusDiv.className = 'alert alert-info';
statusDiv.textContent = '正在启动监控...';
console.log('开始监控目录:', directory);
// 清除旧的选择
selectedPid = null;
clearCharts();
fetch('/monitor/scan-directory/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ directory: directory })
})
.then(response => response.json())
.then(data => {
console.log('接口返回:', data);
if (data.status === 'success') {
currentDirectory = directory;
statusDiv.className = 'alert alert-success';
statusDiv.textContent = data.message;
startBtn.disabled = true;
stopBtn.disabled = false;
// 开始定期获取详细信息
if (updateInterval) {
clearInterval(updateInterval);
}
currentDirectory = directory;
getProcessDetails(directory); // 立即获取一次
updateInterval = setInterval(() => getProcessDetails(directory), 5000); // 每5秒更新一次
} else {
statusDiv.className = 'alert alert-danger';
statusDiv.textContent = data.message;
startBtn.disabled = false;
}
})
.catch(error => {
console.error('监控启动失败:', error);
statusDiv.className = 'alert alert-danger';
statusDiv.textContent = '启动监控失败: ' + error.message;
startBtn.disabled = false;
})
.finally(() => {
startBtn.textContent = '开始监控';
});
});
// 停止监控按钮事件
if (stopBtn) {
stopBtn.addEventListener('click', function() {
console.log('停止监控,当前目录:', currentDirectory);
// 检查是否有当前目录
if (!currentDirectory) {
console.error('没有正在监控的目录');
showMessage('没有正在监控的目录', 'error');
return;
}
// 禁用按钮,防止重复点击
stopBtn.disabled = true;
stopBtn.textContent = '正在停止...';
// 先清理定时器,防止继续发送请求
if (updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
}
// 保存当前目录的副本
const directoryToStop = currentDirectory;
// 发送停止监控请求到服务器
fetch('/monitor/stop-directory-monitor/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify({
directory: directoryToStop,
timestamp: new Date().getTime() // 添加时间戳防止缓存
})
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('停止监控响应:', data);
if (data.status === 'success') {
// 清理前端状态
cleanupMonitoringState();
// 显示成功消息
showMessage('监控已停止', 'success');
} else {
throw new Error(data.message || '停止监控失败');
}
})
.catch(error => {
console.error('停止监控失败:', error);
showMessage(error.message || '停止监控请求失败', 'error');
// 恢复按钮状态
stopBtn.disabled = false;
stopBtn.textContent = '停止监控';
});
});
}
// 初始状态设置
stopBtn.disabled = true; // 初始状态下停止按钮禁用
console.log('初始化完成');
// 为每个监控按钮添加点击事件
monitorButtons.forEach(button => {
button.addEventListener('click', function() {
const pid = pidInput.value.trim();
const type = this.dataset.type; // 从按钮的 data-type 属性获取类型
if (!pid) {
alert('请输入进程ID');
return;
}
// 保存当前选择的监控类型
currentMonitorType = type;
console.log('设置监控类型为:', currentMonitorType);
fetch(`/monitor/start/?pid=${pid}&type=${type}`)
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
startMonitoring(pid);
// 禁用所有监控按钮
monitorButtons.forEach(btn => btn.disabled = true);
stopBtn.disabled = false;
pidInput.disabled = true;
} else {
alert(data.message);
}
})
.catch(error => {
console.error('启动监控失败:', error);
alert('启动监控失败');
});
});
});
// 获取自动检测按钮
const startAutoDetectBtn = document.getElementById('startAutoDetectBtn');
const stopAutoDetectBtn = document.getElementById('stopAutoDetectBtn');
// 获取CSRF Token
function getCSRFToken() {
const name = 'csrftoken';
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// 添加自动检测开始按钮事件
startAutoDetectBtn.addEventListener('click', function() {
fetch('/auto_detect/', {
method: 'POST',
headers: {
'X-CSRFToken': getCSRFToken(),
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('已开始自动检测高资源进程');
startAutoDetectBtn.disabled = true;
stopAutoDetectBtn.disabled = false;
} else {
alert(data.message);
}
})
.catch(error => {
console.error('启动自动检测失败:', error);
alert('启动自动检测失败');
});
});
// 添加自动检测停止按钮事件
stopAutoDetectBtn.addEventListener('click', function() {
fetch('/stop_auto_detect/', {
method: 'POST',
headers: {
'X-CSRFToken': getCSRFToken(),
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('已停止自动检测');
startAutoDetectBtn.disabled = false;
stopAutoDetectBtn.disabled = true;
} else {
alert(data.message);
}
})
.catch(error => {
console.error('停止自动检测失败:', error);
alert('停止自动检测失败');
});
});
});
// 更新 UI 的函数
function updateUI(data, type) {
// 根据监控类型更新对应的表格
if (type === 'all' || type === 'cpu') {
document.getElementById('cpuTable').innerHTML = `
<tr><td>使用率</td><td>${data.cpu.usage}</td></tr>
<tr><td>用户态时间</td><td>${data.cpu.user_time}</td></tr>
<tr><td>内核态时间</td><td>${data.cpu.system_time}</td></tr>
<tr><td>CPU核心数</td><td>${data.cpu.cores}</td></tr>
<tr><td>CPU频率</td><td>${data.cpu.frequency}</td></tr>
<tr><td>上下文切换</td><td>${data.cpu.context_switches}</td></tr>
`;
document.getElementById('cpuStatus').className = 'badge bg-success status-badge';
document.getElementById('cpuStatus').textContent = '监控中';
}
if (type === 'all' || type === 'memory') {
document.getElementById('memoryTable').innerHTML = `
<tr><td>物理内存</td><td>${data.memory.physical}</td></tr>
<tr><td>虚拟内存</td><td>${data.memory.virtual}</td></tr>
<tr><td>内存映射</td><td>${data.memory.mappings}</td></tr>
<tr><td>系统内存使用</td><td>${data.memory.system_usage}</td></tr>
<tr><td>交换空间使用</td><td>${data.memory.swap_usage}</td></tr>
`;
document.getElementById('memoryStatus').className = 'badge bg-success status-badge';
document.getElementById('memoryStatus').textContent = '监控中';
}
if (type === 'all' || type === 'gpu') {
document.getElementById('gpuTable').innerHTML = `
<tr><td>使用率</td><td>${data.gpu.usage}</td></tr>
<tr><td>显存使用</td><td>${data.gpu.memory}</td></tr>
`;
document.getElementById('gpuStatus').className = 'badge bg-success status-badge';
document.getElementById('gpuStatus').textContent = '监控中';
}
// 更新最后更新时间
document.getElementById('lastUpdate').textContent =
`最后更新: ${data.timestamp}`;
}
// 添加错误处理函数
function handleError(error, message) {
console.error(message, error);
['cpu', 'gpu', 'memory'].forEach(type => {
const statusElement = document.getElementById(`${type}Status`);
if (statusElement) {
statusElement.className = 'badge bg-danger status-badge';
statusElement.textContent = '错误';
}
});
alert(message);
}
// 窗口大小改变时调整图表大小
window.addEventListener('resize', function() {
if (cpuChart) cpuChart.resize();
if (memoryChart) memoryChart.resize();
if (gpuChart) gpuChart.resize();
});
// 更新进程详细信息的函数
function updateProcessDetails(process) {
if (!process) return;
// 检查元素是否存在
const basicInfo = document.getElementById('basicInfo');
const resourceInfo = document.getElementById('resourceInfo');
if (!basicInfo || !resourceInfo) {
console.error('找不到详情显示元素');
return;
}
try {
// 更新基本信息
basicInfo.innerHTML = `
<dl class="row mb-0">
<dt class="col-sm-4">PID</dt>
<dd class="col-sm-8">${process.pid}</dd>
<dt class="col-sm-4">进程名</dt>
<dd class="col-sm-8">${process.name}</dd>
<dt class="col-sm-4">命令行</dt>
<dd class="col-sm-8"><small class="text-muted">${process.command_line}</small></dd>
<dt class="col-sm-4">工作目录</dt>
<dd class="col-sm-8"><small class="text-muted">${process.working_directory}</small></dd>
<dt class="col-sm-4">创建时间</dt>
<dd class="col-sm-8">${process.create_time}</dd>
</dl>
`;
// 更新资源信息
resourceInfo.innerHTML = `
<dl class="row mb-0">
<dt class="col-sm-4">CPU使用率</dt>
<dd class="col-sm-8">${process.cpu_usage.toFixed(2)}%</dd>
<dt class="col-sm-4">内存使用</dt>
<dd class="col-sm-8">${process.memory_usage.toFixed(2)} MB</dd>
<dt class="col-sm-4">线程数</dt>
<dd class="col-sm-8">${process.threads}</dd>
<dt class="col-sm-4">GPU使用率</dt>
<dd class="col-sm-8">${process.gpu_info.usage}%</dd>
<dt class="col-sm-4">GPU内存</dt>
<dd class="col-sm-8">${process.gpu_info.memory.toFixed(2)} MB</dd>
</dl>
`;
} catch (error) {
console.error('更新进程详情失败:', error);
}
}
// 修改定期更新函数
function startPeriodicUpdate(directory) {
console.log('开始定期更新, 目录:', directory);
if (updateInterval) {
clearInterval(updateInterval);
}
// 立即执行一次更新
getProcessDetails(directory);
// 设置定时更新
updateInterval = setInterval(() => {
getProcessDetails(directory);
}, 5000);
}
// 修改获取进程详情的函数
function getProcessDetails(directory) {
if (!directory || !currentDirectory) { // 添加currentDirectory检查
console.log('没有目录或已停止监控');
return;
}
// 使用 AbortController 来控制fetch请求
if (window.currentFetch) {
window.currentFetch.abort();
}
window.currentFetch = new AbortController();
fetch(`/monitor/directory-status/?directory=${encodeURIComponent(directory)}`, {
signal: window.currentFetch.signal
})
.then(response => response.json())
.then(data => {
if (!currentDirectory) { // 再次检查是否已停止监控
console.log('监控已停止,不更新数据');
return;
}
console.log('收到进程数据:', data);
if (data.status === 'success' && data.processes && data.processes.length > 0) {
updateProcessTable(data.processes);
if (selectedPid) {
const selectedProcess = data.processes.find(p => p.pid.toString() === selectedPid);
if (selectedProcess) {
updateCharts(selectedProcess);
updateProcessDetails(selectedProcess);
}
}
}
})
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被中止');
} else {
console.error('获取进程详情失败:', error);
}
});
}
// 获取所有正在监控的进程ID
function getAllMonitoredPids() {
const table = document.getElementById('processTable');
if (!table) return [];
const pids = [];
const rows = table.getElementsByTagName('tr');
for (let row of rows) {
const firstCell = row.cells[0];
if (firstCell && !isNaN(firstCell.textContent)) {
pids.push(firstCell.textContent);
}
}
return pids;
}
// 修改清理监控状态的函数
function cleanupMonitoringState() {
console.log('清理监控状态');
// 先取消所有正在进行的请求
if (window.currentFetch) {
window.currentFetch.abort();
window.currentFetch = null;
}
// 清除定时器
if (updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
}
// 清除状态变量(在清理完其他内容后再清除)
const oldDirectory = currentDirectory;
currentDirectory = null;
selectedPid = null;
console.log('已清除的目录:', oldDirectory);
// 清空图表
clearCharts();
// 清空进程列表
const processTable = document.getElementById('processTable');
if (processTable) {
processTable.innerHTML = `
<tr>
<td colspan="8" class="text-center">未开始监控</td>
</tr>
`;
}
// 清空详细信息
const basicInfo = document.getElementById('basicInfo');
const resourceInfo = document.getElementById('resourceInfo');
if (basicInfo) {
basicInfo.innerHTML = '<p class="text-center text-muted">未选择进程</p>';
}
if (resourceInfo) {
resourceInfo.innerHTML = '<p class="text-center text-muted">未选择进程</p>';
}
// 更新按钮状态
const startBtn = document.getElementById('startDirectoryMonitor');
const stopBtn = document.getElementById('stopDirectoryMonitor');
if (startBtn) startBtn.disabled = false;
if (stopBtn) {
stopBtn.disabled = true;
stopBtn.textContent = '停止监控';
}
}
// 添加显示消息的函数
function showMessage(message, type = 'info') {
const statusDiv = document.getElementById('directoryMonitorStatus');
if (statusDiv) {
statusDiv.style.display = 'block';
statusDiv.className = `alert alert-${type}`;
statusDiv.textContent = message;
// 3秒后自动隐藏
setTimeout(() => {
statusDiv.style.display = 'none';
}, 3000);
}
}