feat: 添加操作日志、危险操作确认和业务关联功能

1. 新增操作日志记录功能,记录关键操作
2. 实现危险操作确认对话框,防止误删
3. 添加业务和库房管理模块
4. 支持设备标记为空闲状态
5. 完善API文档和健康检查
6. 优化前端删除操作的确认流程
7. 添加Swagger API文档支持
8. 实现设备与业务的关联功能
9. 改进设备模型,添加空闲相关字段
10. 优化用户、角色管理操作日志
This commit is contained in:
zhang1106
2026-03-20 17:15:14 +08:00
parent c392df9ce3
commit 73cbe4ac1b
52 changed files with 11149 additions and 1756 deletions
+220
View File
@@ -0,0 +1,220 @@
import React, { useState, useEffect } from 'react';
import { Modal, Input, Alert, Typography, Descriptions, Divider } from 'antd';
import { WarningOutlined, InfoCircleOutlined } from '@ant-design/icons';
import axios from 'axios';
import {
RISK_LEVEL,
RISK_CONFIG,
OPERATION_TYPES,
OPERATION_LABELS,
ENTITY_LABELS,
getRiskLevel,
} from '../config/dangerousOperationConfig';
const { Text, Paragraph } = Typography;
export const useDangerousOperation = () => {
const confirm = async ({
operationType = OPERATION_TYPES.DELETE_SINGLE,
entityType = 'item',
items = [],
itemCount = 1,
title = '',
description = '',
impactDetails = {},
onConfirm,
onCancel,
}) => {
const riskLevel = getRiskLevel(operationType, itemCount, {
isSystemLevel: impactDetails.isSystemLevel,
hasRelatedData: impactDetails.relatedDataCount > 0,
});
const config = RISK_CONFIG[riskLevel];
const entityLabel = ENTITY_LABELS[entityType] || entityType;
const operationLabel = OPERATION_LABELS[operationType] || operationType;
return new Promise((resolve) => {
const modalKey = `dangerous-${Date.now()}`;
const handleOk = () => {
Modal.confirm({
title: '确认执行此操作?',
content: '此操作不可逆,请再次确认。',
okText: '确认执行',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
if (onConfirm) {
await onConfirm();
}
resolve(true);
} catch (error) {
resolve(false);
}
},
onCancel: () => {
if (onCancel) onCancel();
resolve(false);
},
});
};
const renderContent = () => {
const impactText = renderImpactDetails(impactDetails, entityLabel);
return (
<div style={styles.contentContainer}>
<Alert
message={
<Text strong style={{ color: config.color }}>
{config.icon} {config.title}
</Text>
}
description={
<Paragraph style={{ marginBottom: 0 }}>
{description || `您即将${operationLabel} ${itemCount > 1 ? `${itemCount}` : '1 个'}${entityLabel}`}
<br />
<Text type="secondary" style={{ fontSize: 12 }}>
此操作不可逆一旦删除将无法恢复
</Text>
</Paragraph>
}
type={riskLevel === RISK_LEVEL.EXTREME ? 'error' : riskLevel === RISK_LEVEL.HIGH ? 'warning' : 'info'}
style={{
backgroundColor: config.bgColor,
borderColor: config.borderColor,
marginBottom: 16,
}}
/>
{config.showImpactDetails && impactText && (
<>
<div style={styles.impactSection}>
<Text strong style={{ display: 'block', marginBottom: 8 }}>
<InfoCircleOutlined /> 影响范围
</Text>
{impactText}
</div>
<Divider style={{ margin: '12px 0' }} />
</>
)}
{riskLevel === RISK_LEVEL.EXTREME && config.requireKeyword && (
<div style={styles.keywordSection}>
<Alert
message={
<Text>
为确认此操作请输入 <Text code strong>CONFIRM</Text>
</Text>
}
type="error"
style={{ marginBottom: 8 }}
/>
<Input
id={`keyword-input-${modalKey}`}
placeholder="请输入 CONFIRM"
onChange={(e) => {
const inputValue = e.target.value;
const okButton = document.querySelector('.ant-modal-confirm .ant-btn-primary');
if (okButton) {
okButton.disabled = inputValue !== config.keyword;
}
}}
/>
</div>
)}
</div>
);
};
const renderImpactDetails = (details, entityLabel) => {
if (!details || Object.keys(details).length === 0) return null;
const items = [];
if (details.relatedDevices) {
items.push(`关联设备:${details.relatedDevices}`);
}
if (details.relatedCables) {
items.push(`关联接线:${details.relatedCables}`);
}
if (details.relatedPorts) {
items.push(`关联端口:${details.relatedPorts}`);
}
if (details.relatedNetworkCards) {
items.push(`关联网卡:${details.relatedNetworkCards}`);
}
if (details.relatedTickets) {
items.push(`关联工单:${details.relatedTickets}`);
}
if (details.relatedDataCount > 0) {
items.push(`其他关联数据:${details.relatedDataCount}`);
}
return items.length > 0 ? (
<ul style={{ margin: 0, paddingLeft: 20, color: '#666' }}>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
) : null;
};
Modal.confirm({
title: (
<span style={{ color: config.color }}>
{config.icon} {title || `${operationLabel}确认`}
</span>
),
icon: <WarningOutlined style={{ color: config.color }} />,
content: renderContent(),
okText: '确认',
okType: 'danger',
cancelText: '取消',
okButtonProps: {
disabled: riskLevel === RISK_LEVEL.EXTREME,
},
width: 520,
onOk: handleOk,
onCancel: () => {
if (onCancel) onCancel();
resolve(false);
},
});
});
};
const logOperation = async ({
operationType,
targetType,
targetId,
targetName,
metadata = {},
success = true,
}) => {
try {
await axios.post('/api/operation-logs/dangerous', {
operationType,
targetType,
targetId,
targetName,
metadata,
success,
timestamp: new Date().toISOString(),
});
} catch (error) {
console.error('Failed to log dangerous operation:', error);
}
};
return { confirm, logOperation };
};
export const confirmDangerousOperation = async (options) => {
const hook = useDangerousOperation();
return hook.confirm(options);
};
export default useDangerousOperation;