feat: 添加数据库索引优化查询性能

refactor(前端): 使用useMemo和useCallback优化性能
perf(后端): 优化统计查询性能
style: 统一前端样式定义
build: 添加创建索引脚本
This commit is contained in:
zhang1106
2026-01-20 14:31:20 +08:00
parent 88e000a41a
commit a2f0032bad
25 changed files with 803 additions and 701 deletions
+33 -31
View File
@@ -124,40 +124,42 @@ router.post('/', async (req, res) => {
router.get('/statistics', async (req, res) => {
try {
const { startDate, endDate } = req.query;
const dateWhere = {};
if (startDate && endDate) {
dateWhere.createdAt = {
[Op.between]: [new Date(startDate), new Date(endDate)]
};
}
const inCount = await ConsumableRecord.count({
where: { ...dateWhere, type: 'in' }
});
const outCount = await ConsumableRecord.count({
where: { ...dateWhere, type: 'out' }
});
const inQuantity = await ConsumableRecord.sum('quantity', {
where: { ...dateWhere, type: 'in' }
}) || 0;
const outQuantity = await ConsumableRecord.sum('quantity', {
where: { ...dateWhere, type: 'out' }
}) || 0;
const byType = await ConsumableRecord.findAll({
const records = await ConsumableRecord.findAll({
where: dateWhere,
attributes: [
'type',
[sequelize.fn('SUM', sequelize.col('quantity')), 'totalQuantity'],
[sequelize.fn('COUNT', '*'), 'count']
],
group: ['type']
attributes: ['type', 'quantity']
});
let inCount = 0;
let outCount = 0;
let inQuantity = 0;
let outQuantity = 0;
const typeMap = {};
records.forEach(item => {
const qty = parseFloat(item.quantity) || 0;
if (item.type === 'in') {
inCount++;
inQuantity += qty;
} else if (item.type === 'out') {
outCount++;
outQuantity += qty;
}
if (!typeMap[item.type]) {
typeMap[item.type] = { count: 0, totalQuantity: 0 };
}
typeMap[item.type].count++;
typeMap[item.type].totalQuantity += qty;
});
const recentRecords = await ConsumableRecord.findAll({
where: dateWhere,
include: [
@@ -166,17 +168,17 @@ router.get('/statistics', async (req, res) => {
order: [['createdAt', 'DESC']],
limit: 10
});
res.json({
inCount,
outCount,
inQuantity,
outQuantity,
netQuantity: inQuantity - outQuantity,
byType: byType.map(item => ({
type: item.type,
totalQuantity: item.dataValues.totalQuantity,
count: item.dataValues.count
byType: Object.entries(typeMap).map(([type, data]) => ({
type,
totalQuantity: data.totalQuantity,
count: data.count
})),
recentRecords
});
+30 -21
View File
@@ -179,34 +179,43 @@ router.get('/low-stock', async (req, res) => {
router.get('/statistics/summary', async (req, res) => {
try {
const total = await Consumable.count();
const lowStock = await Consumable.count({
where: sequelize.where(sequelize.col('currentStock'), {
[Op.lte]: sequelize.col('minStock')
})
});
const consumables = await Consumable.findAll({
attributes: ['currentStock', 'unitPrice']
attributes: ['currentStock', 'unitPrice', 'category']
});
const totalValue = consumables.reduce((sum, item) => {
return sum + (parseFloat(item.currentStock) || 0) * (parseFloat(item.unitPrice) || 0);
}, 0);
const byCategory = await Consumable.findAll({
attributes: ['category', [sequelize.fn('COUNT', '*'), 'count']],
group: ['category']
let total = consumables.length;
let lowStock = 0;
let totalValue = 0;
const categoryMap = {};
consumables.forEach(item => {
const currentStock = parseFloat(item.currentStock) || 0;
const unitPrice = parseFloat(item.unitPrice) || 0;
totalValue += currentStock * unitPrice;
if (currentStock <= (parseFloat(item.minStock) || 0)) {
lowStock++;
}
if (item.category) {
if (!categoryMap[item.category]) {
categoryMap[item.category] = 0;
}
categoryMap[item.category]++;
}
});
const byCategory = Object.entries(categoryMap).map(([category, count]) => ({
category,
count
}));
res.json({
total,
lowStock,
totalValue: totalValue.toFixed(2),
byCategory: byCategory.map(item => ({
category: item.category,
count: item.dataValues.count
}))
byCategory
});
} catch (error) {
res.status(500).json({ error: error.message });
+53 -64
View File
@@ -19,48 +19,60 @@ router.get('/stats', async (req, res) => {
if (endDate) where.createdAt[Op.lte] = new Date(endDate + ' 23:59:59');
}
const total = await Ticket.count({ where });
const Sequelize = require('sequelize');
const statusStats = await Ticket.findAll({
where,
attributes: [
'status',
[require('sequelize').fn('COUNT', '*'), 'count']
],
group: ['status']
});
const priorityStats = await Ticket.findAll({
where,
attributes: [
'priority',
[require('sequelize').fn('COUNT', '*'), 'count']
],
group: ['priority']
});
const categoryStats = await Ticket.findAll({
where,
attributes: [
'faultCategory',
[require('sequelize').fn('COUNT', '*'), 'count']
],
group: ['faultCategory']
});
const monthlyStats = await Ticket.findAll({
where,
attributes: [
[dbDialect === 'mysql'
? require('sequelize').fn('DATE_FORMAT', require('sequelize').col('createdAt'), '%Y-%m')
: require('sequelize').fn('strftime', '%Y-%m', require('sequelize').col('createdAt')),
'month'],
[require('sequelize').fn('COUNT', '*'), 'count']
],
group: ['month'],
order: [['month', 'DESC']],
limit: 12
});
const [total, statusStats, priorityStats, categoryStats, monthlyStats, deviceStats, dailyStats] = await Promise.all([
Ticket.count({ where }),
Ticket.findAll({
where,
attributes: ['status', [Sequelize.fn('COUNT', '*'), 'count']],
group: ['status']
}),
Ticket.findAll({
where,
attributes: ['priority', [Sequelize.fn('COUNT', '*'), 'count']],
group: ['priority']
}),
Ticket.findAll({
where,
attributes: ['faultCategory', [Sequelize.fn('COUNT', '*'), 'count']],
group: ['faultCategory']
}),
Ticket.findAll({
where,
attributes: [
[dbDialect === 'mysql'
? Sequelize.fn('DATE_FORMAT', Sequelize.col('createdAt'), '%Y-%m')
: Sequelize.fn('strftime', '%Y-%m', Sequelize.col('createdAt')),
'month'],
[Sequelize.fn('COUNT', '*'), 'count']
],
group: ['month'],
order: [['month', 'DESC']],
limit: 12
}),
Ticket.findAll({
where,
attributes: [
'deviceId',
'deviceName',
[Sequelize.fn('COUNT', '*'), 'count'],
[Sequelize.fn('MAX', Sequelize.col('createdAt')), 'lastFaultTime']
],
group: ['deviceId', 'deviceName'],
order: [[Sequelize.fn('COUNT', '*'), 'DESC']],
limit: 10
}),
Ticket.findAll({
where,
attributes: [
[Sequelize.fn('DATE', Sequelize.col('createdAt')), 'date'],
[Sequelize.fn('COUNT', '*'), 'created']
],
group: ['date'],
order: [['date', 'ASC']]
})
]);
const statusData = statusStats.map(s => s.dataValues);
const pending = statusData.find(s => s.status === 'pending')?.count || 0;
@@ -88,19 +100,6 @@ router.get('/stats', async (req, res) => {
avgTime: 0
}));
const deviceStats = await Ticket.findAll({
where,
attributes: [
'deviceId',
'deviceName',
[require('sequelize').fn('COUNT', '*'), 'count'],
[require('sequelize').fn('MAX', require('sequelize').col('createdAt')), 'lastFaultTime']
],
group: ['deviceId', 'deviceName'],
order: [[require('sequelize').fn('COUNT', '*'), 'DESC']],
limit: 10
});
const byDevice = deviceStats.map(d => ({
deviceId: d.deviceId,
deviceName: d.deviceName,
@@ -109,16 +108,6 @@ router.get('/stats', async (req, res) => {
deviceType: ''
}));
const dailyStats = await Ticket.findAll({
where,
attributes: [
[require('sequelize').fn('DATE', require('sequelize').col('createdAt')), 'date'],
[require('sequelize').fn('COUNT', '*'), 'created']
],
group: ['date'],
order: [['date', 'ASC']]
});
const trend = dailyStats.map(d => ({
date: d.dataValues.date,
created: d.dataValues.created,
+28 -10
View File
@@ -51,20 +51,38 @@ router.get('/', authMiddleware, async (req, res) => {
order: [['createdAt', 'DESC']]
});
for (const user of users) {
const userRoles = await UserRole.findAll({
if (users.length > 0) {
const userIds = users.map(u => u.userId);
const allUserRoles = await UserRole.findAll({
include: [{
model: Role,
where: { status: 'active' }
where: { status: 'active' },
attributes: ['roleId', 'roleName', 'roleCode']
}],
where: { UserId: user.userId }
where: {
UserId: { [Op.in]: userIds }
}
});
const userRolesMap = {};
allUserRoles.forEach(ur => {
if (!userRolesMap[ur.UserId]) {
userRolesMap[ur.UserId] = [];
}
userRolesMap[ur.UserId].push({
roleId: ur.Role.roleId,
roleName: ur.Role.roleName,
roleCode: ur.Role.roleCode
});
});
users.forEach(user => {
user.dataValues.roles = userRolesMap[user.userId] || [];
});
} else {
users.forEach(user => {
user.dataValues.roles = [];
});
user.dataValues.roles = userRoles.map(ur => ({
roleId: ur.Role.roleId,
roleName: ur.Role.roleName,
roleCode: ur.Role.roleCode
}));
}
res.json({