feat: 修复BUG

This commit is contained in:
zhang1106
2026-03-16 17:07:20 +08:00
parent 8dcbe9948b
commit e5d20820dc
26 changed files with 2363 additions and 1004 deletions
+86 -1
View File
@@ -22,6 +22,11 @@ const {
updateAutoBackupSettings,
executeBackupNow,
} = require('../utils/autoBackupScheduler');
const {
getBackupLogs,
getBackupLogById,
deleteOldLogs,
} = require('../utils/backupLog');
const {
getAllTargets,
getTarget,
@@ -30,6 +35,7 @@ const {
deleteTarget,
getGlobalSettings,
updateGlobalSettings,
getEnabledTargets,
} = require('../utils/remoteBackupConfig');
const { testRemoteConnection, PROTOCOL_TYPES, PROTOCOL_LABELS } = require('../utils/remoteBackup');
@@ -446,6 +452,7 @@ router.post('/auto/settings', (req, res) => {
compress,
maxCount,
maxAgeDays,
backupType,
} = req.body;
const newSettings = {};
@@ -468,6 +475,7 @@ router.post('/auto/settings', (req, res) => {
if (compress !== undefined) newSettings.compress = compress;
if (maxCount !== undefined) newSettings.maxCount = maxCount;
if (maxAgeDays !== undefined) newSettings.maxAgeDays = maxAgeDays;
if (backupType !== undefined) newSettings.backupType = backupType;
const success = updateAutoBackupSettings(newSettings);
if (success) {
@@ -496,12 +504,13 @@ router.post('/auto/settings', (req, res) => {
// 立即执行备份
router.post('/auto/execute', async (req, res) => {
try {
const { description, includeFiles, compress } = req.body;
const { description, includeFiles, compress, backupType } = req.body;
const result = await executeBackupNow({
description: description || '手动触发备份',
includeFiles,
compress,
backupType,
});
if (result.success) {
@@ -854,4 +863,80 @@ router.post('/remote/upload', async (req, res) => {
}
});
// ==================== 备份日志接口 ====================
// 获取备份日志列表
router.get('/logs', async (req, res) => {
try {
const { page = 1, pageSize = 20, logType, status } = req.query;
const result = await getBackupLogs({
page: parseInt(page),
pageSize: parseInt(pageSize),
logType,
status,
});
res.json({
success: true,
data: result,
});
} catch (error) {
console.error('获取备份日志失败:', error);
res.status(500).json({
success: false,
message: '获取备份日志失败',
error: error.message,
});
}
});
// 获取备份日志详情
router.get('/logs/:id', async (req, res) => {
try {
const { id } = req.params;
const log = await getBackupLogById(parseInt(id));
if (!log) {
return res.status(404).json({
success: false,
message: '备份日志不存在',
});
}
res.json({
success: true,
data: log,
});
} catch (error) {
console.error('获取备份日志详情失败:', error);
res.status(500).json({
success: false,
message: '获取备份日志详情失败',
error: error.message,
});
}
});
// 清理旧日志
router.delete('/logs/clean', async (req, res) => {
try {
const { days = 30 } = req.body;
const deletedCount = await deleteOldLogs(parseInt(days));
res.json({
success: true,
message: '清理完成',
data: { deletedCount },
});
} catch (error) {
console.error('清理旧日志失败:', error);
res.status(500).json({
success: false,
message: '清理旧日志失败',
error: error.message,
});
}
});
module.exports = router;
+39 -5
View File
@@ -34,7 +34,7 @@ router.get('/', async (req, res) => {
const { count, rows } = await ConsumableRecord.findAndCountAll({
where,
include: [
{ model: Consumable, attributes: ['name', 'category', 'unit'] }
{ model: Consumable, as: 'consumable', attributes: ['name', 'category', 'unit'] }
],
offset,
limit: parseInt(pageSize),
@@ -123,17 +123,36 @@ router.post('/', async (req, res) => {
router.get('/statistics', async (req, res) => {
try {
const { startDate, endDate } = req.query;
const { startDate, endDate, category } = req.query;
const dateWhere = {};
if (startDate && endDate) {
// 修复日期范围查询:startDate 从 00:00:00 开始,endDate 到 23:59:59 结束
const startDateTime = new Date(startDate);
const endDateTime = new Date(endDate);
endDateTime.setHours(23, 59, 59, 999); // 设置 endDate 为当天最后一刻
dateWhere.createdAt = {
[Op.between]: [new Date(startDate), new Date(endDate)]
[Op.gte]: startDateTime,
[Op.lte]: endDateTime
};
}
const consumableWhere = {};
if (category) {
consumableWhere.category = category;
}
const records = await ConsumableRecord.findAll({
where: dateWhere,
include: [
{
model: Consumable,
as: 'consumable',
attributes: ['name', 'category'],
where: Object.keys(consumableWhere).length > 0 ? consumableWhere : undefined
}
],
attributes: ['type', 'quantity']
});
@@ -163,7 +182,12 @@ router.get('/statistics', async (req, res) => {
const recentRecords = await ConsumableRecord.findAll({
where: dateWhere,
include: [
{ model: Consumable, attributes: ['name', 'category'] }
{
model: Consumable,
as: 'consumable',
attributes: ['name', 'category'],
where: Object.keys(consumableWhere).length > 0 ? consumableWhere : undefined
}
],
order: [['createdAt', 'DESC']],
limit: 10
@@ -180,7 +204,17 @@ router.get('/statistics', async (req, res) => {
totalQuantity: data.totalQuantity,
count: data.count
})),
recentRecords
recentRecords: recentRecords.map(record => ({
recordId: record.recordId,
type: record.type,
quantity: record.quantity,
operator: record.operator,
createdAt: record.createdAt,
consumableId: record.consumableId,
consumableName: record.consumable?.name || '未知耗材',
category: record.consumable?.category || null,
unit: record.consumable?.unit || '个'
}))
});
} catch (error) {
res.status(500).json({ error: error.message });
+20 -9
View File
@@ -223,12 +223,17 @@ router.get('/low-stock', async (req, res) => {
try {
const consumables = await Consumable.findAll({
where: {
status: 'active',
[Op.and]: [
sequelize.where(sequelize.col('currentStock'), {
[Op.lte]: sequelize.col('minStock')
}),
sequelize.where(sequelize.col('minStock'), {
[Op.gt]: 0
})
]
}
},
order: [['currentStock', 'ASC']]
});
res.json(consumables);
} catch (error) {
@@ -239,35 +244,41 @@ router.get('/low-stock', async (req, res) => {
router.get('/statistics/summary', async (req, res) => {
try {
const consumables = await Consumable.findAll({
attributes: ['currentStock', 'unitPrice', 'category']
attributes: ['currentStock', 'unitPrice', 'category', 'minStock', 'status']
});
let total = consumables.length;
let total = 0;
let lowStock = 0;
let totalValue = 0;
const categoryMap = {};
consumables.forEach(item => {
if (item.status === 'inactive') return;
total++;
const currentStock = parseFloat(item.currentStock) || 0;
const unitPrice = parseFloat(item.unitPrice) || 0;
const minStockValue = parseFloat(item.minStock) || 0;
totalValue += currentStock * unitPrice;
if (currentStock <= (parseFloat(item.minStock) || 0)) {
if (minStockValue > 0 && currentStock <= minStockValue) {
lowStock++;
}
if (item.category) {
if (!categoryMap[item.category]) {
categoryMap[item.category] = 0;
categoryMap[item.category] = { count: 0, totalQuantity: 0 };
}
categoryMap[item.category]++;
categoryMap[item.category].count++;
categoryMap[item.category].totalQuantity += currentStock;
}
});
const byCategory = Object.entries(categoryMap).map(([category, count]) => ({
const byCategory = Object.entries(categoryMap).map(([category, data]) => ({
category,
count
count: data.count,
totalQuantity: data.totalQuantity
}));
res.json({
@@ -292,7 +303,7 @@ router.get('/inout/records', async (req, res) => {
order: [['createdAt', 'DESC']],
include: [{
model: Consumable,
as: 'Consumable',
as: 'consumable',
attributes: ['consumableId', 'name', 'category']
}]
});
+62 -62
View File
@@ -60,6 +60,68 @@ router.get('/device/:deviceId', async (req, res) => {
}
});
router.get('/device/:deviceId/with-ports', async (req, res) => {
try {
const { deviceId } = req.params;
const networkCards = await NetworkCard.findAll({
where: { deviceId },
order: [['slotNumber', 'ASC'], ['name', 'ASC']]
});
const cardsWithPorts = await Promise.all(
networkCards.map(async (card) => {
const ports = await DevicePort.findAll({
where: { nicId: card.nicId },
order: [['portName', 'ASC']]
});
const freeCount = ports.filter(p => p.status === 'free').length;
const occupiedCount = ports.filter(p => p.status === 'occupied').length;
const faultCount = ports.filter(p => p.status === 'fault').length;
return {
...card.toJSON(),
ports,
stats: {
total: ports.length,
free: freeCount,
occupied: occupiedCount,
fault: faultCount
}
};
})
);
const ungroupedPorts = await DevicePort.findAll({
where: { deviceId, nicId: null },
order: [['portName', 'ASC']]
});
if (ungroupedPorts.length > 0) {
cardsWithPorts.push({
nicId: '_ungrouped',
name: '未分组端口',
description: '未分配到网卡的端口',
portCount: ungroupedPorts.length,
isUngrouped: true,
ports: ungroupedPorts,
stats: {
total: ungroupedPorts.length,
free: ungroupedPorts.filter(p => p.status === 'free').length,
occupied: ungroupedPorts.filter(p => p.status === 'occupied').length,
fault: ungroupedPorts.filter(p => p.status === 'fault').length
}
});
}
res.json(cardsWithPorts);
} catch (error) {
console.error('获取网卡及端口失败:', error);
res.status(500).json({ error: error.message });
}
});
router.get('/:nicId', async (req, res) => {
try {
const networkCard = await NetworkCard.findByPk(req.params.nicId, {
@@ -198,66 +260,4 @@ router.delete('/:nicId', async (req, res) => {
}
});
router.get('/device/:deviceId/with-ports', async (req, res) => {
try {
const { deviceId } = req.params;
const networkCards = await NetworkCard.findAll({
where: { deviceId },
order: [['slotNumber', 'ASC'], ['name', 'ASC']]
});
const cardsWithPorts = await Promise.all(
networkCards.map(async (card) => {
const ports = await DevicePort.findAll({
where: { nicId: card.nicId },
order: [['portName', 'ASC']]
});
const freeCount = ports.filter(p => p.status === 'free').length;
const occupiedCount = ports.filter(p => p.status === 'occupied').length;
const faultCount = ports.filter(p => p.status === 'fault').length;
return {
...card.toJSON(),
ports,
stats: {
total: ports.length,
free: freeCount,
occupied: occupiedCount,
fault: faultCount
}
};
})
);
const ungroupedPorts = await DevicePort.findAll({
where: { deviceId, nicId: null },
order: [['portName', 'ASC']]
});
if (ungroupedPorts.length > 0) {
cardsWithPorts.push({
nicId: '_ungrouped',
name: '未分组端口',
description: '未分配到网卡的端口',
portCount: ungroupedPorts.length,
isUngrouped: true,
ports: ungroupedPorts,
stats: {
total: ungroupedPorts.length,
free: ungroupedPorts.filter(p => p.status === 'free').length,
occupied: ungroupedPorts.filter(p => p.status === 'occupied').length,
fault: ungroupedPorts.filter(p => p.status === 'fault').length
}
});
}
res.json(cardsWithPorts);
} catch (error) {
console.error('获取网卡及端口失败:', error);
res.status(500).json({ error: error.message });
}
});
module.exports = router;