feat(设备管理): 重构设备管理页面并优化初始化逻辑
This commit is contained in:
@@ -160,21 +160,20 @@ async function initDeviceFields() {
|
|||||||
console.log('开始初始化设备字段配置...');
|
console.log('开始初始化设备字段配置...');
|
||||||
// 注意:数据库连接和表结构同步已在 server.js 中完成,这里直接初始化数据
|
// 注意:数据库连接和表结构同步已在 server.js 中完成,这里直接初始化数据
|
||||||
|
|
||||||
// 批量创建默认字段
|
// 批量创建默认字段(只创建不存在的字段,保留用户自定义配置)
|
||||||
for (const field of defaultDeviceFields) {
|
for (const field of defaultDeviceFields) {
|
||||||
// 检查字段是否已存在
|
// 检查字段是否已存在
|
||||||
const existingField = await DeviceField.findOne({
|
const existingField = await DeviceField.findOne({
|
||||||
where: { fieldName: field.fieldName }
|
where: { fieldName: field.fieldName }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingField) {
|
if (!existingField) {
|
||||||
// 更新已有字段
|
// 只创建新字段,不更新已存在的字段
|
||||||
await existingField.update(field);
|
|
||||||
console.log(`更新字段: ${field.displayName}`);
|
|
||||||
} else {
|
|
||||||
// 创建新字段
|
|
||||||
await DeviceField.create(field);
|
await DeviceField.create(field);
|
||||||
console.log(`创建字段: ${field.displayName}`);
|
console.log(`创建字段: ${field.displayName}`);
|
||||||
|
} else {
|
||||||
|
// 已存在的字段跳过,保留用户自定义配置
|
||||||
|
console.log(`跳过已存在字段: ${field.displayName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+52
-12
@@ -1,7 +1,7 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
const { sequelize } = require('../db'); // Import sequelize for transactions
|
const { sequelize, dbDialect } = require('../db'); // Import sequelize and dbDialect for transactions
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
@@ -34,18 +34,53 @@ router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
|
|||||||
// 构建查询条件
|
// 构建查询条件
|
||||||
const where = {};
|
const where = {};
|
||||||
|
|
||||||
// 关键词搜索(所有文本字段)
|
// 关键词搜索(所有文本字段 + 自定义字段)
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
where[Op.or] = [
|
console.log('搜索关键词:', keyword);
|
||||||
{ deviceId: { [Op.like]: `%${keyword}%` } },
|
console.log('数据库类型:', dbDialect);
|
||||||
{ name: { [Op.like]: `%${keyword}%` } },
|
|
||||||
{ type: { [Op.like]: `%${keyword}%` } },
|
// 转义关键词中的特殊字符,防止SQL注入
|
||||||
{ model: { [Op.like]: `%${keyword}%` } },
|
const escapedKeyword = keyword.replace(/'/g, "''");
|
||||||
{ serialNumber: { [Op.like]: `%${keyword}%` } },
|
|
||||||
{ position: { [Op.like]: `%${keyword}%` } },
|
// 基础字段搜索条件(只包含文本类型字段)
|
||||||
{ ipAddress: { [Op.like]: `%${keyword}%` } },
|
const searchConditions = [
|
||||||
{ description: { [Op.like]: `%${keyword}%` } }
|
{ deviceId: { [Op.like]: `%${escapedKeyword}%` } },
|
||||||
|
{ name: { [Op.like]: `%${escapedKeyword}%` } },
|
||||||
|
{ type: { [Op.like]: `%${escapedKeyword}%` } },
|
||||||
|
{ model: { [Op.like]: `%${escapedKeyword}%` } },
|
||||||
|
{ serialNumber: { [Op.like]: `%${escapedKeyword}%` } },
|
||||||
|
{ ipAddress: { [Op.like]: `%${escapedKeyword}%` } },
|
||||||
|
{ description: { [Op.like]: `%${escapedKeyword}%` } }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 动态获取文本类型的自定义字段
|
||||||
|
const customFields = await DeviceField.findAll({
|
||||||
|
where: {
|
||||||
|
isSystem: false,
|
||||||
|
fieldType: { [Op.in]: ['string', 'textarea'] }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('找到的自定义字段:', customFields.map(f => f.fieldName));
|
||||||
|
|
||||||
|
// 构建自定义字段搜索条件(使用原始SQL,兼容SQLite和MySQL)
|
||||||
|
if (customFields.length > 0) {
|
||||||
|
// 使用原始SQL查询JSON字段
|
||||||
|
const jsonConditions = customFields.map(field => {
|
||||||
|
const fieldName = field.fieldName;
|
||||||
|
// 使用 sequelize.literal 构建原始SQL条件
|
||||||
|
if (dbDialect === 'mysql') {
|
||||||
|
return sequelize.literal(`JSON_EXTRACT(customFields, '$."${fieldName}"') LIKE '%${escapedKeyword}%'`);
|
||||||
|
} else {
|
||||||
|
return sequelize.literal(`json_extract(customFields, '$.${fieldName}') LIKE '%${escapedKeyword}%'`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
searchConditions.push(...jsonConditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并所有搜索条件
|
||||||
|
where[Op.or] = searchConditions;
|
||||||
|
console.log('搜索条件数量:', searchConditions.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态筛选
|
// 状态筛选
|
||||||
@@ -85,7 +120,12 @@ router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
|
|||||||
pageSize: parseInt(pageSize)
|
pageSize: parseInt(pageSize)
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
console.error('搜索设备失败:', error);
|
||||||
|
console.error('错误详情:', error.message);
|
||||||
|
if (error.sql) {
|
||||||
|
console.error('SQL:', error.sql);
|
||||||
|
}
|
||||||
|
res.status(500).json({ error: error.message, sql: error.sql });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* 主题配置文件
|
||||||
|
* 集中管理应用的颜色、阴影、间距等设计令牌
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const designTokens = {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
main: '#667eea',
|
||||||
|
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
light: '#8b9ff0',
|
||||||
|
dark: '#4f5db8'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
main: '#10b981',
|
||||||
|
gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
||||||
|
light: '#34d399',
|
||||||
|
dark: '#047857'
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
main: '#f59e0b',
|
||||||
|
gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
||||||
|
light: '#fbbf24',
|
||||||
|
dark: '#b45309'
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
main: '#ef4444',
|
||||||
|
gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
|
||||||
|
light: '#f87171',
|
||||||
|
dark: '#b91c1c'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: '#1e293b',
|
||||||
|
secondary: '#64748b',
|
||||||
|
tertiary: '#94a3b8',
|
||||||
|
inverse: '#ffffff'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
primary: '#ffffff',
|
||||||
|
secondary: '#f8fafc',
|
||||||
|
tertiary: '#f1f5f9',
|
||||||
|
dark: '#1e293b'
|
||||||
|
},
|
||||||
|
border: {
|
||||||
|
light: '#e2e8f0',
|
||||||
|
medium: '#cbd5e1',
|
||||||
|
dark: '#94a3b8'
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
server: '#3b82f6',
|
||||||
|
switch: '#22c55e',
|
||||||
|
router: '#f59e0b',
|
||||||
|
storage: '#8b5cf6',
|
||||||
|
other: '#64748b'
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
normal: '#10b981',
|
||||||
|
running: '#10b981',
|
||||||
|
warning: '#f59e0b',
|
||||||
|
error: '#ef4444',
|
||||||
|
fault: '#ef4444',
|
||||||
|
offline: '#6b7280',
|
||||||
|
maintenance: '#3b82f6'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shadows: {
|
||||||
|
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||||
|
medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
||||||
|
large: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
|
||||||
|
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
|
||||||
|
glow: '0 0 20px rgba(102, 126, 234, 0.3)'
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
small: '6px',
|
||||||
|
medium: '10px',
|
||||||
|
large: '16px',
|
||||||
|
xl: '24px',
|
||||||
|
round: '50%'
|
||||||
|
},
|
||||||
|
transitions: {
|
||||||
|
fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
normal: '300ms cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
slow: '500ms cubic-bezier(0.4, 0, 0.2, 1)'
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
xs: '4px',
|
||||||
|
sm: '8px',
|
||||||
|
md: '16px',
|
||||||
|
lg: '24px',
|
||||||
|
xl: '32px'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default designTokens;
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* 设备管理页面常量配置
|
||||||
|
* 集中管理分页、延迟、字段等配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
export const PAGINATION_CONFIG = {
|
||||||
|
// 默认每页条数
|
||||||
|
defaultPageSize: 10,
|
||||||
|
// 可选每页条数
|
||||||
|
pageSizeOptions: ['10', '20', '30', '50', '100'],
|
||||||
|
// 显示快速跳转
|
||||||
|
showSizeChanger: true,
|
||||||
|
// 显示总数
|
||||||
|
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索防抖延迟(毫秒)
|
||||||
|
export const DEBOUNCE_DELAY = 300;
|
||||||
|
|
||||||
|
// 表格滚动配置
|
||||||
|
export const TABLE_SCROLL_CONFIG = {
|
||||||
|
x: 'max-content',
|
||||||
|
y: 'calc(100vh - 400px)'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 默认设备字段配置
|
||||||
|
export const DEFAULT_DEVICE_FIELDS = [
|
||||||
|
{
|
||||||
|
fieldName: 'deviceId',
|
||||||
|
displayName: '设备ID',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'name',
|
||||||
|
displayName: '设备名称',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'type',
|
||||||
|
displayName: '设备类型',
|
||||||
|
fieldType: 'select',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
options: [
|
||||||
|
{ value: 'server', label: '服务器' },
|
||||||
|
{ value: 'switch', label: '交换机' },
|
||||||
|
{ value: 'router', label: '路由器' },
|
||||||
|
{ value: 'storage', label: '存储设备' },
|
||||||
|
{ value: 'other', label: '其他设备' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'model',
|
||||||
|
displayName: '设备型号',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: false,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'serialNumber',
|
||||||
|
displayName: '序列号',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'rackId',
|
||||||
|
displayName: '所在机柜',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'position',
|
||||||
|
displayName: '位置(U)',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'height',
|
||||||
|
displayName: '高度(U)',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'powerConsumption',
|
||||||
|
displayName: '功率(W)',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: false,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'ipAddress',
|
||||||
|
displayName: 'IP地址',
|
||||||
|
fieldType: 'text',
|
||||||
|
required: false,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'status',
|
||||||
|
displayName: '状态',
|
||||||
|
fieldType: 'select',
|
||||||
|
required: true,
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
options: [
|
||||||
|
{ value: 'running', label: '运行中' },
|
||||||
|
{ value: 'maintenance', label: '维护中' },
|
||||||
|
{ value: 'offline', label: '离线' },
|
||||||
|
{ value: 'fault', label: '故障' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'purchaseDate',
|
||||||
|
displayName: '购买日期',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: false,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'warrantyExpiry',
|
||||||
|
displayName: '保修日期',
|
||||||
|
fieldType: 'date',
|
||||||
|
required: false,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'description',
|
||||||
|
displayName: '备注',
|
||||||
|
fieldType: 'textarea',
|
||||||
|
required: false,
|
||||||
|
visible: true,
|
||||||
|
editable: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 基础字段名称列表(用于导入导出时排除自定义字段)
|
||||||
|
export const BASE_FIELD_NAMES = [
|
||||||
|
'deviceId',
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'model',
|
||||||
|
'serialNumber',
|
||||||
|
'rackId',
|
||||||
|
'position',
|
||||||
|
'height',
|
||||||
|
'powerConsumption',
|
||||||
|
'ipAddress',
|
||||||
|
'status',
|
||||||
|
'purchaseDate',
|
||||||
|
'warrantyExpiry',
|
||||||
|
'description'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 系统字段列表(不可编辑)
|
||||||
|
export const SYSTEM_FIELDS = ['createdAt', 'updatedAt', 'Rack', 'Room', 'customFields'];
|
||||||
|
|
||||||
|
// 固定字段列表(表格中必须显示的字段)
|
||||||
|
export const FIXED_FIELDS = ['name', 'type', 'status', 'rackId'];
|
||||||
|
|
||||||
|
// 导入配置
|
||||||
|
export const IMPORT_CONFIG = {
|
||||||
|
// 支持的文件类型
|
||||||
|
acceptedFileTypes: '.csv,.xlsx,.xls',
|
||||||
|
// 最大文件大小(MB)
|
||||||
|
maxFileSize: 10,
|
||||||
|
// 单次最大导入条数
|
||||||
|
maxImportCount: 5000,
|
||||||
|
// 编码格式
|
||||||
|
encoding: 'gbk'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出配置
|
||||||
|
export const EXPORT_CONFIG = {
|
||||||
|
// 默认文件名前缀
|
||||||
|
fileNamePrefix: '设备列表',
|
||||||
|
// 日期格式
|
||||||
|
dateFormat: 'YYYY-MM-DD_HH-mm-ss',
|
||||||
|
// 支持的导出格式
|
||||||
|
formats: ['xlsx', 'csv']
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模态框配置
|
||||||
|
export const MODAL_CONFIG = {
|
||||||
|
// 添加设备模态框宽度
|
||||||
|
addModalWidth: 900,
|
||||||
|
// 编辑设备模态框宽度
|
||||||
|
editModalWidth: 900,
|
||||||
|
// 详情模态框宽度
|
||||||
|
detailModalWidth: 700,
|
||||||
|
// 导入模态框宽度
|
||||||
|
importModalWidth: 600,
|
||||||
|
// 导出模态框宽度
|
||||||
|
exportModalWidth: 500
|
||||||
|
};
|
||||||
|
|
||||||
|
// 统计卡片配置
|
||||||
|
export const STATS_CONFIG = {
|
||||||
|
// 显示的运行中设备数量上限(超过显示为 99+)
|
||||||
|
maxRunningDisplay: 99,
|
||||||
|
// 显示的维护中设备数量上限
|
||||||
|
maxMaintenanceDisplay: 99,
|
||||||
|
// 显示的故障设备数量上限
|
||||||
|
maxFaultDisplay: 99
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设备类型选项(用于筛选)
|
||||||
|
export const DEVICE_TYPE_OPTIONS = [
|
||||||
|
{ value: 'all', label: '全部类型' },
|
||||||
|
{ value: 'server', label: '服务器' },
|
||||||
|
{ value: 'switch', label: '交换机' },
|
||||||
|
{ value: 'router', label: '路由器' },
|
||||||
|
{ value: 'storage', label: '存储设备' },
|
||||||
|
{ value: 'other', label: '其他设备' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 设备状态选项(用于筛选)
|
||||||
|
export const DEVICE_STATUS_OPTIONS = [
|
||||||
|
{ value: 'all', label: '全部状态' },
|
||||||
|
{ value: 'running', label: '运行中' },
|
||||||
|
{ value: 'maintenance', label: '维护中' },
|
||||||
|
{ value: 'offline', label: '离线' },
|
||||||
|
{ value: 'fault', label: '故障' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 表格列宽配置
|
||||||
|
export const COLUMN_WIDTH_CONFIG = {
|
||||||
|
deviceId: 100,
|
||||||
|
name: 150,
|
||||||
|
type: 100,
|
||||||
|
model: 120,
|
||||||
|
serialNumber: 150,
|
||||||
|
rackId: 150,
|
||||||
|
position: 80,
|
||||||
|
height: 80,
|
||||||
|
powerConsumption: 100,
|
||||||
|
ipAddress: 120,
|
||||||
|
status: 100,
|
||||||
|
purchaseDate: 110,
|
||||||
|
warrantyExpiry: 110,
|
||||||
|
description: 200,
|
||||||
|
action: 150
|
||||||
|
};
|
||||||
|
|
||||||
|
// 空状态配置
|
||||||
|
export const EMPTY_STATE_CONFIG = {
|
||||||
|
description: '暂无设备数据',
|
||||||
|
image: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 操作按钮配置
|
||||||
|
export const ACTION_BUTTON_CONFIG = {
|
||||||
|
// 批量操作阈值(超过此数量显示确认对话框)
|
||||||
|
batchConfirmThreshold: 10,
|
||||||
|
// 批量删除确认消息
|
||||||
|
batchDeleteConfirmMessage: (count) => `确定要删除选中的 ${count} 个设备吗?此操作不可恢复。`,
|
||||||
|
// 单个删除确认消息
|
||||||
|
singleDeleteConfirmMessage: (name) => `确定要删除设备 "${name}" 吗?此操作不可恢复。`
|
||||||
|
};
|
||||||
@@ -3,100 +3,61 @@ import { Table, Button, Modal, Form, Input, Select, DatePicker, message, Card, S
|
|||||||
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined, DownloadOutlined, SettingOutlined, UndoOutlined, CloudServerOutlined, SafetyOutlined, DatabaseOutlined, AppstoreOutlined, MoreOutlined, ReloadOutlined, ExportOutlined, FileExcelOutlined, SwapOutlined } from '@ant-design/icons';
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, UploadOutlined, DownloadOutlined, SettingOutlined, UndoOutlined, CloudServerOutlined, SafetyOutlined, DatabaseOutlined, AppstoreOutlined, MoreOutlined, ReloadOutlined, ExportOutlined, FileExcelOutlined, SwapOutlined } from '@ant-design/icons';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { designTokens } from '../config/theme';
|
||||||
|
import {
|
||||||
|
PAGINATION_CONFIG,
|
||||||
|
DEBOUNCE_DELAY,
|
||||||
|
TABLE_SCROLL_CONFIG,
|
||||||
|
DEFAULT_DEVICE_FIELDS,
|
||||||
|
BASE_FIELD_NAMES,
|
||||||
|
SYSTEM_FIELDS,
|
||||||
|
FIXED_FIELDS,
|
||||||
|
IMPORT_CONFIG,
|
||||||
|
EXPORT_CONFIG,
|
||||||
|
MODAL_CONFIG,
|
||||||
|
DEVICE_TYPE_OPTIONS,
|
||||||
|
DEVICE_STATUS_OPTIONS,
|
||||||
|
COLUMN_WIDTH_CONFIG,
|
||||||
|
EMPTY_STATE_CONFIG
|
||||||
|
} from '../constants/deviceManagementConstants';
|
||||||
|
import {
|
||||||
|
pageContainerStyle,
|
||||||
|
headerStyle,
|
||||||
|
titleRowStyle,
|
||||||
|
titleSectionStyle,
|
||||||
|
titleIconStyle,
|
||||||
|
titleTextStyle,
|
||||||
|
pageTitleStyle,
|
||||||
|
pageSubtitleStyle,
|
||||||
|
primaryActionStyle,
|
||||||
|
secondaryActionStyle,
|
||||||
|
statsRowStyle,
|
||||||
|
statCardStyle,
|
||||||
|
statValueStyle,
|
||||||
|
statLabelStyle,
|
||||||
|
statCardRunningStyle,
|
||||||
|
statCardMaintenanceStyle,
|
||||||
|
statCardFaultStyle,
|
||||||
|
cardStyle,
|
||||||
|
filterCardStyle,
|
||||||
|
modalHeaderStyle,
|
||||||
|
tableStyles,
|
||||||
|
searchInputStyle,
|
||||||
|
selectStyle,
|
||||||
|
refreshButtonStyle,
|
||||||
|
searchButtonStyle,
|
||||||
|
resetButtonStyle,
|
||||||
|
importModalStyles,
|
||||||
|
detailModalStyles,
|
||||||
|
exportModalStyles,
|
||||||
|
generateGlobalStyles
|
||||||
|
} from '../styles/deviceManagementStyles';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
const designTokens = {
|
|
||||||
colors: {
|
|
||||||
primary: {
|
|
||||||
main: '#667eea',
|
|
||||||
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
||||||
light: '#8b9ff0',
|
|
||||||
dark: '#4f5db8'
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
main: '#10b981',
|
|
||||||
gradient: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
|
||||||
light: '#34d399',
|
|
||||||
dark: '#047857'
|
|
||||||
},
|
|
||||||
warning: {
|
|
||||||
main: '#f59e0b',
|
|
||||||
gradient: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
|
|
||||||
light: '#fbbf24',
|
|
||||||
dark: '#b45309'
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
main: '#ef4444',
|
|
||||||
gradient: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
|
|
||||||
light: '#f87171',
|
|
||||||
dark: '#b91c1c'
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
primary: '#1e293b',
|
|
||||||
secondary: '#64748b',
|
|
||||||
tertiary: '#94a3b8',
|
|
||||||
inverse: '#ffffff'
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
primary: '#ffffff',
|
|
||||||
secondary: '#f8fafc',
|
|
||||||
tertiary: '#f1f5f9',
|
|
||||||
dark: '#1e293b'
|
|
||||||
},
|
|
||||||
border: {
|
|
||||||
light: '#e2e8f0',
|
|
||||||
medium: '#cbd5e1',
|
|
||||||
dark: '#94a3b8'
|
|
||||||
},
|
|
||||||
device: {
|
|
||||||
server: '#3b82f6',
|
|
||||||
switch: '#22c55e',
|
|
||||||
router: '#f59e0b',
|
|
||||||
storage: '#8b5cf6',
|
|
||||||
other: '#64748b'
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
normal: '#10b981',
|
|
||||||
running: '#10b981',
|
|
||||||
warning: '#f59e0b',
|
|
||||||
error: '#ef4444',
|
|
||||||
fault: '#ef4444',
|
|
||||||
offline: '#6b7280',
|
|
||||||
maintenance: '#3b82f6'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shadows: {
|
|
||||||
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
||||||
medium: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
|
|
||||||
large: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
|
|
||||||
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
|
|
||||||
glow: '0 0 20px rgba(102, 126, 234, 0.3)'
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
small: '6px',
|
|
||||||
medium: '10px',
|
|
||||||
large: '16px',
|
|
||||||
xl: '24px',
|
|
||||||
round: '50%'
|
|
||||||
},
|
|
||||||
transitions: {
|
|
||||||
fast: '150ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
||||||
normal: '300ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
||||||
slow: '500ms cubic-bezier(0.4, 0, 0.2, 1)'
|
|
||||||
},
|
|
||||||
spacing: {
|
|
||||||
xs: '4px',
|
|
||||||
sm: '8px',
|
|
||||||
md: '16px',
|
|
||||||
lg: '24px',
|
|
||||||
xl: '32px'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 防抖 Hook
|
// 防抖 Hook
|
||||||
function useDebounce(value, delay) {
|
function useDebounce(value, delay = DEBOUNCE_DELAY) {
|
||||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -165,25 +126,8 @@ const formatDate = (date, fieldName) => {
|
|||||||
return formattedDate;
|
return formattedDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 默认设备字段配置
|
// 使用从常量文件导入的默认设备字段配置
|
||||||
const defaultDeviceFields = [
|
const defaultDeviceFields = DEFAULT_DEVICE_FIELDS;
|
||||||
{ fieldName: 'deviceId', displayName: '设备ID', fieldType: 'string', required: false, order: 1, visible: false },
|
|
||||||
{ fieldName: 'name', displayName: '设备名称', fieldType: 'string', required: true, order: 2, visible: true },
|
|
||||||
{ fieldName: 'type', displayName: '设备类型', fieldType: 'select', required: true, order: 3, visible: true,
|
|
||||||
options: [{ value: 'server', label: '服务器' }, { value: 'switch', label: '交换机' }, { value: 'router', label: '路由器' }, { value: 'storage', label: '存储设备' }, { value: 'other', label: '其他设备' }] },
|
|
||||||
{ fieldName: 'model', displayName: '型号', fieldType: 'string', required: true, order: 4, visible: true },
|
|
||||||
{ fieldName: 'serialNumber', displayName: '序列号', fieldType: 'string', required: true, order: 5, visible: true },
|
|
||||||
{ fieldName: 'rackId', displayName: '所在机柜', fieldType: 'select', required: true, order: 6, visible: true },
|
|
||||||
{ fieldName: 'position', displayName: '位置(U)', fieldType: 'number', required: true, order: 7, visible: true },
|
|
||||||
{ fieldName: 'height', displayName: '高度(U)', fieldType: 'number', required: true, order: 8, visible: true },
|
|
||||||
{ fieldName: 'powerConsumption', displayName: '功率(W)', fieldType: 'number', required: true, order: 9, visible: true },
|
|
||||||
{ fieldName: 'status', displayName: '状态', fieldType: 'select', required: true, order: 10, visible: true,
|
|
||||||
options: [{ value: 'running', label: '运行中' }, { value: 'maintenance', label: '维护中' }, { value: 'offline', label: '离线' }, { value: 'fault', label: '故障' }] },
|
|
||||||
{ fieldName: 'purchaseDate', displayName: '购买日期', fieldType: 'date', required: false, order: 11, visible: true },
|
|
||||||
{ fieldName: 'warrantyExpiry', displayName: '保修到期', fieldType: 'date', required: false, order: 12, visible: true },
|
|
||||||
{ fieldName: 'ipAddress', displayName: 'IP地址', fieldType: 'string', required: false, order: 13, visible: true },
|
|
||||||
{ fieldName: 'description', displayName: '描述', fieldType: 'textarea', required: false, order: 14, visible: true }
|
|
||||||
];
|
|
||||||
|
|
||||||
// 简单的可调整列宽的表头组件
|
// 简单的可调整列宽的表头组件
|
||||||
const ResizableTitle = (props) => {
|
const ResizableTitle = (props) => {
|
||||||
@@ -263,14 +207,14 @@ function DeviceManagement() {
|
|||||||
const [status, setStatus] = useState('all');
|
const [status, setStatus] = useState('all');
|
||||||
const [type, setType] = useState('all');
|
const [type, setType] = useState('all');
|
||||||
const [searchForm] = Form.useForm();
|
const [searchForm] = Form.useForm();
|
||||||
// 分页状态
|
// 分页状态 - 使用常量配置
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 10,
|
pageSize: PAGINATION_CONFIG.defaultPageSize,
|
||||||
total: 0,
|
total: 0,
|
||||||
pageSizeOptions: ['10', '20', '30', '50', '100'],
|
pageSizeOptions: PAGINATION_CONFIG.pageSizeOptions,
|
||||||
showSizeChanger: true,
|
showSizeChanger: PAGINATION_CONFIG.showSizeChanger,
|
||||||
showTotal: (total) => `共 ${total} 条记录`
|
showTotal: PAGINATION_CONFIG.showTotal
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设备字段配置
|
// 设备字段配置
|
||||||
@@ -312,8 +256,8 @@ function DeviceManagement() {
|
|||||||
// 列宽状态
|
// 列宽状态
|
||||||
const [columnWidths, setColumnWidths] = useState({});
|
const [columnWidths, setColumnWidths] = useState({});
|
||||||
|
|
||||||
// 防抖搜索关键词
|
// 防抖搜索关键词 - 使用常量配置的延迟时间
|
||||||
const debouncedKeyword = useDebounce(keyword, 300);
|
const debouncedKeyword = useDebounce(keyword, DEBOUNCE_DELAY);
|
||||||
|
|
||||||
// 使用 useMemo 缓存筛选后的设备数据(现在直接使用 allDevices,因为后端已经处理了筛选)
|
// 使用 useMemo 缓存筛选后的设备数据(现在直接使用 allDevices,因为后端已经处理了筛选)
|
||||||
const filteredDevicesMemo = useMemo(() => {
|
const filteredDevicesMemo = useMemo(() => {
|
||||||
@@ -1149,360 +1093,11 @@ function DeviceManagement() {
|
|||||||
message.success('字段配置已重置为默认值');
|
message.success('字段配置已重置为默认值');
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageContainerStyle = {
|
// 样式已从外部样式文件导入,无需在组件内定义
|
||||||
minHeight: '100vh',
|
|
||||||
background: designTokens.colors.background.secondary,
|
|
||||||
padding: designTokens.spacing.lg
|
|
||||||
};
|
|
||||||
|
|
||||||
const headerStyle = {
|
|
||||||
marginBottom: designTokens.spacing.lg
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleRowStyle = {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginBottom: designTokens.spacing.lg,
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: designTokens.spacing.md
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleSectionStyle = {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: designTokens.spacing.md
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleIconStyle = {
|
|
||||||
width: '44px',
|
|
||||||
height: '44px',
|
|
||||||
borderRadius: designTokens.borderRadius.medium,
|
|
||||||
background: designTokens.colors.primary.gradient,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
boxShadow: designTokens.shadows.medium
|
|
||||||
};
|
|
||||||
|
|
||||||
const titleTextStyle = {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '2px'
|
|
||||||
};
|
|
||||||
|
|
||||||
const pageTitleStyle = {
|
|
||||||
fontSize: '22px',
|
|
||||||
fontWeight: '700',
|
|
||||||
margin: 0,
|
|
||||||
color: designTokens.colors.text.primary,
|
|
||||||
lineHeight: 1.2
|
|
||||||
};
|
|
||||||
|
|
||||||
const pageSubtitleStyle = {
|
|
||||||
fontSize: '13px',
|
|
||||||
color: designTokens.colors.text.secondary,
|
|
||||||
margin: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const actionButtonStyle = {
|
|
||||||
height: '36px',
|
|
||||||
borderRadius: designTokens.borderRadius.small,
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: '500',
|
|
||||||
display: 'inline-flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px'
|
|
||||||
};
|
|
||||||
|
|
||||||
const primaryActionStyle = {
|
|
||||||
...actionButtonStyle,
|
|
||||||
background: designTokens.colors.primary.gradient,
|
|
||||||
border: 'none',
|
|
||||||
color: '#ffffff !important',
|
|
||||||
boxShadow: designTokens.shadows.small
|
|
||||||
};
|
|
||||||
|
|
||||||
const secondaryActionStyle = {
|
|
||||||
...actionButtonStyle,
|
|
||||||
background: designTokens.colors.background.primary,
|
|
||||||
border: `1px solid ${designTokens.colors.border.light}`,
|
|
||||||
color: designTokens.colors.text.primary
|
|
||||||
};
|
|
||||||
|
|
||||||
const dangerActionStyle = {
|
|
||||||
...actionButtonStyle,
|
|
||||||
background: designTokens.colors.error.main,
|
|
||||||
border: 'none',
|
|
||||||
color: '#ffffff'
|
|
||||||
};
|
|
||||||
|
|
||||||
const primaryButtonStyle = {
|
|
||||||
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'
|
|
||||||
};
|
|
||||||
|
|
||||||
const statsRowStyle = {
|
|
||||||
display: 'flex',
|
|
||||||
gap: designTokens.spacing.md,
|
|
||||||
marginBottom: designTokens.spacing.lg,
|
|
||||||
flexWrap: 'wrap'
|
|
||||||
};
|
|
||||||
|
|
||||||
const statCardStyle = {
|
|
||||||
flex: 1,
|
|
||||||
minWidth: '140px',
|
|
||||||
maxWidth: '200px',
|
|
||||||
padding: `${designTokens.spacing.md}px ${designTokens.spacing.lg}px`,
|
|
||||||
background: designTokens.colors.background.primary,
|
|
||||||
borderRadius: designTokens.borderRadius.medium,
|
|
||||||
border: `1px solid ${designTokens.colors.border.light}`,
|
|
||||||
boxShadow: designTokens.shadows.small,
|
|
||||||
transition: `all ${designTokens.transitions.fast}`
|
|
||||||
};
|
|
||||||
|
|
||||||
const statValueStyle = {
|
|
||||||
fontSize: '24px',
|
|
||||||
fontWeight: '700',
|
|
||||||
color: designTokens.colors.text.primary,
|
|
||||||
lineHeight: 1.2
|
|
||||||
};
|
|
||||||
|
|
||||||
const statLabelStyle = {
|
|
||||||
fontSize: '12px',
|
|
||||||
color: designTokens.colors.text.secondary,
|
|
||||||
marginTop: '4px'
|
|
||||||
};
|
|
||||||
|
|
||||||
const statCardRunningStyle = {
|
|
||||||
...statCardStyle,
|
|
||||||
borderLeft: `3px solid ${designTokens.colors.success.main}`,
|
|
||||||
background: `${designTokens.colors.success.main}08`
|
|
||||||
};
|
|
||||||
|
|
||||||
const statCardMaintenanceStyle = {
|
|
||||||
...statCardStyle,
|
|
||||||
borderLeft: `3px solid ${designTokens.colors.warning.main}`,
|
|
||||||
background: `${designTokens.colors.warning.main}08`
|
|
||||||
};
|
|
||||||
|
|
||||||
const statCardFaultStyle = {
|
|
||||||
...statCardStyle,
|
|
||||||
borderLeft: `3px solid ${designTokens.colors.error.main}`,
|
|
||||||
background: `${designTokens.colors.error.main}08`
|
|
||||||
};
|
|
||||||
|
|
||||||
const cardStyle = {
|
|
||||||
borderRadius: designTokens.borderRadius.large,
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: designTokens.shadows.medium,
|
|
||||||
overflow: 'hidden',
|
|
||||||
background: designTokens.colors.background.primary
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterCardStyle = {
|
|
||||||
borderRadius: designTokens.borderRadius.medium,
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: designTokens.shadows.small,
|
|
||||||
background: designTokens.colors.background.primary,
|
|
||||||
marginBottom: designTokens.spacing.lg
|
|
||||||
};
|
|
||||||
|
|
||||||
const modalHeaderStyle = {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: designTokens.spacing.sm,
|
|
||||||
fontSize: '18px',
|
|
||||||
fontWeight: '600'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={pageContainerStyle}>
|
<div style={pageContainerStyle}>
|
||||||
<style>{`
|
<style>{generateGlobalStyles(designTokens)}</style>
|
||||||
.device-modal .ant-modal-close {
|
|
||||||
top: 16px;
|
|
||||||
right: 24px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-modal .ant-modal-close-x {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-container {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-content {
|
|
||||||
width: 100% !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
overflow-x: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-thead {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-thead > tr > th {
|
|
||||||
white-space: normal !important;
|
|
||||||
word-break: break-word !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
font-weight: 600 !important;
|
|
||||||
line-height: 1.4 !important;
|
|
||||||
padding: 14px 12px !important;
|
|
||||||
background: ${designTokens.colors.background.tertiary} !important;
|
|
||||||
color: ${designTokens.colors.text.primary} !important;
|
|
||||||
border-bottom: 1px solid ${designTokens.colors.border.light} !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-tbody {
|
|
||||||
flex-shrink: 1;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr > td {
|
|
||||||
white-space: normal !important;
|
|
||||||
word-break: break-word !important;
|
|
||||||
line-height: 1.6 !important;
|
|
||||||
max-width: 250px !important;
|
|
||||||
padding: 12px !important;
|
|
||||||
border-bottom: 1px solid ${designTokens.colors.border.light} !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr > td .ant-typography,
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr > td .ant-typography-expand,
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr > td span {
|
|
||||||
white-space: normal !important;
|
|
||||||
word-break: break-word !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-cell {
|
|
||||||
word-break: break-word !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-row-even {
|
|
||||||
background-color: ${designTokens.colors.background.primary};
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-row-odd {
|
|
||||||
background-color: ${designTokens.colors.background.secondary};
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-row-selected {
|
|
||||||
background-color: ${designTokens.colors.primary.main}15 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-row-selected:hover > td {
|
|
||||||
background-color: ${designTokens.colors.primary.main}25 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr:hover > td {
|
|
||||||
background-color: ${designTokens.colors.background.tertiary} !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-selection-column {
|
|
||||||
position: sticky !important;
|
|
||||||
left: 0 !important;
|
|
||||||
z-index: 2 !important;
|
|
||||||
background: inherit !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr > td:last-child {
|
|
||||||
min-width: 120px !important;
|
|
||||||
max-width: 150px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-pagination {
|
|
||||||
margin: 16px 0 !important;
|
|
||||||
flex-wrap: wrap !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
padding: 12px 16px !important;
|
|
||||||
background: ${designTokens.colors.background.primary};
|
|
||||||
border-radius: ${designTokens.borderRadius.medium};
|
|
||||||
margin-top: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-pagination-item-active {
|
|
||||||
background: ${designTokens.colors.primary.gradient} !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-pagination-item-active a {
|
|
||||||
color: #ffffff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-cell-fix-left,
|
|
||||||
.device-table-wrapper .ant-table-cell-fix-right {
|
|
||||||
background: inherit !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
.device-table-wrapper .ant-table-tbody > tr > td {
|
|
||||||
max-width: 150px !important;
|
|
||||||
font-size: 13px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-table-wrapper .ant-table-thead > tr > th {
|
|
||||||
font-size: 13px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start !important;
|
|
||||||
gap: 16px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-cards {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-card {
|
|
||||||
min-width: calc(50% - 8px) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.stat-card {
|
|
||||||
min-width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-form .ant-form-item {
|
|
||||||
margin-bottom: 12px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
|
|
||||||
<div style={headerStyle}>
|
<div style={headerStyle}>
|
||||||
<div style={titleRowStyle}>
|
<div style={titleRowStyle}>
|
||||||
@@ -1586,7 +1181,18 @@ function DeviceManagement() {
|
|||||||
批量删除 ({selectedDevices.length})
|
批量删除 ({selectedDevices.length})
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={dangerActionStyle}
|
style={{
|
||||||
|
height: '36px',
|
||||||
|
borderRadius: designTokens.borderRadius.small,
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: '500',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '6px',
|
||||||
|
background: designTokens.colors.error.main,
|
||||||
|
border: 'none',
|
||||||
|
color: '#ffffff'
|
||||||
|
}}
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={handleDeleteAll}
|
onClick={handleDeleteAll}
|
||||||
>
|
>
|
||||||
@@ -1862,7 +1468,7 @@ function DeviceManagement() {
|
|||||||
<Form.Item style={{ textAlign: 'right', marginTop: '24px' }}>
|
<Form.Item style={{ textAlign: 'right', marginTop: '24px' }}>
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={handleCancel} style={secondaryActionStyle}>取消</Button>
|
<Button onClick={handleCancel} style={secondaryActionStyle}>取消</Button>
|
||||||
<Button type="primary" htmlType="submit" style={primaryButtonStyle}>确定</Button>
|
<Button type="primary" htmlType="submit" 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>
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -1910,7 +1516,7 @@ function DeviceManagement() {
|
|||||||
<Space>
|
<Space>
|
||||||
<Button onClick={() => setFieldConfigModalVisible(false)} style={secondaryActionStyle}>取消</Button>
|
<Button onClick={() => setFieldConfigModalVisible(false)} style={secondaryActionStyle}>取消</Button>
|
||||||
<Button onClick={handleResetFieldConfig} style={secondaryActionStyle}>重置默认</Button>
|
<Button onClick={handleResetFieldConfig} style={secondaryActionStyle}>重置默认</Button>
|
||||||
<Button type="primary" htmlType="submit" style={primaryButtonStyle}>保存</Button>
|
<Button type="primary" htmlType="submit" 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>
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -1995,7 +1601,7 @@ function DeviceManagement() {
|
|||||||
beforeUpload={handleImport}
|
beforeUpload={handleImport}
|
||||||
maxCount={1}
|
maxCount={1}
|
||||||
>
|
>
|
||||||
<Button type="primary" icon={<UploadOutlined />} block style={primaryButtonStyle}>
|
<Button type="primary" icon={<UploadOutlined />} block 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' }}>
|
||||||
选择CSV文件
|
选择CSV文件
|
||||||
</Button>
|
</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
@@ -2086,7 +1692,7 @@ function DeviceManagement() {
|
|||||||
setImportResult(null);
|
setImportResult(null);
|
||||||
setIsImporting(false);
|
setIsImporting(false);
|
||||||
fetchDevices();
|
fetchDevices();
|
||||||
}} style={{ marginTop: '20px', ...primaryButtonStyle, height: '40px' }}>
|
}} style={{ marginTop: '20px', 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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2115,7 +1721,7 @@ function DeviceManagement() {
|
|||||||
<Button key="edit" type="primary" onClick={() => {
|
<Button key="edit" type="primary" onClick={() => {
|
||||||
setDetailModalVisible(false);
|
setDetailModalVisible(false);
|
||||||
showModal(selectedDevice);
|
showModal(selectedDevice);
|
||||||
}} style={primaryButtonStyle}>
|
}} 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>
|
</Button>
|
||||||
]}
|
]}
|
||||||
@@ -2240,7 +1846,7 @@ function DeviceManagement() {
|
|||||||
<Button key="cancel" onClick={() => setBatchStatusModalVisible(false)} style={secondaryActionStyle}>
|
<Button key="cancel" onClick={() => setBatchStatusModalVisible(false)} style={secondaryActionStyle}>
|
||||||
取消
|
取消
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button key="submit" type="primary" loading={batchStatusLoading} onClick={handleBatchStatusChange} style={primaryButtonStyle}>
|
<Button key="submit" type="primary" loading={batchStatusLoading} onClick={handleBatchStatusChange} 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>
|
</Button>
|
||||||
]}
|
]}
|
||||||
@@ -2279,7 +1885,7 @@ function DeviceManagement() {
|
|||||||
<Button key="cancel" onClick={() => setExportModalVisible(false)} style={secondaryActionStyle}>
|
<Button key="cancel" onClick={() => setExportModalVisible(false)} style={secondaryActionStyle}>
|
||||||
取消
|
取消
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button key="submit" type="primary" loading={exportLoading} onClick={handleEnhancedExport} style={primaryButtonStyle}>
|
<Button key="submit" type="primary" loading={exportLoading} onClick={handleEnhancedExport} 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>
|
</Button>
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -0,0 +1,583 @@
|
|||||||
|
/**
|
||||||
|
* 设备管理页面样式配置
|
||||||
|
* 集中管理所有内联样式对象
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { designTokens } from '../config/theme';
|
||||||
|
|
||||||
|
const { colors, shadows, borderRadius, transitions, spacing } = designTokens;
|
||||||
|
|
||||||
|
// 页面容器样式
|
||||||
|
export const pageContainerStyle = {
|
||||||
|
minHeight: '100vh',
|
||||||
|
background: colors.background.secondary,
|
||||||
|
padding: spacing.lg
|
||||||
|
};
|
||||||
|
|
||||||
|
// 头部样式
|
||||||
|
export const headerStyle = {
|
||||||
|
marginBottom: spacing.lg
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标题行样式
|
||||||
|
export const titleRowStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: spacing.lg,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: spacing.md
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标题区域样式
|
||||||
|
export const titleSectionStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.md
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标题图标样式
|
||||||
|
export const titleIconStyle = {
|
||||||
|
width: '44px',
|
||||||
|
height: '44px',
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
background: colors.primary.gradient,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
boxShadow: shadows.medium
|
||||||
|
};
|
||||||
|
|
||||||
|
// 标题文本样式
|
||||||
|
export const titleTextStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '2px'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面标题样式
|
||||||
|
export const pageTitleStyle = {
|
||||||
|
fontSize: '22px',
|
||||||
|
fontWeight: '700',
|
||||||
|
margin: 0,
|
||||||
|
color: colors.text.primary,
|
||||||
|
lineHeight: 1.2
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面副标题样式
|
||||||
|
export const pageSubtitleStyle = {
|
||||||
|
fontSize: '13px',
|
||||||
|
color: colors.text.secondary,
|
||||||
|
margin: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// 操作按钮基础样式
|
||||||
|
export const actionButtonStyle = {
|
||||||
|
height: '36px',
|
||||||
|
borderRadius: borderRadius.small,
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: '500',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '6px'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 主要操作按钮样式
|
||||||
|
export const primaryActionStyle = {
|
||||||
|
...actionButtonStyle,
|
||||||
|
background: colors.primary.gradient,
|
||||||
|
border: 'none',
|
||||||
|
color: '#ffffff !important',
|
||||||
|
boxShadow: shadows.small
|
||||||
|
};
|
||||||
|
|
||||||
|
// 次要操作按钮样式
|
||||||
|
export const secondaryActionStyle = {
|
||||||
|
...actionButtonStyle,
|
||||||
|
background: colors.background.primary,
|
||||||
|
border: `1px solid ${colors.border.light}`,
|
||||||
|
color: colors.text.primary
|
||||||
|
};
|
||||||
|
|
||||||
|
// 危险操作按钮样式
|
||||||
|
export const dangerActionStyle = {
|
||||||
|
...actionButtonStyle,
|
||||||
|
background: colors.error.main,
|
||||||
|
border: 'none',
|
||||||
|
color: '#ffffff'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 主要按钮样式(大)
|
||||||
|
export const primaryButtonStyle = {
|
||||||
|
height: '40px',
|
||||||
|
borderRadius: borderRadius.small,
|
||||||
|
background: colors.primary.gradient,
|
||||||
|
border: 'none',
|
||||||
|
color: '#ffffff',
|
||||||
|
boxShadow: shadows.small,
|
||||||
|
fontWeight: '500',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 统计卡片行样式
|
||||||
|
export const statsRowStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
gap: spacing.md,
|
||||||
|
marginBottom: spacing.lg,
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 统计卡片基础样式
|
||||||
|
export const statCardStyle = {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: '140px',
|
||||||
|
maxWidth: '200px',
|
||||||
|
padding: `${spacing.md} ${spacing.lg}`,
|
||||||
|
background: colors.background.primary,
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
border: `1px solid ${colors.border.light}`,
|
||||||
|
boxShadow: shadows.small,
|
||||||
|
transition: `all ${transitions.fast}`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 统计数值样式
|
||||||
|
export const statValueStyle = {
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: '700',
|
||||||
|
color: colors.text.primary,
|
||||||
|
lineHeight: 1.2
|
||||||
|
};
|
||||||
|
|
||||||
|
// 统计标签样式
|
||||||
|
export const statLabelStyle = {
|
||||||
|
fontSize: '12px',
|
||||||
|
color: colors.text.secondary,
|
||||||
|
marginTop: '4px'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 运行中状态统计卡片样式
|
||||||
|
export const statCardRunningStyle = {
|
||||||
|
...statCardStyle,
|
||||||
|
borderLeft: `3px solid ${colors.success.main}`,
|
||||||
|
background: `${colors.success.main}08`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 维护中状态统计卡片样式
|
||||||
|
export const statCardMaintenanceStyle = {
|
||||||
|
...statCardStyle,
|
||||||
|
borderLeft: `3px solid ${colors.warning.main}`,
|
||||||
|
background: `${colors.warning.main}08`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 故障状态统计卡片样式
|
||||||
|
export const statCardFaultStyle = {
|
||||||
|
...statCardStyle,
|
||||||
|
borderLeft: `3px solid ${colors.error.main}`,
|
||||||
|
background: `${colors.error.main}08`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 卡片基础样式
|
||||||
|
export const cardStyle = {
|
||||||
|
borderRadius: borderRadius.large,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: shadows.medium,
|
||||||
|
overflow: 'hidden',
|
||||||
|
background: colors.background.primary
|
||||||
|
};
|
||||||
|
|
||||||
|
// 筛选卡片样式
|
||||||
|
export const filterCardStyle = {
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: shadows.small,
|
||||||
|
background: colors.background.primary,
|
||||||
|
marginBottom: spacing.lg
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模态框头部样式
|
||||||
|
export const modalHeaderStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: spacing.sm,
|
||||||
|
fontSize: '18px',
|
||||||
|
fontWeight: '600'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表格样式常量
|
||||||
|
export const tableStyles = {
|
||||||
|
// 表格容器样式
|
||||||
|
wrapper: {
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
overflow: 'hidden'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 空状态样式
|
||||||
|
empty: {
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: '60px 20px',
|
||||||
|
color: colors.text.secondary,
|
||||||
|
fontSize: '15px'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 空状态图标样式
|
||||||
|
emptyIcon: {
|
||||||
|
fontSize: '48px',
|
||||||
|
marginBottom: '16px',
|
||||||
|
color: colors.border.light
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索输入框样式
|
||||||
|
export const searchInputStyle = {
|
||||||
|
width: '280px',
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
border: `1px solid ${colors.border.light}`,
|
||||||
|
transition: `all ${transitions.fast}`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择器样式
|
||||||
|
export const selectStyle = {
|
||||||
|
borderRadius: borderRadius.medium
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下拉菜单样式
|
||||||
|
export const dropdownStyle = {
|
||||||
|
borderRadius: borderRadius.medium
|
||||||
|
};
|
||||||
|
|
||||||
|
// 刷新按钮样式
|
||||||
|
export const refreshButtonStyle = {
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
border: `1px solid ${colors.border.light}`,
|
||||||
|
height: '36px'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索按钮样式
|
||||||
|
export const searchButtonStyle = {
|
||||||
|
height: '36px',
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
background: colors.primary.gradient,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: shadows.small
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置按钮样式
|
||||||
|
export const resetButtonStyle = {
|
||||||
|
height: '36px',
|
||||||
|
borderRadius: borderRadius.medium,
|
||||||
|
border: `1px solid ${colors.border.light}`
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导入模态框样式
|
||||||
|
export const importModalStyles = {
|
||||||
|
// 说明区域样式
|
||||||
|
description: {
|
||||||
|
marginBottom: '20px',
|
||||||
|
padding: '16px',
|
||||||
|
background: 'linear-gradient(180deg, #fafafa 0%, #ffffff 100%)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
border: '1px solid #f0f0f0'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 标题样式
|
||||||
|
title: {
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: '8px',
|
||||||
|
color: '#333'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 列表样式
|
||||||
|
list: {
|
||||||
|
paddingLeft: '20px',
|
||||||
|
marginBottom: '10px',
|
||||||
|
color: '#666',
|
||||||
|
fontSize: '13px',
|
||||||
|
marginTop: '12px'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 进度容器样式
|
||||||
|
progressContainer: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: '16px'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 进度图标样式
|
||||||
|
progressIcon: {
|
||||||
|
width: '48px',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: colors.primary.gradient,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
marginRight: '16px',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '20px'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 进度信息样式
|
||||||
|
progressInfo: {
|
||||||
|
title: {
|
||||||
|
margin: '0 0 4px 0',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#333',
|
||||||
|
fontSize: '16px'
|
||||||
|
},
|
||||||
|
phase: {
|
||||||
|
margin: 0,
|
||||||
|
color: colors.primary.main,
|
||||||
|
fontSize: '14px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 结果卡片样式
|
||||||
|
resultCard: (type) => ({
|
||||||
|
padding: '12px',
|
||||||
|
background: type === 'total' ? colors.primary.gradient :
|
||||||
|
type === 'success' ? 'linear-gradient(135deg, #52c41a 0%, #389e0d 100%)' :
|
||||||
|
'linear-gradient(135deg, #ff4d4f 0%, #cf1322 100%)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#fff',
|
||||||
|
textAlign: 'center'
|
||||||
|
}),
|
||||||
|
|
||||||
|
// 结果数值样式
|
||||||
|
resultValue: {
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: '700'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 结果标签样式
|
||||||
|
resultLabel: {
|
||||||
|
fontSize: '12px',
|
||||||
|
opacity: 0.9
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 详情模态框样式
|
||||||
|
export const detailModalStyles = {
|
||||||
|
// 信息项样式
|
||||||
|
infoItem: {
|
||||||
|
label: {
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
marginLeft: 8,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 描述区域样式
|
||||||
|
description: {
|
||||||
|
marginTop: '16px'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 描述内容样式
|
||||||
|
descriptionContent: {
|
||||||
|
marginTop: '8px',
|
||||||
|
padding: '12px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出模态框样式
|
||||||
|
export const exportModalStyles = {
|
||||||
|
// 字段选择区域样式
|
||||||
|
fieldSelector: {
|
||||||
|
maxHeight: '300px',
|
||||||
|
overflow: 'auto',
|
||||||
|
border: '1px solid #f0f0f0',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 字段项样式
|
||||||
|
fieldItem: {
|
||||||
|
marginBottom: '8px'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// CSS-in-JS 样式字符串生成函数
|
||||||
|
export const generateGlobalStyles = (tokens) => `
|
||||||
|
.device-modal .ant-modal-close {
|
||||||
|
top: 16px;
|
||||||
|
right: 24px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
alignItems: center;
|
||||||
|
justifyContent: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-modal .ant-modal-close-x {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
alignItems: center;
|
||||||
|
justifyContent: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-container {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-content {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-thead {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-thead > tr > th {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-word !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
padding: 14px 12px !important;
|
||||||
|
background: ${tokens.colors.background.tertiary} !important;
|
||||||
|
color: ${tokens.colors.text.primary} !important;
|
||||||
|
border-bottom: 1px solid ${tokens.colors.border.light} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-tbody {
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr > td {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-word !important;
|
||||||
|
line-height: 1.6 !important;
|
||||||
|
max-width: 250px !important;
|
||||||
|
padding: 12px !important;
|
||||||
|
border-bottom: 1px solid ${tokens.colors.border.light} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr > td .ant-typography,
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr > td .ant-typography-expand,
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr > td span {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-word !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-cell {
|
||||||
|
word-break: break-word !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-row-even {
|
||||||
|
background-color: ${tokens.colors.background.primary};
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-row-odd {
|
||||||
|
background-color: ${tokens.colors.background.secondary};
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-row-selected {
|
||||||
|
background-color: ${tokens.colors.primary.main}15 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-row-selected:hover > td {
|
||||||
|
background-color: ${tokens.colors.primary.main}25 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr:hover > td {
|
||||||
|
background-color: ${tokens.colors.background.tertiary} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-selection-column {
|
||||||
|
position: sticky !important;
|
||||||
|
left: 0 !important;
|
||||||
|
z-index: 2 !important;
|
||||||
|
background: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr > td:last-child {
|
||||||
|
min-width: 120px !important;
|
||||||
|
max-width: 150px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-pagination {
|
||||||
|
margin: 16px 0 !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding: 12px 16px !important;
|
||||||
|
background: ${tokens.colors.background.primary};
|
||||||
|
border-radius: ${tokens.borderRadius.medium};
|
||||||
|
margin-top: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-pagination-item-active {
|
||||||
|
background: ${tokens.colors.primary.gradient} !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-pagination-item-active a {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-cell-fix-left,
|
||||||
|
.device-table-wrapper .ant-table-cell-fix-right {
|
||||||
|
background: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.device-table-wrapper .ant-table-tbody > tr > td {
|
||||||
|
max-width: 150px !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-table-wrapper .ant-table-thead > tr > th {
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.page-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
gap: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-cards {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
min-width: calc(50% - 8px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.stat-card {
|
||||||
|
min-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form .ant-form-item {
|
||||||
|
margin-bottom: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user