refactor(模型关系): 重构模型关联关系定义位置,从模型文件移至路由文件

This commit is contained in:
zhang1106
2026-03-12 11:19:56 +08:00
parent 2e87654c98
commit 668ea073e6
17 changed files with 1478 additions and 553 deletions
+1 -1
View File
@@ -641,7 +641,7 @@ SOFTWARE.
- **Gitee Issues**: https://gitee.com/zhang96110/idc_assest/issues
- **GitHub Issues**: https://github.com/gituib/idc_assest/issues
- 功能建议:提交 Issue 并标注 `feature-request`
- QQ群:1081123775
---
**⭐ 如果这个项目对您有帮助,请给我们一个 Star!**
+4 -3
View File
@@ -1,5 +1,6 @@
{
"type": "image",
"image": null,
"size": "contain"
"opacity": 0.8,
"blur": 5,
"value": "/images/bg.jpg",
"type": "image"
}
+63 -4
View File
@@ -116,9 +116,30 @@ const authMiddleware = async (req, res, next) => {
});
}
const user = await User.findByPk(decoded.userId);
// 验证用户是否存在,增加详细的错误日志
let user;
try {
user = await User.findByPk(decoded.userId);
} catch (dbError) {
// 数据库查询错误
console.error('[认证中间件] 数据库查询失败:', {
userId: decoded.userId,
error: dbError.message,
stack: dbError.stack,
timestamp: new Date().toISOString()
});
return res.status(500).json({
success: false,
message: '数据库查询失败,请稍后重试'
});
}
if (!user) {
console.warn('[认证中间件] 用户不存在:', {
userId: decoded.userId,
username: decoded.username,
timestamp: new Date().toISOString()
});
return res.status(401).json({
success: false,
message: '用户不存在'
@@ -126,6 +147,11 @@ const authMiddleware = async (req, res, next) => {
}
if (user.status === 'locked') {
console.warn('[认证中间件] 账户已被锁定:', {
userId: user.userId,
username: user.username,
timestamp: new Date().toISOString()
});
return res.status(403).json({
success: false,
message: '账户已被锁定'
@@ -133,6 +159,11 @@ const authMiddleware = async (req, res, next) => {
}
if (user.status === 'inactive') {
console.warn('[认证中间件] 账户已禁用:', {
userId: user.userId,
username: user.username,
timestamp: new Date().toISOString()
});
return res.status(403).json({
success: false,
message: '账户已禁用'
@@ -143,10 +174,18 @@ const authMiddleware = async (req, res, next) => {
req.userModel = user;
next();
} catch (error) {
console.error('认证中间件错误:', error);
// 捕获未预期的错误
console.error('[认证中间件] 未预期的错误:', {
error: error.message,
stack: error.stack,
url: req?.url,
method: req?.method,
ip: req?.ip,
timestamp: new Date().toISOString()
});
return res.status(500).json({
success: false,
message: '认证失败'
message: '认证失败,请稍后重试'
});
}
};
@@ -160,7 +199,21 @@ const optionalAuth = async (req, res, next) => {
const decoded = verifyToken(token);
if (decoded) {
const user = await User.findByPk(decoded.userId);
let user;
try {
user = await User.findByPk(decoded.userId);
} catch (dbError) {
// 数据库错误时不中断请求,仅记录日志
console.warn('[可选认证] 数据库查询失败:', {
userId: decoded.userId,
error: dbError.message,
timestamp: new Date().toISOString()
});
// 继续执行,不设置用户信息
next();
return;
}
if (user && user.status === 'active') {
req.user = decoded;
req.userModel = user;
@@ -170,6 +223,12 @@ const optionalAuth = async (req, res, next) => {
next();
} catch (error) {
// 可选认证失败不影响主流程,仅记录日志
console.warn('[可选认证] 认证失败(已忽略):', {
error: error.message,
url: req?.url,
timestamp: new Date().toISOString()
});
next();
}
};
+33 -28
View File
@@ -3,49 +3,54 @@ const validate = (schema, source = 'body') => {
const data = source === 'query' ? req.query : req.body;
try {
let result;
let value;
if (schema.validate && typeof schema.validate === 'function') {
if (schema.validate.constructor.name === 'AsyncFunction' ||
schema.validate.length === 1) {
result = await schema.validate(data, {
abortEarly: false,
stripUnknown: true,
allowUnknown: source === 'query'
const result = schema.validate(data, {
abortEarly: false,
stripUnknown: true,
allowUnknown: source === 'query'
});
if (result && typeof result.then === 'function') {
value = await result;
} else if (result && result.error) {
const errorMessages = result.error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}));
return res.status(400).json({
error: '参数验证失败',
details: errorMessages
});
} else if (result && result.value !== undefined) {
value = result.value;
} else {
result = schema.validate(data, {
abortEarly: false,
stripUnknown: true,
allowUnknown: source === 'query'
});
value = result;
}
} else if (schema.validateAsync) {
result = await schema.validateAsync(data, {
value = await schema.validateAsync(data, {
abortEarly: false,
stripUnknown: true,
allowUnknown: source === 'query'
});
} else {
result = schema.validate(data, {
const result = schema.validate(data, {
abortEarly: false,
stripUnknown: true,
allowUnknown: source === 'query'
});
}
const { error, value } = result;
if (error) {
const errorMessages = error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}));
return res.status(400).json({
error: '参数验证失败',
details: errorMessages
});
if (result.error) {
const errorMessages = result.error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message
}));
return res.status(400).json({
error: '参数验证失败',
details: errorMessages
});
}
value = result.value;
}
if (source === 'query') {
+1 -9
View File
@@ -1,6 +1,5 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const Rack = require('./Rack');
const Device = sequelize.define('Device', {
deviceId: {
@@ -28,11 +27,7 @@ const Device = sequelize.define('Device', {
},
rackId: {
type: DataTypes.STRING,
allowNull: true,
references: {
model: Rack,
key: 'rackId'
}
allowNull: true
},
position: {
type: DataTypes.INTEGER,
@@ -86,7 +81,4 @@ const Device = sequelize.define('Device', {
]
});
Device.belongsTo(Rack, { foreignKey: 'rackId' });
Rack.hasMany(Device, { foreignKey: 'rackId' });
module.exports = Device;
-8
View File
@@ -1,7 +1,5 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const Device = require('./Device');
const NetworkCard = require('./NetworkCard');
const DevicePort = sequelize.define('DevicePort', {
portId: {
@@ -67,10 +65,4 @@ const DevicePort = sequelize.define('DevicePort', {
]
});
DevicePort.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
Device.hasMany(DevicePort, { foreignKey: 'deviceId', as: 'ports' });
DevicePort.belongsTo(NetworkCard, { foreignKey: 'nicId', as: 'networkCard' });
NetworkCard.hasMany(DevicePort, { foreignKey: 'nicId', as: 'ports' });
module.exports = DevicePort;
-4
View File
@@ -1,6 +1,5 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../db');
const Device = require('./Device');
const NetworkCard = sequelize.define('NetworkCard', {
nicId: {
@@ -63,7 +62,4 @@ const NetworkCard = sequelize.define('NetworkCard', {
]
});
NetworkCard.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
Device.hasMany(NetworkCard, { foreignKey: 'deviceId', as: 'networkCards' });
module.exports = NetworkCard;
+27 -7
View File
@@ -3,13 +3,11 @@ const path = require('path');
const fs = require('fs');
const router = express.Router();
// 确保上传目录存在
const UPLOAD_DIR = path.join(__dirname, '../uploads');
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
// 确保背景设置文件存在
const SETTINGS_FILE = path.join(__dirname, '../backgroundSettings.json');
if (!fs.existsSync(SETTINGS_FILE)) {
fs.writeFileSync(SETTINGS_FILE, JSON.stringify({
@@ -19,7 +17,33 @@ if (!fs.existsSync(SETTINGS_FILE)) {
}, null, 2));
}
// 上传图片接口
router.get('/', (req, res) => {
try {
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
res.json({
success: true,
data: settings
});
} catch (error) {
console.error('读取背景设置失败:', error);
res.status(500).json({ error: '读取背景设置失败' });
}
});
router.put('/', (req, res) => {
try {
const settings = req.body;
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2));
res.json({
success: true,
data: settings
});
} catch (error) {
console.error('保存背景设置失败:', error);
res.status(500).json({ error: '保存背景设置失败' });
}
});
router.post('/upload', (req, res) => {
try {
if (!req.files || !req.files.file) {
@@ -30,14 +54,12 @@ router.post('/upload', (req, res) => {
const fileName = `${Date.now()}_${file.name}`;
const filePath = path.join(UPLOAD_DIR, fileName);
// 保存文件到服务器
file.mv(filePath, (err) => {
if (err) {
console.error('文件保存失败:', err);
return res.status(500).json({ error: '文件保存失败' });
}
// 返回文件路径
const fileUrl = `/uploads/${fileName}`;
res.json({ path: fileUrl });
});
@@ -47,7 +69,6 @@ router.post('/upload', (req, res) => {
}
});
// 获取背景设置
router.get('/settings', (req, res) => {
try {
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
@@ -58,7 +79,6 @@ router.get('/settings', (req, res) => {
}
});
// 保存背景设置
router.post('/settings', (req, res) => {
try {
const settings = req.body;
+5
View File
@@ -3,6 +3,11 @@ const router = express.Router();
const { Op } = require('sequelize');
const DevicePort = require('../models/DevicePort');
const Device = require('../models/Device');
const NetworkCard = require('../models/NetworkCard');
DevicePort.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
Device.hasMany(DevicePort, { foreignKey: 'deviceId', as: 'ports' });
DevicePort.belongsTo(NetworkCard, { foreignKey: 'nicId', as: 'networkCard' });
router.get('/', async (req, res) => {
try {
+33 -23
View File
@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const { Op } = require('sequelize');
const { sequelize, dbDialect } = require('../db'); // Import sequelize and dbDialect for transactions
const { sequelize, dbDialect } = require('../db');
const fs = require('fs');
const path = require('path');
const csv = require('csv-parser');
@@ -12,9 +12,9 @@ const Rack = require('../models/Rack');
const Room = require('../models/Room');
const DeviceField = require('../models/DeviceField');
const Ticket = require('../models/Ticket');
const DevicePort = require('../models/DevicePort'); // Import DevicePort
const Cable = require('../models/Cable'); // Import Cable
const NetworkCard = require('../models/NetworkCard'); // Import NetworkCard
const DevicePort = require('../models/DevicePort');
const Cable = require('../models/Cable');
const NetworkCard = require('../models/NetworkCard');
const { validateBody, validateQuery } = require('../middleware/validation');
const {
createDeviceSchema,
@@ -25,7 +25,9 @@ const {
queryDeviceSchema
} = require('../validation/deviceSchema');
// 获取所有设备(支持搜索和筛选)
Device.belongsTo(Rack, { foreignKey: 'rackId' });
Rack.hasMany(Device, { foreignKey: 'rackId' });
router.get('/', validateQuery(queryDeviceSchema), async (req, res) => {
try {
const { keyword, status, type, rackId, page = 1, pageSize = 10 } = req.query;
@@ -772,6 +774,16 @@ router.post('/import', async (req, res) => {
} catch (error) {
await t.rollback();
console.error('导入设备数据失败:', error);
// 清理临时文件
try {
if (filePath && fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
} catch (fileErr) {
console.error('删除临时文件失败:', fileErr);
}
res.status(500).json({
errors: [{ row: 0, error: error.message || '导入过程中发生未知错误' }]
});
@@ -1224,7 +1236,22 @@ router.delete('/:deviceId', async (req, res) => {
{ where: { deviceId: deviceId }, transaction: t }
);
// 5. 删除设备 (Delete Device)
// 5. 更新机柜功率 (必须在删除设备之前)
if (device.rackId) {
try {
const rack = await Rack.findByPk(device.rackId, { transaction: t });
if (rack) {
await rack.update({
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
}, { transaction: t });
}
} catch (err) {
console.error('更新机柜功率失败:', err);
throw err; // 重新抛出错误,触发事务回滚
}
}
// 6. 删除设备 (Delete Device)
await Device.destroy({
where: { deviceId: deviceId },
transaction: t
@@ -1237,23 +1264,6 @@ router.delete('/:deviceId', async (req, res) => {
console.log(`已删除 ${deletedCables} 条相关接线`);
}
// 更新机柜功率 (Update Rack power)
// 注意:设备已删除,不需要再减去功率?或者需要?
// 原逻辑是:rack.currentPower - device.powerConsumption
// 既然设备已经物理删除了,机柜的当前功率确实应该减少。
if (device.rackId) {
try {
const rack = await Rack.findByPk(device.rackId);
if (rack) {
await rack.update({
currentPower: Math.max(0, rack.currentPower - device.powerConsumption)
});
}
} catch (err) {
console.error('更新机柜功率失败:', err);
}
}
res.status(200).json({
message: '删除成功',
deviceId: deviceId,
+4
View File
@@ -5,6 +5,10 @@ const NetworkCard = require('../models/NetworkCard');
const Device = require('../models/Device');
const DevicePort = require('../models/DevicePort');
NetworkCard.belongsTo(Device, { foreignKey: 'deviceId', as: 'device' });
Device.hasMany(NetworkCard, { foreignKey: 'deviceId', as: 'networkCards' });
NetworkCard.hasMany(DevicePort, { foreignKey: 'nicId', as: 'ports' });
router.get('/', async (req, res) => {
try {
const { deviceId } = req.query;
+31
View File
@@ -180,6 +180,37 @@ app.use('/api/inventory', inventoryRoutes);
app.use('/uploads', express.static('uploads'));
app.get('/api', (req, res) => {
res.json({
name: 'IDC设备管理系统 API',
version: '1.0.0',
description: '数据中心设备管理平台后端服务',
endpoints: {
auth: '/api/auth',
rooms: '/api/rooms',
racks: '/api/racks',
devices: '/api/devices',
deviceFields: '/api/deviceFields',
devicePorts: '/api/device-ports',
networkCards: '/api/network-cards',
cables: '/api/cables',
tickets: '/api/tickets',
ticketCategories: '/api/ticket-categories',
ticketFields: '/api/ticket-fields',
consumables: '/api/consumables',
consumableRecords: '/api/consumable-records',
consumableCategories: '/api/consumable-categories',
users: '/api/users',
roles: '/api/roles',
systemSettings: '/api/system-settings',
background: '/api/background',
inventory: '/api/inventory'
},
health: '/health',
documentation: '/docs/api/README.md'
});
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', message: 'IDC设备管理系统后端服务正常运行' });
});
+53 -176
View File
@@ -1,168 +1,61 @@
const Joi = require('joi');
const DeviceField = require('../models/DeviceField');
const DEVICE_TYPES = ['server', 'switch', 'router', 'storage', 'other'];
const DEVICE_STATUS = ['running', 'maintenance', 'offline', 'fault'];
const baseFieldSchemas = {
deviceId: Joi.string()
.max(50)
.pattern(/^[a-zA-Z0-9_-]+$/)
.allow('', null)
.messages({
'string.max': '设备ID不能超过50个字符',
'string.pattern.base': '设备ID只能包含字母、数字、下划线和横线'
}),
name: Joi.string()
.max(100)
.messages({
'string.empty': '设备名称不能为空',
'string.max': '设备名称不能超过100个字符'
}),
type: Joi.string()
.valid(...DEVICE_TYPES)
.messages({
'string.empty': '设备类型不能为空',
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`
}),
model: Joi.string()
.max(100)
.allow('', null)
.messages({
'string.max': '型号不能超过100个字符'
}),
serialNumber: Joi.string()
.max(100)
.messages({
'string.empty': '序列号不能为空',
'string.max': '序列号不能超过100个字符'
}),
rackId: Joi.string()
.max(50)
.messages({
'string.empty': '机柜ID不能为空',
'string.max': '机柜ID不能超过50个字符'
}),
position: Joi.number()
.integer()
.min(1)
.max(100)
.messages({
'number.base': '位置必须是数字',
'number.integer': '位置必须是整数',
'number.min': '位置不能小于1',
'number.max': '位置不能大于100'
}),
height: Joi.number()
.integer()
.min(1)
.max(50)
.messages({
'number.base': '高度必须是数字',
'number.integer': '高度必须是整数',
'number.min': '高度不能小于1',
'number.max': '高度不能大于50'
}),
powerConsumption: Joi.number()
.min(0)
.max(100000)
.messages({
'number.base': '功率必须是数字',
'number.min': '功率不能小于0',
'number.max': '功率不能超过100000'
}),
ipAddress: Joi.string()
.ip({ version: ['ipv4', 'ipv6'] })
.allow('', null)
.messages({
'string.ip': 'IP地址格式无效'
}),
status: Joi.string()
.valid(...DEVICE_STATUS)
.default('offline')
.messages({
'any.only': `状态必须是以下之一: ${DEVICE_STATUS.join(', ')}`
}),
purchaseDate: Joi.date()
.allow(null)
.messages({
'date.base': '购买日期格式无效'
}),
warrantyExpiry: Joi.date()
.allow(null)
.messages({
'date.base': '保修到期日期格式无效'
}),
description: Joi.string()
.max(500)
.allow('', null)
.messages({
'string.max': '描述不能超过500个字符'
}),
const createDeviceSchema = Joi.object({
name: Joi.string().required().max(100).messages({
'string.empty': '设备名称不能为空',
'string.max': '设备名称不能超过100个字符',
'any.required': '设备名称是必填字段'
}),
type: Joi.string().required().valid(...DEVICE_TYPES).messages({
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`,
'any.required': '设备类型是必填字段'
}),
model: Joi.string().allow('', null).max(100),
serialNumber: Joi.string().required().max(100).messages({
'string.empty': '序列号不能为空',
'string.max': '序列号不能超过100个字符',
'any.required': '序列号是必填字段'
}),
rackId: Joi.string().allow('', null).max(50),
position: Joi.number().integer().min(1).max(100).allow(null),
height: Joi.number().integer().min(1).max(50).allow(null),
powerConsumption: Joi.number().min(0).max(100000).allow(null),
ipAddress: Joi.string().allow('', null).max(50),
status: Joi.string().valid(...DEVICE_STATUS).default('offline'),
purchaseDate: Joi.date().allow(null),
warrantyExpiry: Joi.date().allow(null),
description: Joi.string().allow('', null).max(500),
customFields: Joi.object().allow(null)
};
});
async function buildDynamicSchema(isCreate = true) {
const fields = await DeviceField.findAll({
where: { isSystem: true },
order: [['order', 'ASC']]
});
const schemaObj = {};
fields.forEach(field => {
const baseSchema = baseFieldSchemas[field.fieldName];
if (baseSchema) {
let fieldSchema = baseSchema.clone();
if (field.required && isCreate) {
fieldSchema = fieldSchema.required();
}
schemaObj[field.fieldName] = fieldSchema;
}
});
schemaObj.customFields = baseFieldSchemas.customFields;
return Joi.object(schemaObj).custom((value, helpers) => {
if (value.purchaseDate && value.warrantyExpiry) {
const purchase = new Date(value.purchaseDate);
const warranty = new Date(value.warrantyExpiry);
if (warranty <= purchase) {
return helpers.error('date.warrantyAfterPurchase');
}
}
return value;
}).messages({
'date.warrantyAfterPurchase': '保修到期日期必须晚于购买日期'
});
}
async function getCreateDeviceSchema() {
return buildDynamicSchema(true);
}
async function getUpdateDeviceSchema() {
const schema = await buildDynamicSchema(false);
return schema.min(1).messages({
'object.min': '至少需要提供一个字段进行更新'
});
}
const updateDeviceSchema = Joi.object({
name: Joi.string().max(100).messages({
'string.empty': '设备名称不能为空',
'string.max': '设备名称不能超过100个字符'
}),
type: Joi.string().valid(...DEVICE_TYPES).messages({
'any.only': `设备类型必须是以下之一: ${DEVICE_TYPES.join(', ')}`
}),
model: Joi.string().allow('', null).max(100),
serialNumber: Joi.string().max(100).messages({
'string.max': '序列号不能超过100个字符'
}),
rackId: Joi.string().allow('', null).max(50),
position: Joi.number().integer().min(1).max(100).allow(null),
height: Joi.number().integer().min(1).max(50).allow(null),
powerConsumption: Joi.number().min(0).max(100000).allow(null),
ipAddress: Joi.string().allow('', null).max(50),
status: Joi.string().valid(...DEVICE_STATUS),
purchaseDate: Joi.date().allow(null),
warrantyExpiry: Joi.date().allow(null),
description: Joi.string().allow('', null).max(500),
customFields: Joi.object().allow(null)
}).min(1).messages({
'object.min': '至少需要提供一个字段进行更新'
});
const batchDeviceIdsSchema = Joi.object({
deviceIds: Joi.array()
@@ -233,24 +126,10 @@ const queryDeviceSchema = Joi.object({
pageSize: Joi.number()
.integer()
.min(1)
.max(100)
.max(10000)
.default(10)
});
const createDeviceSchema = {
validate: async (data, options = {}) => {
const schema = await getCreateDeviceSchema();
return schema.validate(data, options);
}
};
const updateDeviceSchema = {
validate: async (data, options = {}) => {
const schema = await getUpdateDeviceSchema();
return schema.validate(data, options);
}
};
module.exports = {
createDeviceSchema,
updateDeviceSchema,
@@ -259,7 +138,5 @@ module.exports = {
batchMoveSchema,
queryDeviceSchema,
DEVICE_TYPES,
DEVICE_STATUS,
getCreateDeviceSchema,
getUpdateDeviceSchema
DEVICE_STATUS
};
+1146 -267
View File
File diff suppressed because it is too large Load Diff
+41 -16
View File
@@ -13,20 +13,27 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
const [sourcePorts, setSourcePorts] = useState([]);
const [targetPorts, setTargetPorts] = useState([]);
const [fetchingDevices, setFetchingDevices] = useState(false);
const prevVisibleRef = useRef(false);
const devicesRef = useRef([]);
const fetchDevices = useCallback(async (keyword = '') => {
try {
setFetchingDevices(true);
const params = { pageSize: 50 };
const params = { pageSize: 100 };
if (keyword && keyword.trim()) {
params.keyword = keyword.trim();
}
console.log('[CableCreateModal] Fetching devices with params:', params);
const response = await axios.get('/api/devices', { params });
setDevices(response.data.devices || []);
console.log('[CableCreateModal] API response:', response.data);
const deviceList = response.data.devices || [];
console.log('[CableCreateModal] Device list:', deviceList.length, 'devices');
setDevices(deviceList);
devicesRef.current = deviceList;
return deviceList;
} catch (error) {
console.error('Failed to fetch devices:', error);
console.error('[CableCreateModal] Failed to fetch devices:', error);
message.error('获取设备列表失败');
return [];
} finally {
setFetchingDevices(false);
}
@@ -40,18 +47,32 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
);
useEffect(() => {
if (visible && !prevVisibleRef.current) {
if (visible) {
console.log('[CableCreateModal] Modal opened, sourceDevice:', sourceDevice);
form.resetFields();
if (sourceDevice) {
form.setFieldsValue({
sourceDeviceId: sourceDevice.deviceId || sourceDevice.id,
});
fetchDevicePorts(sourceDevice.deviceId || sourceDevice.id, 'source');
}
fetchDevices();
setSourcePorts([]);
setTargetPorts([]);
setDevices([]);
devicesRef.current = [];
fetchDevices().then(deviceList => {
console.log('[CableCreateModal] Devices fetched:', deviceList.length);
const sourceDeviceId = sourceDevice?.deviceId || sourceDevice?.id;
console.log('[CableCreateModal] sourceDeviceId:', sourceDeviceId);
if (sourceDeviceId && deviceList.length > 0) {
const deviceExists = deviceList.some(d => d.deviceId === sourceDeviceId);
console.log('[CableCreateModal] Device exists in list:', deviceExists);
if (deviceExists) {
console.log('[CableCreateModal] Setting form value:', sourceDeviceId);
form.setFieldsValue({
sourceDeviceId: sourceDeviceId,
});
fetchDevicePorts(sourceDeviceId, 'source');
}
}
});
}
prevVisibleRef.current = visible;
}, [visible, sourceDevice, form, fetchDevices]);
}, [visible, sourceDevice?.deviceId, sourceDevice?.id, form, fetchDevices]);
const fetchDevicePorts = async (deviceId, type) => {
if (!deviceId) {
@@ -138,9 +159,11 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
<Select
showSearch
filterOption={false}
placeholder="搜索设备..."
placeholder={fetchingDevices ? '加载中...' : '搜索设备...'}
loading={fetchingDevices}
onSearch={handleDeviceSearch}
onChange={handleSourceDeviceChange}
notFoundContent={fetchingDevices ? <Spin size="small" /> : '暂无数据'}
>
{devices.map(device => (
<Option key={device.deviceId} value={device.deviceId}>
@@ -167,9 +190,11 @@ const CableCreateModal = ({ visible, onClose, onSuccess, sourceDevice }) => {
<Select
showSearch
filterOption={false}
placeholder="搜索设备..."
placeholder={fetchingDevices ? '加载中...' : '搜索设备...'}
loading={fetchingDevices}
onSearch={handleDeviceSearch}
onChange={handleTargetDeviceChange}
notFoundContent={fetchingDevices ? <Spin size="small" /> : '暂无数据'}
>
{devices.map(device => (
<Option key={device.deviceId} value={device.deviceId}>
+11
View File
@@ -110,6 +110,17 @@ export const designTokens = {
date: '#06b6d4',
textarea: '#64748b',
},
slot: {
empty: '#4b5563',
occupied: '#3b82f6',
warning: '#f59e0b',
error: '#ef4444',
},
metal: {
light: '#9ca3af',
medium: '#6b7280',
dark: '#374151',
},
},
shadows: {
small: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
+25 -7
View File
@@ -206,18 +206,36 @@ function CableManagement() {
const fetchDevices = useCallback(async (keyword = '') => {
try {
setDeviceSearching(true);
const params = { pageSize: 50 };
// 并行获取所有设备和交换机设备
const params = { pageSize: 1000 };
if (keyword && keyword.trim()) {
params.keyword = keyword.trim();
}
const response = await axios.get('/api/devices', { params });
const allDevices = response.data.devices || [];
const switches = allDevices.filter(device => device.type === 'switch');
const [allResponse, switchResponse] = await Promise.all([
axios.get('/api/devices', { params }),
axios.get('/api/devices', { params: { ...params, type: 'switch' } })
]);
const allDevices = allResponse.data.devices || [];
const switchDevices = switchResponse.data.devices || [];
// 统计各类型设备数量
const typeCount = {};
allDevices.forEach(d => {
const t = d.type || 'undefined';
typeCount[t] = (typeCount[t] || 0) + 1;
});
console.log('[CableManagement] 设备类型统计:', typeCount);
console.log('[CableManagement] All devices:', allDevices.length);
console.log('[CableManagement] Switch devices (from API):', switchDevices.length);
setDevices(allDevices);
setSwitchDevices(switches);
setSwitchDevices(switchDevices);
} catch (error) {
message.error('获取设备列表失败');
console.error('获取设备列表失败:', error);
console.error('[CableManagement] 获取设备列表失败:', error);
} finally {
setDeviceSearching(false);
}
@@ -1244,7 +1262,7 @@ function CableManagement() {
rules={[{ required: true, message: '请选择源设备' }]}
>
<Select
placeholder="输入关键词搜索源设备"
placeholder="输入关键词搜索交换机"
showSearch
loading={deviceSearching}
filterOption={false}