feat: 添加工单统计自动刷新功能并优化设备选择逻辑

refactor(工单管理): 重构设备选择表单并优化UI体验

fix(工单统计): 修复每日完成工单统计不准确的问题

perf(后端): 添加批量导出接口优化大数据量查询性能

chore: 添加react-transition-group依赖支持动画效果

feat(设备管理): 添加设备全量查询接口支持导出功能

style(工单管理): 优化表单布局和样式提升用户体验

refactor(工单字段): 优化字段管理组件处理空值情况

fix(机柜管理): 修复机柜列表查询接口参数问题

docs: 移除不再使用的工单字段管理页面
This commit is contained in:
zhang1106
2026-03-24 17:10:38 +08:00
parent 1b898814ed
commit 4e8d9be864
13 changed files with 1559 additions and 367 deletions
@@ -1,23 +1,25 @@
import React from 'react';
import { Modal, Button, Card, Row, Col, Tag } from 'antd';
import { AppstoreOutlined } from '@ant-design/icons';
import React, { useEffect, useState } from 'react';
import { Modal, Button, Row, Col, Tag, Progress } from 'antd';
import {
AppstoreOutlined,
EnvironmentOutlined,
ThunderboltOutlined,
CalendarOutlined,
ExclamationCircleOutlined,
ClockCircleOutlined,
EditOutlined,
FileTextOutlined,
PlusOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
SyncOutlined,
StopOutlined,
DesktopOutlined,
} from '@ant-design/icons';
import { designTokens } from '../../config/theme';
import { getStatusConfig, getTypeLabel, getDeviceTypeIcon } from '../../utils/deviceUtils.jsx';
const modalHeaderStyle = {
display: 'flex',
alignItems: 'center',
gap: '8px',
fontSize: '18px',
fontWeight: 600,
};
const secondaryActionStyle = {
height: '40px',
borderRadius: designTokens.borderRadius.small,
border: `1px solid ${designTokens.colors.border.light}`,
fontWeight: '500',
};
const { colors, shadows, borderRadius, transitions, spacing } = designTokens;
const DeviceDetailModal = ({
visible,
@@ -28,225 +30,723 @@ const DeviceDetailModal = ({
onViewTickets,
onCreateTicket,
}) => {
const [isVisible, setIsVisible] = useState(false);
const [activeTab, setActiveTab] = useState('basic');
useEffect(() => {
if (visible) {
setIsVisible(true);
} else {
const timer = setTimeout(() => setIsVisible(false), 300);
return () => clearTimeout(timer);
}
}, [visible]);
if (!device) return null;
const getStatusIcon = (status) => {
const iconMap = {
running: <SyncOutlined spin style={{ color: colors.status.running }} />,
maintenance: <ClockCircleOutlined style={{ color: colors.status.maintenance }} />,
offline: <StopOutlined style={{ color: colors.status.offline }} />,
fault: <ExclamationCircleOutlined style={{ color: colors.status.fault }} />,
idle: <CheckCircleOutlined style={{ color: '#36cfc9' }} />,
};
return iconMap[status] || <DesktopOutlined style={{ color: colors.text.tertiary }} />;
};
const getDeviceTypeColor = (type) => {
return colors.device[type] || colors.device.other;
};
const isWarrantyExpired = device.warrantyExpiry && new Date(device.warrantyExpiry) < new Date();
const warrantyDaysLeft = device.warrantyExpiry
? Math.ceil((new Date(device.warrantyExpiry) - new Date()) / (1000 * 60 * 60 * 24))
: null;
const InfoCard = ({ title, icon, children, className = '' }) => (
<div
className={`info-card ${className}`}
style={{
background: colors.background.primary,
borderRadius: borderRadius.large,
border: `1px solid ${colors.border.light}`,
boxShadow: shadows.small,
overflow: 'hidden',
transition: `all ${transitions.normal}`,
}}
>
<div
style={{
padding: '16px 20px',
borderBottom: `1px solid ${colors.border.light}`,
display: 'flex',
alignItems: 'center',
gap: '10px',
background: `linear-gradient(180deg, ${colors.background.secondary} 0%, ${colors.background.primary} 100%)`,
}}
>
<span style={{ color: colors.primary.main, fontSize: '16px' }}>{icon}</span>
<span style={{ fontWeight: 600, fontSize: '15px', color: colors.text.primary }}>
{title}
</span>
</div>
<div style={{ padding: '20px' }}>{children}</div>
</div>
);
const InfoItem = ({ label, value, status, copyable = false, fullWidth = false }) => {
const renderValue = () => {
if (status === 'warning') {
return (
<span
style={{
color: colors.warning.main,
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: '6px',
}}
>
<ExclamationCircleOutlined />
{value}
</span>
);
}
if (status === 'danger') {
return (
<span
style={{
color: colors.error.main,
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: '6px',
}}
>
<CloseCircleOutlined />
{value}
</span>
);
}
return <span style={{ fontWeight: 500 }}>{value || '-'}</span>;
};
return (
<div
style={{
marginBottom: '16px',
paddingBottom: '16px',
borderBottom: `1px dashed ${colors.border.light}`,
}}
className={fullWidth ? 'info-item-full' : ''}
>
<div
style={{
fontSize: '12px',
color: colors.text.tertiary,
marginBottom: '6px',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
{label}
</div>
<div
style={{
fontSize: '14px',
color: colors.text.primary,
}}
>
{renderValue()}
</div>
</div>
);
};
const tabItems = [
{ key: 'basic', label: '基本信息' },
{ key: 'location', label: '位置信息' },
{ key: 'maintenance', label: '维保信息' },
];
return (
<Modal
title={
<div style={{ ...modalHeaderStyle, paddingRight: '32px' }}>
<AppstoreOutlined style={{ color: '#667eea' }} />
设备详情
</div>
}
open={visible}
onCancel={onClose}
footer={[
<Button key="close" onClick={onClose} style={secondaryActionStyle}>
关闭
</Button>,
<Button key="viewTickets" onClick={() => onViewTickets(device)} style={secondaryActionStyle}>
查看工单
</Button>,
<Button key="createTicket" onClick={() => onCreateTicket(device)} style={secondaryActionStyle}>
创建工单
</Button>,
<Button
key="edit"
type="primary"
onClick={() => {
onClose();
onEdit(device);
}}
style={{
height: '40px',
borderRadius: designTokens.borderRadius.small,
background: designTokens.colors.primary.gradient,
border: 'none',
color: '#ffffff',
boxShadow: designTokens.shadows.small,
fontWeight: '500',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
编辑
</Button>,
]}
width={700}
destroyOnHidden
footer={null}
width={800}
destroyOnClose
centered
className="device-detail-modal"
styles={{
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
mask: {
backdropFilter: 'blur(4px)',
backgroundColor: 'rgba(0, 0, 0, 0.45)',
},
content: {
padding: 0,
borderRadius: borderRadius.xl,
overflow: 'hidden',
boxShadow: shadows.xl,
},
body: { padding: '0', overflow: 'auto' },
}}
>
<div>
<style>{`
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
.device-detail-modal .info-card:hover {
box-shadow: ${shadows.md};
transform: translateY(-2px);
}
.device-detail-modal .info-item:last-child {
margin-bottom: 0 !important;
padding-bottom: 0 !important;
border-bottom: none !important;
}
.device-detail-modal .tab-btn {
transition: all ${transitions.fast};
border-bottom: 2px solid transparent;
}
.device-detail-modal .tab-btn:hover {
color: ${colors.primary.main};
background: ${colors.primary.bg};
}
.device-detail-modal .tab-btn.active {
color: ${colors.primary.main};
border-bottom-color: ${colors.primary.main};
background: ${colors.primary.bg};
}
.device-detail-modal .action-btn {
transition: all ${transitions.fast};
}
.device-detail-modal .action-btn:hover {
transform: translateY(-1px);
box-shadow: ${shadows.md};
}
.device-detail-modal .device-icon-wrapper {
transition: all ${transitions.normal};
}
.device-detail-modal .device-icon-wrapper:hover {
transform: scale(1.05);
}
.device-detail-modal .progress-bar {
transition: all ${transitions.slow};
}
@media (max-width: 768px) {
.device-detail-modal .ant-modal {
max-width: 95vw !important;
margin: 10px auto !important;
}
}
`}</style>
<div
style={{
background: `linear-gradient(135deg, ${colors.primary.main} 0%, ${colors.primary.dark} 50%, ${colors.purple.main} 100%)`,
padding: '28px 32px',
color: '#fff',
position: 'relative',
overflow: 'hidden',
}}
>
<div
style={{
padding: '24px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: '#fff',
position: 'absolute',
top: 0,
right: 0,
width: '200px',
height: '200px',
background: 'radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%)',
borderRadius: '0 0 0 100%',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div
style={{
width: '64px',
height: '64px',
borderRadius: '12px',
backgroundColor: 'rgba(255,255,255,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
/>
<div
style={{
position: 'absolute',
bottom: '-50px',
left: '20%',
width: '100px',
height: '100px',
background: 'radial-gradient(circle, rgba(255,255,255,0.08) 0%, transparent 70%)',
borderRadius: '50%',
}}
/>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '20px', position: 'relative', zIndex: 1 }}>
<div
className="device-icon-wrapper"
style={{
width: '72px',
height: '72px',
borderRadius: borderRadius.large,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
backdropFilter: 'blur(10px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: `0 8px 32px rgba(0, 0, 0, 0.2)`,
}}
>
<div style={{ fontSize: '32px', color: '#fff' }}>
{getDeviceTypeIcon(device.type)}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '24px', fontWeight: 600, marginBottom: '8px' }}>
</div>
<div style={{ flex: 1 }}>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '12px',
marginBottom: '8px',
}}
>
<h2
style={{
margin: 0,
fontSize: '24px',
fontWeight: 700,
color: '#fff',
textShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
}}
>
{device.name}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px', opacity: 0.9 }}>
<span>{getTypeLabel(device.type)}</span>
<span>|</span>
<span>{device.deviceId}</span>
<span>|</span>
</h2>
{device.status && (
<Tag
color={device.status ? getStatusConfig(device.status).badgeColor : 'default'}
style={{ margin: 0 }}
style={{
backgroundColor: 'rgba(255, 255, 255, 0.2)',
border: '1px solid rgba(255, 255, 255, 0.3)',
color: '#fff',
borderRadius: borderRadius.round,
padding: '2px 12px',
fontWeight: 500,
display: 'flex',
alignItems: 'center',
gap: '6px',
}}
>
{device.status ? getStatusConfig(device.status).text : '-'}
{getStatusIcon(device.status)}
{getStatusConfig(device.status).text}
</Tag>
</div>
)}
</div>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: '16px',
opacity: 0.9,
fontSize: '14px',
}}
>
<span
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
padding: '4px 12px',
borderRadius: borderRadius.round,
}}
>
<AppstoreOutlined style={{ fontSize: '12px' }} />
{getTypeLabel(device.type)}
</span>
<span
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
padding: '4px 12px',
borderRadius: borderRadius.round,
}}
>
<span style={{ fontFamily: 'monospace', fontSize: '13px' }}>{device.deviceId}</span>
</span>
{device.model && (
<span
style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
padding: '4px 12px',
borderRadius: borderRadius.round,
}}
>
{device.model}
</span>
)}
</div>
</div>
</div>
</div>
<div style={{ padding: '20px 24px' }}>
<Card
size="small"
title={<span style={{ fontWeight: 600 }}>基本信息</span>}
style={{ marginBottom: '16px', borderRadius: '8px' }}
>
<Row gutter={[24, 16]}>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>设备型号</div>
<div style={{ fontWeight: 500 }}>{device.model || '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>序列号</div>
<div style={{ fontWeight: 500 }}>{device.serialNumber || '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>IP地址</div>
<div style={{ fontWeight: 500 }}>{device.ipAddress || '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>所在机房</div>
<div style={{ fontWeight: 500 }}>{device.Rack?.Room?.name || '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>所在机柜</div>
<div style={{ fontWeight: 500 }}>{device.Rack?.name || '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>位置(U)</div>
<div style={{ fontWeight: 500 }}>U{device.position || '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>高度</div>
<div style={{ fontWeight: 500 }}>{device.height ? `${device.height}U` : '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>功率</div>
<div style={{ fontWeight: 500 }}>{device.powerConsumption ? `${device.powerConsumption}W` : '-'}</div>
</Col>
<Col span={8}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>状态</div>
<div
style={{
fontWeight: 500,
color: device.status ? getStatusConfig(device.status).color : '#666',
}}
>
{device.status ? getStatusConfig(device.status).text : '-'}
</div>
</Col>
</Row>
</Card>
<Card
size="small"
title={<span style={{ fontWeight: 600 }}>维保信息</span>}
style={{ marginBottom: '16px', borderRadius: '8px' }}
>
<Row gutter={[24, 16]}>
<Col span={12}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>购买日期</div>
<div style={{ fontWeight: 500 }}>
{device.purchaseDate
? new Date(device.purchaseDate).toLocaleDateString('zh-CN')
: '-'}
</div>
</Col>
<Col span={12}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>保修到期</div>
<div
style={{
fontWeight:
device.warrantyExpiry && new Date(device.warrantyExpiry) < new Date()
? 600
: 500,
color:
device.warrantyExpiry && new Date(device.warrantyExpiry) < new Date()
? '#d93025'
: '#333',
}}
>
{device.warrantyExpiry
? new Date(device.warrantyExpiry).toLocaleDateString('zh-CN')
: '-'}
</div>
</Col>
</Row>
</Card>
{device.description && (
<Card
size="small"
title={<span style={{ fontWeight: 600 }}>描述</span>}
style={{ marginBottom: '16px', borderRadius: '8px' }}
<div style={{ padding: '0 24px', backgroundColor: colors.background.secondary }}>
<div
style={{
display: 'flex',
gap: '8px',
paddingTop: '16px',
}}
>
{tabItems.map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`tab-btn ${activeTab === tab.key ? 'active' : ''}`}
style={{
padding: '10px 20px',
border: 'none',
background: 'transparent',
cursor: 'pointer',
fontSize: '14px',
fontWeight: 500,
color: activeTab === tab.key ? colors.primary.main : colors.text.secondary,
borderRadius: borderRadius.medium,
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
<div style={{ whiteSpace: 'pre-wrap', lineHeight: 1.6 }}>{device.description}</div>
</Card>
)}
{tab.label}
{activeTab === tab.key && (
<span
style={{
width: '4px',
height: '4px',
borderRadius: '50%',
backgroundColor: colors.primary.main,
}}
/>
)}
</button>
))}
</div>
</div>
{device.customFields && Object.keys(device.customFields).length > 0 && (
<Card
size="small"
title={<span style={{ fontWeight: 600 }}>自定义字段</span>}
style={{ borderRadius: '8px' }}
<div style={{ padding: '24px 32px', maxHeight: '480px', overflowY: 'auto' }}>
{activeTab === 'basic' && (
<InfoCard
title="基本信息"
icon={<AppstoreOutlined />}
style={{ animation: 'fadeInUp 0.3s ease-out' }}
>
<Row gutter={[24, 16]}>
<Col span={8}>
<InfoItem label="设备ID" value={device.deviceId} />
</Col>
<Col span={8}>
<InfoItem label="设备名称" value={device.name} />
</Col>
<Col span={8}>
<InfoItem label="设备类型" value={getTypeLabel(device.type)} />
</Col>
<Col span={8}>
<InfoItem label="设备型号" value={device.model} />
</Col>
<Col span={8}>
<InfoItem label="序列号" value={device.serialNumber} />
</Col>
<Col span={8}>
<InfoItem label="IP地址" value={device.ipAddress} />
</Col>
<Col span={8}>
<InfoItem
label="设备状态"
value={device.status ? getStatusConfig(device.status).text : '-'}
status={
device.status === 'fault'
? 'danger'
: device.status === 'maintenance'
? 'warning'
: undefined
}
/>
</Col>
<Col span={8}>
<InfoItem label="功率消耗" value={device.powerConsumption ? `${device.powerConsumption}W` : '-'} />
</Col>
<Col span={8}>
<InfoItem label="设备高度" value={device.height ? `${device.height}U` : '-'} />
</Col>
</Row>
{device.description && (
<div style={{ marginTop: '16px', paddingTop: '16px', borderTop: `1px dashed ${colors.border.light}` }}>
<InfoItem label="设备描述" value={device.description} fullWidth />
</div>
)}
{device.customFields && Object.keys(device.customFields).length > 0 && (
<div style={{ marginTop: '16px', paddingTop: '16px', borderTop: `1px dashed ${colors.border.light}` }}>
<div
style={{
fontSize: '12px',
color: colors.text.tertiary,
marginBottom: '12px',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
自定义字段
</div>
<Row gutter={[16, 16]}>
{Object.entries(device.customFields).map(([key, value]) => {
const fieldConfig = deviceFields.find((f) => f.fieldName === key);
const displayName = fieldConfig?.displayName || key;
return (
<Col span={8} key={key}>
<div
style={{
padding: '12px 16px',
background: colors.background.secondary,
borderRadius: borderRadius.medium,
border: `1px solid ${colors.border.light}`,
}}
>
<div
style={{
fontSize: '12px',
color: colors.text.tertiary,
marginBottom: '4px',
fontWeight: 500,
}}
>
{displayName}
</div>
<div
style={{
fontSize: '14px',
color: colors.text.primary,
fontWeight: 500,
}}
>
{String(value)}
</div>
</div>
</Col>
);
})}
</Row>
</div>
)}
</InfoCard>
)}
{activeTab === 'location' && (
<InfoCard
title="位置信息"
icon={<EnvironmentOutlined />}
style={{ animation: 'fadeInUp 0.3s ease-out' }}
>
<Row gutter={[24, 16]}>
<Col span={8}>
<InfoItem label="所在机房" value={device.Rack?.Room?.name} />
</Col>
<Col span={8}>
<InfoItem label="所在机柜" value={device.Rack?.name} />
</Col>
<Col span={8}>
<InfoItem label="机柜编号" value={device.Rack?.rackId} />
</Col>
<Col span={8}>
<InfoItem label="安装位置" value={device.position ? `U${device.position}` : '-'} />
</Col>
</Row>
</InfoCard>
)}
{activeTab === 'maintenance' && (
<>
<InfoCard
title="购买与维保信息"
icon={<CalendarOutlined />}
style={{ animation: 'fadeInUp 0.3s ease-out', marginBottom: '20px' }}
>
<Row gutter={[24, 16]}>
{Object.entries(device.customFields).map(([key, value]) => {
const fieldConfig = deviceFields.find((f) => f.fieldName === key);
const displayName = fieldConfig?.displayName || key;
return (
<Col span={8} key={key}>
<div style={{ color: '#666', fontSize: '12px', marginBottom: '4px' }}>
{displayName}
</div>
<div style={{ fontWeight: 500 }}>{String(value)}</div>
</Col>
);
})}
<Col span={8}>
<InfoItem label="购买日期" value={device.purchaseDate ? new Date(device.purchaseDate).toLocaleDateString('zh-CN') : '-'} />
</Col>
<Col span={8}>
<InfoItem
label="保修到期"
value={device.warrantyExpiry ? new Date(device.warrantyExpiry).toLocaleDateString('zh-CN') : '-'}
status={isWarrantyExpired ? 'danger' : undefined}
/>
</Col>
{warrantyDaysLeft !== null && warrantyDaysLeft > 0 && (
<Col span={24}>
<div
style={{
fontSize: '12px',
color: colors.text.tertiary,
marginBottom: '8px',
fontWeight: 500,
textTransform: 'uppercase',
letterSpacing: '0.5px',
}}
>
保修剩余天数
</div>
<Progress
percent={Math.min(Math.round((warrantyDaysLeft / 365) * 100), 100)}
format={() => `${warrantyDaysLeft}`}
strokeColor={warrantyDaysLeft < 30 ? colors.error.main : colors.primary.main}
trailColor={colors.border.light}
size="small"
/>
</Col>
)}
</Row>
</Card>
)}
{isWarrantyExpired && (
<div
style={{
marginTop: '16px',
padding: '12px',
backgroundColor: colors.error.bg,
borderRadius: borderRadius.medium,
border: `1px solid ${colors.error.main}30`,
display: 'flex',
alignItems: 'center',
gap: '10px',
}}
>
<ExclamationCircleOutlined style={{ color: colors.error.main, fontSize: '18px' }} />
<span style={{ color: colors.error.main, fontSize: '13px', fontWeight: 500 }}>
设备已过保建议续保
</span>
</div>
)}
</InfoCard>
</>
)}
</div>
<div
style={{
padding: '20px 32px',
borderTop: `1px solid ${colors.border.light}`,
background: colors.background.primary,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<div style={{ display: 'flex', gap: '12px' }}>
<Button
className="action-btn"
icon={<FileTextOutlined />}
onClick={() => onViewTickets(device)}
style={{
height: '40px',
borderRadius: borderRadius.medium,
border: `1px solid ${colors.border.light}`,
display: 'flex',
alignItems: 'center',
gap: '8px',
fontWeight: 500,
}}
>
查看工单
</Button>
<Button
className="action-btn"
type="primary"
icon={<PlusOutlined />}
onClick={() => onCreateTicket(device)}
style={{
height: '40px',
borderRadius: borderRadius.medium,
background: colors.primary.gradient,
border: 'none',
display: 'flex',
alignItems: 'center',
gap: '8px',
fontWeight: 500,
boxShadow: shadows.small,
}}
>
创建工单
</Button>
</div>
<div style={{ display: 'flex', gap: '12px' }}>
<Button
onClick={onClose}
style={{
height: '40px',
borderRadius: borderRadius.medium,
border: `1px solid ${colors.border.light}`,
fontWeight: 500,
}}
>
关闭
</Button>
<Button
className="action-btn"
type="primary"
icon={<EditOutlined />}
onClick={() => {
onClose();
onEdit(device);
}}
style={{
height: '40px',
borderRadius: borderRadius.medium,
background: colors.primary.gradient,
border: 'none',
display: 'flex',
alignItems: 'center',
gap: '8px',
fontWeight: 500,
boxShadow: shadows.small,
}}
>
编辑设备
</Button>
</div>
</div>
</Modal>