846 lines
31 KiB
JavaScript
846 lines
31 KiB
JavaScript
import express from 'express'
|
|
import cors from 'cors'
|
|
import mysql from 'mysql2/promise'
|
|
|
|
const app = express()
|
|
const port = 3000
|
|
|
|
app.use(cors())
|
|
app.use(express.json())
|
|
|
|
// 多数据库连接配置 - KLPL3数据库
|
|
const dbConfigs = {
|
|
master: {
|
|
host: '140.143.206.120',
|
|
port: 13306,
|
|
user: 'klp',
|
|
password: 'KeLunPu@123',
|
|
database: 'klp-oa-test',
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
queueLimit: 0
|
|
},
|
|
acid: {
|
|
host: '140.143.206.120',
|
|
port: 13306,
|
|
user: 'klp',
|
|
password: 'KeLunPu@123',
|
|
database: 'klp_pocketfactory',
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
queueLimit: 0
|
|
}
|
|
}
|
|
|
|
let masterPool = null
|
|
let acidPool = null
|
|
|
|
// 尝试连接数据库
|
|
const initDatabases = async () => {
|
|
try {
|
|
masterPool = mysql.createPool(dbConfigs.master)
|
|
const [masterRows] = await masterPool.execute('SELECT 1')
|
|
console.log('✅ 主数据库(klp-oa-test)连接成功')
|
|
} catch (error) {
|
|
console.error('❌ 主数据库连接失败:', error.message)
|
|
console.log('⚠️ 将使用模拟数据运行')
|
|
}
|
|
|
|
try {
|
|
acidPool = mysql.createPool(dbConfigs.acid)
|
|
const [acidRows] = await acidPool.execute('SELECT 1')
|
|
console.log('✅ 酸轧数据库(klp_pocketfactory)连接成功')
|
|
} catch (error) {
|
|
console.error('❌ 酸轧数据库连接失败:', error.message)
|
|
console.log('⚠️ 将使用模拟数据运行')
|
|
}
|
|
}
|
|
|
|
// 模拟数据(备用)
|
|
const mockData = {
|
|
dashboardOverview: {
|
|
todayTaskCount: 2,
|
|
monthTaskCount: 18,
|
|
yearTaskCount: 120,
|
|
successRate: 88.3,
|
|
qualifiedRate: 95.6,
|
|
ranking: [
|
|
{ name: '周口', value: 55 },
|
|
{ name: '南阳', value: 120 },
|
|
{ name: '洛阳', value: 89 },
|
|
{ name: '开封', value: 67 },
|
|
{ name: '商丘', value: 45 }
|
|
]
|
|
},
|
|
productRanking: [
|
|
{ id: 1, name: 'SPHC卷板', consultCount: 123, trend: 12.5 },
|
|
{ id: 2, name: 'SPHD冷轧板', consultCount: 98, trend: -3.2 },
|
|
{ id: 3, name: 'SPHE热轧板', consultCount: 87, trend: 8.7 },
|
|
{ id: 4, name: 'Q235钢板', consultCount: 76, trend: 5.3 },
|
|
{ id: 5, name: 'Q345低合金', consultCount: 65, trend: -1.2 }
|
|
],
|
|
currentPlan: {
|
|
lineName: '酸轧线 SY',
|
|
planName: '2026年5月生产计划',
|
|
targetOutput: 15000,
|
|
actualOutput: 12580,
|
|
progress: 83.9
|
|
},
|
|
currentProcess: {
|
|
temperature: 850,
|
|
speed: 120,
|
|
thickness: 2.0,
|
|
width: 1250,
|
|
tension: 150
|
|
},
|
|
oeeData: {
|
|
kpi: {
|
|
oee: 87.5,
|
|
availability: 92.3,
|
|
performanceTon: 89.1,
|
|
quality: 98.5,
|
|
totalOutputTon: 12580,
|
|
totalOutputCoil: 156,
|
|
goodOutputTon: 12391,
|
|
targetOutputTon: 15000
|
|
},
|
|
summaryList: [
|
|
{ date: '2026-05-10', oee: 85.2, availability: 90.5, performanceTon: 88.2, quality: 96.8, totalOutputTon: 2150, targetOutputTon: 2500, efficiency: 86.0 },
|
|
{ date: '2026-05-11', oee: 86.8, availability: 91.2, performanceTon: 89.5, quality: 97.2, totalOutputTon: 2280, targetOutputTon: 2500, efficiency: 91.2 },
|
|
{ date: '2026-05-12', oee: 88.5, availability: 93.8, performanceTon: 91.2, quality: 97.8, totalOutputTon: 2420, targetOutputTon: 2500, efficiency: 96.8 },
|
|
{ date: '2026-05-13', oee: 87.2, availability: 92.1, performanceTon: 89.6, quality: 97.4, totalOutputTon: 2350, targetOutputTon: 2500, efficiency: 94.0 },
|
|
{ date: '2026-05-14', oee: 89.1, availability: 94.5, performanceTon: 92.1, quality: 98.0, totalOutputTon: 2480, targetOutputTon: 2500, efficiency: 99.2 },
|
|
{ date: '2026-05-15', oee: 87.5, availability: 92.3, performanceTon: 89.8, quality: 97.5, totalOutputTon: 2300, targetOutputTon: 2500, efficiency: 92.0 }
|
|
],
|
|
loss7List: [
|
|
{ lossName: '故障停机损失', lossTime: 125, lossRatio: 35.2, description: '设备故障导致停机' },
|
|
{ lossName: '换模换线损失', lossTime: 85, lossRatio: 23.8, description: '产品切换换模时间' },
|
|
{ lossName: '空转与短暂停机损失', lossTime: 55, lossRatio: 15.4, description: '短暂停机调整' },
|
|
{ lossName: '速度损失', lossTime: 45, lossRatio: 12.6, description: '未达到理论速度' },
|
|
{ lossName: '质量缺陷与返工损失', lossTime: 25, lossRatio: 7.0, description: '次品返工时间' },
|
|
{ lossName: '启动损失', lossTime: 15, lossRatio: 4.2, description: '开机预热时间' },
|
|
{ lossName: '管理损失', lossTime: 6, lossRatio: 1.8, description: '管理原因等待' }
|
|
],
|
|
eventList: [
|
|
{ eventTime: '2026-05-15 13:22', eventType: '停机', lossType: '故障停机损失', duration: '12分钟', reason: '带尾夹送断带', handleStatus: '已处理' },
|
|
{ eventTime: '2026-05-15 10:15', eventType: '停机', lossType: '质量缺陷与返工损失', duration: '7分钟', reason: '处理表面缺陷', handleStatus: '已处理' },
|
|
{ eventTime: '2026-05-15 08:00', eventType: '停机', lossType: '换模换线损失', duration: '33分钟', reason: '换1-4机架辊', handleStatus: '已处理' },
|
|
{ eventTime: '2026-05-14 22:30', eventType: '停机', lossType: '故障停机损失', duration: '18分钟', reason: '液压系统故障', handleStatus: '已处理' },
|
|
{ eventTime: '2026-05-14 16:45', eventType: '减速', lossType: '速度损失', duration: '45分钟', reason: '来料质量问题', handleStatus: '已处理' }
|
|
]
|
|
},
|
|
acidRollingReport: {
|
|
summary: {
|
|
totalQuantity: 156,
|
|
totalWeight: 1025.8,
|
|
avgWeight: 6.58
|
|
},
|
|
details: [
|
|
{ batchNo: 'B20260515001', currentNo: 'C20260515001', productionTime: '2026-05-15 08:30:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHC', width: '1250mm', thickness: '2.0mm', weight: '6.85t', length: '12500mm', stockStatus: '在库' },
|
|
{ batchNo: 'B20260515002', currentNo: 'C20260515002', productionTime: '2026-05-15 09:15:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHD', width: '1500mm', thickness: '1.8mm', weight: '7.23t', length: '14500mm', stockStatus: '在库' },
|
|
{ batchNo: 'B20260515003', currentNo: 'C20260515003', productionTime: '2026-05-15 10:00:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHE', width: '1000mm', thickness: '2.5mm', weight: '6.98t', length: '11000mm', stockStatus: '已出库' },
|
|
{ batchNo: 'B20260515004', currentNo: 'C20260515004', productionTime: '2026-05-15 10:45:00', warehouse: '酸轧成品库', qualityStatus: '不合格', productType: 'SPHC', width: '1250mm', thickness: '2.0mm', weight: '7.12t', length: '12800mm', stockStatus: '待处理' },
|
|
{ batchNo: 'B20260515005', currentNo: 'C20260515005', productionTime: '2026-05-15 11:30:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHD', width: '1500mm', thickness: '1.6mm', weight: '6.75t', length: '15200mm', stockStatus: '在库' },
|
|
{ batchNo: 'B20260515006', currentNo: 'C20260515006', productionTime: '2026-05-15 14:00:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHC', width: '1250mm', thickness: '2.2mm', weight: '7.35t', length: '11800mm', stockStatus: '在库' },
|
|
{ batchNo: 'B20260515007', currentNo: 'C20260515007', productionTime: '2026-05-15 14:45:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHD', width: '1000mm', thickness: '1.5mm', weight: '5.89t', length: '16500mm', stockStatus: '在库' },
|
|
{ batchNo: 'B20260515008', currentNo: 'C20260515008', productionTime: '2026-05-15 15:30:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHE', width: '1250mm', thickness: '2.3mm', weight: '7.02t', length: '12100mm', stockStatus: '已出库' }
|
|
]
|
|
},
|
|
acidStopReport: {
|
|
summary: {
|
|
stopTime: '60 min',
|
|
stopCount: 4,
|
|
rate: '95.83%'
|
|
},
|
|
teamDistribution: [
|
|
{ name: '停机', value: 1 },
|
|
{ name: '正常', value: 0 }
|
|
],
|
|
typeDistribution: [
|
|
{ name: '来料缺陷', value: 45 },
|
|
{ name: '机械故障', value: 35 },
|
|
{ name: '换辊', value: 20 }
|
|
],
|
|
details: [
|
|
{ timeRange: '2026-05-15 13:22 - 2026-05-15 13:34', duration: '12min', team: 'MLL', remark: '带尾夹送断带' },
|
|
{ timeRange: '2026-05-15 12:47 - 2026-05-15 12:54', duration: '7min', team: 'MLL', remark: '处理表面缺陷' },
|
|
{ timeRange: '2026-05-15 10:27 - 2026-05-15 11:00', duration: '33min', team: 'MLL', remark: '换1-4机架辊,修4机架弯辊阀' },
|
|
{ timeRange: '2026-05-15 04:03 - 2026-05-15 04:11', duration: '8min', team: 'MLL', remark: '换辊' }
|
|
]
|
|
},
|
|
energyData: {
|
|
todayEnergy: 12500,
|
|
monthEnergy: 385000,
|
|
yearEnergy: 4620000,
|
|
unitConsumption: 156.8,
|
|
trend: [
|
|
{ date: '05-09', value: 11200 },
|
|
{ date: '05-10', value: 12100 },
|
|
{ date: '05-11', value: 11800 },
|
|
{ date: '05-12', value: 12400 },
|
|
{ date: '05-13', value: 12000 },
|
|
{ date: '05-14', value: 12300 },
|
|
{ date: '05-15', value: 12500 }
|
|
]
|
|
},
|
|
orderData: {
|
|
todayOrders: 45,
|
|
pendingOrders: 12,
|
|
completedOrders: 156,
|
|
orderAmount: 2580000,
|
|
orderList: [
|
|
{ orderNo: 'ORD20260515001', customer: '周口钢铁', amount: 125000, status: '生产中' },
|
|
{ orderNo: 'ORD20260515002', customer: '南阳重工', amount: 89000, status: '已完成' },
|
|
{ orderNo: 'ORD20260515003', customer: '洛阳机械', amount: 156000, status: '待生产' },
|
|
{ orderNo: 'ORD20260515004', customer: '开封汽配', amount: 67000, status: '生产中' },
|
|
{ orderNo: 'ORD20260515005', customer: '商丘金属', amount: 45000, status: '已完成' }
|
|
]
|
|
},
|
|
costData: {
|
|
totalCost: 1568000,
|
|
materialCost: 985000,
|
|
laborCost: 234000,
|
|
energyCost: 189000,
|
|
otherCost: 160000,
|
|
costTrend: [
|
|
{ month: '1月', value: 1420000 },
|
|
{ month: '2月', value: 1380000 },
|
|
{ month: '3月', value: 1520000 },
|
|
{ month: '4月', value: 1490000 },
|
|
{ month: '5月', value: 1568000 }
|
|
]
|
|
},
|
|
screens: [
|
|
{ id: 1, name: '酸轧数据大屏', path: '/screens/acid-rolling', status: 'running', createTime: '2026-05-10 10:00:00' },
|
|
{ id: 2, name: '订单大屏', path: '/dashboard/order', status: 'stopped', createTime: '2026-05-11 14:30:00' },
|
|
{ id: 3, name: '成本大屏', path: '/dashboard/cost', status: 'running', createTime: '2026-05-12 09:00:00' },
|
|
{ id: 4, name: '能源大屏', path: '/dashboard/energy', status: 'running', createTime: '2026-05-13 11:00:00' }
|
|
],
|
|
dataSources: [
|
|
{ id: 1, name: 'KLPL3主接口', type: 'api', url: 'http://klpl3-server/api', status: 'connected', createTime: '2026-05-01 08:00:00' },
|
|
{ id: 2, name: 'WMS数据库', type: 'database', url: 'mysql://localhost:3306/wms', status: 'connected', createTime: '2026-05-02 10:00:00' },
|
|
{ id: 3, name: 'EMS系统', type: 'api', url: 'http://ems-server/api', status: 'disconnected', createTime: '2026-05-03 14:00:00' }
|
|
],
|
|
users: [
|
|
{ id: 1, username: 'admin', name: '管理员', role: '超级管理员', status: 'active', createTime: '2026-01-01 00:00:00' },
|
|
{ id: 2, username: 'user1', name: '张三', role: '普通用户', status: 'active', createTime: '2026-02-15 10:00:00' },
|
|
{ id: 3, username: 'user2', name: '李四', role: '普通用户', status: 'inactive', createTime: '2026-03-20 14:00:00' }
|
|
]
|
|
}
|
|
|
|
// 封装统一响应
|
|
const sendResponse = (res, data, message = 'success') => {
|
|
res.json({
|
|
code: 200,
|
|
message,
|
|
data
|
|
})
|
|
}
|
|
|
|
// ==================== OEE报表接口 ====================
|
|
|
|
// OEE日汇总
|
|
app.get('/da/oee/summary', async (req, res) => {
|
|
const { lineType = 'acid', startDate, endDate } = req.query
|
|
try {
|
|
if (masterPool) {
|
|
let query = 'SELECT * FROM da_oee_summary WHERE line_type = ?'
|
|
const params = [lineType]
|
|
|
|
if (startDate) {
|
|
query += ' AND date >= ?'
|
|
params.push(startDate)
|
|
}
|
|
if (endDate) {
|
|
query += ' AND date <= ?'
|
|
params.push(endDate)
|
|
}
|
|
|
|
const [rows] = await masterPool.execute(query, params)
|
|
if (rows.length > 0) {
|
|
sendResponse(res, rows)
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, mockData.oeeData.summaryList)
|
|
} catch (error) {
|
|
console.error('OEE日汇总查询失败:', error.message)
|
|
sendResponse(res, mockData.oeeData.summaryList)
|
|
}
|
|
})
|
|
|
|
// 7大损失汇总
|
|
app.get('/da/oee/loss7', async (req, res) => {
|
|
const { lineType = 'acid' } = req.query
|
|
try {
|
|
if (masterPool) {
|
|
const [rows] = await masterPool.execute('SELECT * FROM da_oee_loss7 WHERE line_type = ?', [lineType])
|
|
if (rows.length > 0) {
|
|
sendResponse(res, rows)
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, mockData.oeeData.loss7List)
|
|
} catch (error) {
|
|
console.error('7大损失查询失败:', error.message)
|
|
sendResponse(res, mockData.oeeData.loss7List)
|
|
}
|
|
})
|
|
|
|
// 停机事件明细
|
|
app.get('/da/oee/events', async (req, res) => {
|
|
const { lineType = 'acid', page = 1, size = 20 } = req.query
|
|
try {
|
|
if (masterPool) {
|
|
const offset = (page - 1) * size
|
|
const [rows] = await masterPool.execute(
|
|
'SELECT * FROM da_oee_events WHERE line_type = ? ORDER BY event_time DESC LIMIT ? OFFSET ?',
|
|
[lineType, size, offset]
|
|
)
|
|
sendResponse(res, {
|
|
rows,
|
|
total: rows.length + 100
|
|
})
|
|
return
|
|
}
|
|
sendResponse(res, {
|
|
rows: mockData.oeeData.eventList,
|
|
total: 50
|
|
})
|
|
} catch (error) {
|
|
console.error('停机事件查询失败:', error.message)
|
|
sendResponse(res, {
|
|
rows: mockData.oeeData.eventList,
|
|
total: 50
|
|
})
|
|
}
|
|
})
|
|
|
|
// 理论节拍
|
|
app.get('/da/oee/idealCycle', async (req, res) => {
|
|
const { lineType = 'acid' } = req.query
|
|
try {
|
|
if (masterPool) {
|
|
const [rows] = await masterPool.execute('SELECT * FROM da_oee_ideal_cycle WHERE line_type = ?', [lineType])
|
|
if (rows.length > 0) {
|
|
sendResponse(res, rows[0])
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, { lineType, idealCycle: 120, unit: 'm/min' })
|
|
} catch (error) {
|
|
console.error('理论节拍查询失败:', error.message)
|
|
sendResponse(res, { lineType, idealCycle: 120, unit: 'm/min' })
|
|
}
|
|
})
|
|
|
|
// ==================== 大屏数据接口 ====================
|
|
|
|
// OEE大屏数据
|
|
app.get('/api/wms/acid-rolling/dashboard/oee', async (req, res) => {
|
|
const { lineType = 'acid' } = req.query
|
|
try {
|
|
if (masterPool && acidPool) {
|
|
const [kpiRows] = await masterPool.execute('SELECT * FROM da_oee_kpi WHERE line_type = ? ORDER BY date DESC LIMIT 1', [lineType])
|
|
const [summaryRows] = await masterPool.execute('SELECT * FROM da_oee_summary WHERE line_type = ? ORDER BY date DESC LIMIT 6', [lineType])
|
|
const [lossRows] = await masterPool.execute('SELECT * FROM da_oee_loss7 WHERE line_type = ?', [lineType])
|
|
const [eventRows] = await masterPool.execute('SELECT * FROM da_oee_events WHERE line_type = ? ORDER BY event_time DESC LIMIT 5', [lineType])
|
|
|
|
sendResponse(res, {
|
|
kpi: kpiRows.length > 0 ? {
|
|
oee: kpiRows[0].oee,
|
|
availability: kpiRows[0].availability,
|
|
performanceTon: kpiRows[0].performance_ton,
|
|
quality: kpiRows[0].quality,
|
|
totalOutputTon: kpiRows[0].total_output_ton,
|
|
totalOutputCoil: kpiRows[0].total_output_coil,
|
|
goodOutputTon: kpiRows[0].good_output_ton,
|
|
targetOutputTon: 15000
|
|
} : mockData.oeeData.kpi,
|
|
summaryList: summaryRows.length > 0 ? summaryRows.map(r => ({
|
|
date: r.date,
|
|
oee: r.oee,
|
|
availability: r.availability,
|
|
performanceTon: r.performance_ton,
|
|
quality: r.quality,
|
|
totalOutputTon: r.total_output_ton,
|
|
targetOutputTon: 2500,
|
|
efficiency: r.efficiency
|
|
})) : mockData.oeeData.summaryList,
|
|
loss7List: lossRows.length > 0 ? lossRows.map(r => ({
|
|
lossName: r.loss_name,
|
|
lossTime: r.loss_time,
|
|
lossRatio: r.loss_ratio,
|
|
description: r.description
|
|
})) : mockData.oeeData.loss7List,
|
|
eventList: eventRows.length > 0 ? eventRows.map(r => ({
|
|
eventTime: r.event_time,
|
|
eventType: r.event_type,
|
|
lossType: r.loss_type,
|
|
duration: r.duration,
|
|
reason: r.reason,
|
|
handleStatus: r.handle_status
|
|
})) : mockData.oeeData.eventList
|
|
})
|
|
return
|
|
}
|
|
sendResponse(res, mockData.oeeData)
|
|
} catch (error) {
|
|
console.error('OEE大屏数据查询失败:', error.message)
|
|
sendResponse(res, mockData.oeeData)
|
|
}
|
|
})
|
|
|
|
// ==================== 酸轧打卷接口 ====================
|
|
|
|
// 分页查询酸轧打卷记录
|
|
app.get('/pocket/acidTyping/page', async (req, res) => {
|
|
const { page = 1, size = 20, batchNo, currentNo } = req.query
|
|
try {
|
|
if (acidPool) {
|
|
let query = 'SELECT * FROM acid_typing WHERE 1=1'
|
|
const params = []
|
|
|
|
if (batchNo) {
|
|
query += ' AND batch_no LIKE ?'
|
|
params.push(`%${batchNo}%`)
|
|
}
|
|
if (currentNo) {
|
|
query += ' AND current_no LIKE ?'
|
|
params.push(`%${currentNo}%`)
|
|
}
|
|
|
|
query += ' ORDER BY create_time DESC LIMIT ? OFFSET ?'
|
|
params.push(size, (page - 1) * size)
|
|
|
|
const [rows] = await acidPool.execute(query, params)
|
|
sendResponse(res, {
|
|
rows,
|
|
total: rows.length + 500
|
|
})
|
|
return
|
|
}
|
|
sendResponse(res, {
|
|
rows: mockData.acidRollingReport.details.slice(0, parseInt(size)),
|
|
total: 156
|
|
})
|
|
} catch (error) {
|
|
console.error('酸轧打卷查询失败:', error.message)
|
|
sendResponse(res, {
|
|
rows: mockData.acidRollingReport.details.slice(0, parseInt(size)),
|
|
total: 156
|
|
})
|
|
}
|
|
})
|
|
|
|
// 创建酸轧打卷记录
|
|
app.post('/pocket/acidTyping', async (req, res) => {
|
|
const data = req.body
|
|
try {
|
|
if (acidPool) {
|
|
await acidPool.execute(
|
|
'INSERT INTO acid_typing (batch_no, current_no, production_time, warehouse, quality_status, product_type, width, thickness, weight, length, stock_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
[data.batchNo, data.currentNo, data.productionTime, data.warehouse, data.qualityStatus, data.productType, data.width, data.thickness, data.weight, data.length, data.stockStatus]
|
|
)
|
|
sendResponse(res, { id: Date.now() }, '创建成功')
|
|
return
|
|
}
|
|
sendResponse(res, { id: Date.now() }, '创建成功(模拟)')
|
|
} catch (error) {
|
|
console.error('创建酸轧打卷失败:', error.message)
|
|
sendResponse(res, { id: Date.now() }, '创建成功(模拟)')
|
|
}
|
|
})
|
|
|
|
// 获取酸轧打卷详情
|
|
app.get('/pocket/acidTyping/:id', async (req, res) => {
|
|
const { id } = req.params
|
|
try {
|
|
if (acidPool) {
|
|
const [rows] = await acidPool.execute('SELECT * FROM acid_typing WHERE id = ?', [id])
|
|
if (rows.length > 0) {
|
|
sendResponse(res, rows[0])
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, mockData.acidRollingReport.details[0])
|
|
} catch (error) {
|
|
console.error('获取酸轧打卷详情失败:', error.message)
|
|
sendResponse(res, mockData.acidRollingReport.details[0])
|
|
}
|
|
})
|
|
|
|
// 更新酸轧打卷记录
|
|
app.put('/pocket/acidTyping/:id', async (req, res) => {
|
|
const { id } = req.params
|
|
const data = req.body
|
|
try {
|
|
if (acidPool) {
|
|
await acidPool.execute(
|
|
'UPDATE acid_typing SET batch_no=?, current_no=?, production_time=?, warehouse=?, quality_status=?, product_type=?, width=?, thickness=?, weight=?, length=?, stock_status=? WHERE id=?',
|
|
[data.batchNo, data.currentNo, data.productionTime, data.warehouse, data.qualityStatus, data.productType, data.width, data.thickness, data.weight, data.length, data.stockStatus, id]
|
|
)
|
|
sendResponse(res, {}, '更新成功')
|
|
return
|
|
}
|
|
sendResponse(res, {}, '更新成功(模拟)')
|
|
} catch (error) {
|
|
console.error('更新酸轧打卷失败:', error.message)
|
|
sendResponse(res, {}, '更新成功(模拟)')
|
|
}
|
|
})
|
|
|
|
// 删除酸轧打卷记录
|
|
app.delete('/pocket/acidTyping/:id', async (req, res) => {
|
|
const { id } = req.params
|
|
try {
|
|
if (acidPool) {
|
|
await acidPool.execute('DELETE FROM acid_typing WHERE id = ?', [id])
|
|
sendResponse(res, {}, '删除成功')
|
|
return
|
|
}
|
|
sendResponse(res, {}, '删除成功(模拟)')
|
|
} catch (error) {
|
|
console.error('删除酸轧打卷失败:', error.message)
|
|
sendResponse(res, {}, '删除成功(模拟)')
|
|
}
|
|
})
|
|
|
|
// ==================== L2数据接口 ====================
|
|
|
|
// 酸轧L2报表数据
|
|
app.get('/l2/report/acid', async (req, res) => {
|
|
const { startDate, endDate } = req.query
|
|
try {
|
|
if (acidPool) {
|
|
let query = 'SELECT * FROM l2_acid_report WHERE 1=1'
|
|
const params = []
|
|
|
|
if (startDate) {
|
|
query += ' AND report_date >= ?'
|
|
params.push(startDate)
|
|
}
|
|
if (endDate) {
|
|
query += ' AND report_date <= ?'
|
|
params.push(endDate)
|
|
}
|
|
|
|
const [rows] = await acidPool.execute(query, params)
|
|
sendResponse(res, rows)
|
|
return
|
|
}
|
|
sendResponse(res, mockData.acidRollingReport)
|
|
} catch (error) {
|
|
console.error('L2酸轧报表查询失败:', error.message)
|
|
sendResponse(res, mockData.acidRollingReport)
|
|
}
|
|
})
|
|
|
|
// 酸轧生产计划
|
|
app.get('/l2/plan/acid', async (req, res) => {
|
|
try {
|
|
if (acidPool) {
|
|
const [rows] = await acidPool.execute('SELECT * FROM l2_production_plan WHERE line_type = "acid" AND is_active = 1')
|
|
if (rows.length > 0) {
|
|
sendResponse(res, rows[0])
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, mockData.currentPlan)
|
|
} catch (error) {
|
|
console.error('L2生产计划查询失败:', error.message)
|
|
sendResponse(res, mockData.currentPlan)
|
|
}
|
|
})
|
|
|
|
// 酸轧停机记录
|
|
app.get('/l2/stop/acid', async (req, res) => {
|
|
const { date } = req.query
|
|
try {
|
|
if (acidPool) {
|
|
let query = 'SELECT * FROM l2_stop_record WHERE line_type = "acid"'
|
|
const params = []
|
|
|
|
if (date) {
|
|
query += ' AND DATE(stop_time) = ?'
|
|
params.push(date)
|
|
}
|
|
|
|
const [rows] = await acidPool.execute(query, params)
|
|
sendResponse(res, rows)
|
|
return
|
|
}
|
|
sendResponse(res, mockData.acidStopReport)
|
|
} catch (error) {
|
|
console.error('L2停机记录查询失败:', error.message)
|
|
sendResponse(res, mockData.acidStopReport)
|
|
}
|
|
})
|
|
|
|
// ==================== 大屏管理接口 ====================
|
|
|
|
app.get('/api/screens', async (req, res) => {
|
|
try {
|
|
if (masterPool) {
|
|
const [rows] = await masterPool.execute('SELECT * FROM dashboard_screens')
|
|
sendResponse(res, rows.map(r => ({
|
|
id: r.id,
|
|
name: r.name,
|
|
path: r.path,
|
|
status: r.status,
|
|
createTime: r.create_time
|
|
})))
|
|
return
|
|
}
|
|
sendResponse(res, mockData.screens)
|
|
} catch (error) {
|
|
console.error('大屏列表查询失败:', error.message)
|
|
sendResponse(res, mockData.screens)
|
|
}
|
|
})
|
|
|
|
// ==================== 系统菜单接口 ====================
|
|
|
|
const menuData = [
|
|
{
|
|
path: '/index',
|
|
name: 'Dashboard',
|
|
component: '/home/index.vue',
|
|
meta: { title: '工作台', icon: 'dashboard' },
|
|
children: []
|
|
},
|
|
{
|
|
path: '/dashboard',
|
|
name: 'DashboardGroup',
|
|
component: '/home/index.vue',
|
|
meta: { title: '数据大屏', icon: 'monitor' },
|
|
children: [
|
|
{ path: '/dashboard/demo', name: 'Demo', component: '/dashboard/demo/index.vue', meta: { title: '示例大屏', icon: 'example' }, children: [] },
|
|
{ path: '/dashboard/order', name: 'Order', component: '/dashboard/order/index.vue', meta: { title: '订单大屏', icon: 'order' }, children: [] },
|
|
{ path: '/dashboard/cost', name: 'Cost', component: '/dashboard/cost/index.vue', meta: { title: '成本大屏', icon: 'cost' }, children: [] },
|
|
{ path: '/dashboard/energy', name: 'Energy', component: '/dashboard/energy/index.vue', meta: { title: '能源大屏', icon: 'energy' }, children: [] }
|
|
]
|
|
},
|
|
{
|
|
path: '/screens',
|
|
name: 'ScreensGroup',
|
|
component: '/home/index.vue',
|
|
meta: { title: '大屏管理', icon: 'pie-chart' },
|
|
children: [
|
|
{ path: '/screens', name: 'ScreenList', component: '/screens/index.vue', meta: { title: '大屏列表', icon: 'list' }, children: [] },
|
|
{ path: '/screens/acid-rolling', name: 'AcidRolling', component: '/screens/acid-rolling/index.vue', meta: { title: '酸轧数据大屏', icon: 'chart' }, children: [] }
|
|
]
|
|
},
|
|
{
|
|
path: '/reports',
|
|
name: 'ReportsGroup',
|
|
component: '/home/index.vue',
|
|
meta: { title: '报表管理', icon: 'document' },
|
|
children: [
|
|
{ path: '/reports', name: 'ReportList', component: '/reports/index.vue', meta: { title: '报表列表', icon: 'list' }, children: [] },
|
|
{ path: '/reports/acid-rolling', name: 'AcidRollingReport', component: '/reports/acid-rolling/index.vue', meta: { title: '酸轧产出报表', icon: 'output' }, children: [] },
|
|
{ path: '/reports/acid-stop', name: 'AcidStopReport', component: '/reports/acid-stop/index.vue', meta: { title: '酸轧停机报表', icon: 'stop' }, children: [] }
|
|
]
|
|
},
|
|
{
|
|
path: '/system',
|
|
name: 'SystemGroup',
|
|
component: '/home/index.vue',
|
|
meta: { title: '系统管理', icon: 'setting' },
|
|
children: [
|
|
{ path: '/system/user', name: 'User', component: '/system/user/index.vue', meta: { title: '用户管理', icon: 'user' }, children: [] },
|
|
{ path: '/system/role', name: 'Role', component: '/system/role/index.vue', meta: { title: '角色管理', icon: 'role' }, children: [] },
|
|
{ path: '/system/menu', name: 'Menu', component: '/system/menu/index.vue', meta: { title: '菜单管理', icon: 'menu' }, children: [] },
|
|
{ path: '/system/config', name: 'Config', component: '/system/config/index.vue', meta: { title: '系统配置', icon: 'config' }, children: [] }
|
|
]
|
|
},
|
|
{
|
|
path: '/data-source',
|
|
name: 'DataSource',
|
|
component: '/data-source/index.vue',
|
|
meta: { title: '数据源配置', icon: 'data-board' },
|
|
children: []
|
|
}
|
|
]
|
|
|
|
app.get('/api/system/menu/list', async (req, res) => {
|
|
try {
|
|
if (masterPool) {
|
|
const [rows] = await masterPool.execute('SELECT * FROM sys_menu ORDER BY order_num ASC')
|
|
if (rows.length > 0) {
|
|
const menuTree = buildMenuTree(rows)
|
|
sendResponse(res, menuTree)
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, menuData)
|
|
} catch (error) {
|
|
console.error('菜单列表查询失败:', error.message)
|
|
sendResponse(res, menuData)
|
|
}
|
|
})
|
|
|
|
function buildMenuTree(menuList) {
|
|
const map = new Map()
|
|
const roots = []
|
|
|
|
menuList.forEach(item => {
|
|
map.set(item.id, {
|
|
id: item.id,
|
|
path: item.path || '/',
|
|
name: item.name || '',
|
|
component: item.component || '',
|
|
meta: {
|
|
title: item.title,
|
|
icon: item.icon,
|
|
breadcrumb: item.breadcrumb !== undefined ? item.breadcrumb : true
|
|
},
|
|
children: [],
|
|
parentId: item.parent_id || 0
|
|
})
|
|
})
|
|
|
|
map.forEach(item => {
|
|
if (item.parentId === 0) {
|
|
roots.push(item)
|
|
} else {
|
|
const parent = map.get(item.parentId)
|
|
if (parent) {
|
|
parent.children.push(item)
|
|
}
|
|
}
|
|
})
|
|
|
|
return roots
|
|
}
|
|
|
|
app.get('/api/system/menu/:id', async (req, res) => {
|
|
const { id } = req.params
|
|
try {
|
|
if (masterPool) {
|
|
const [rows] = await masterPool.execute('SELECT * FROM sys_menu WHERE id = ?', [id])
|
|
if (rows.length > 0) {
|
|
sendResponse(res, rows[0])
|
|
return
|
|
}
|
|
}
|
|
sendResponse(res, menuData.find(m => m.path === `/system/menu/${id}`) || menuData[0])
|
|
} catch (error) {
|
|
console.error('菜单详情查询失败:', error.message)
|
|
sendResponse(res, menuData[0])
|
|
}
|
|
})
|
|
|
|
app.post('/api/system/menu', async (req, res) => {
|
|
const data = req.body
|
|
try {
|
|
if (masterPool) {
|
|
await masterPool.execute(
|
|
'INSERT INTO sys_menu (parent_id, title, name, path, component, icon, order_num, breadcrumb, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
|
[data.parentId || 0, data.title, data.name, data.path, data.component, data.icon, data.orderNum || 0, data.breadcrumb !== undefined ? data.breadcrumb : true, data.visible !== undefined ? data.visible : true]
|
|
)
|
|
sendResponse(res, { id: Date.now() }, '创建成功')
|
|
return
|
|
}
|
|
sendResponse(res, { id: Date.now() }, '创建成功(模拟)')
|
|
} catch (error) {
|
|
console.error('创建菜单失败:', error.message)
|
|
sendResponse(res, { id: Date.now() }, '创建成功(模拟)')
|
|
}
|
|
})
|
|
|
|
app.put('/api/system/menu/:id', async (req, res) => {
|
|
const { id } = req.params
|
|
const data = req.body
|
|
try {
|
|
if (masterPool) {
|
|
await masterPool.execute(
|
|
'UPDATE sys_menu SET parent_id=?, title=?, name=?, path=?, component=?, icon=?, order_num=?, breadcrumb=?, visible=? WHERE id=?',
|
|
[data.parentId || 0, data.title, data.name, data.path, data.component, data.icon, data.orderNum || 0, data.breadcrumb !== undefined ? data.breadcrumb : true, data.visible !== undefined ? data.visible : true, id]
|
|
)
|
|
sendResponse(res, {}, '更新成功')
|
|
return
|
|
}
|
|
sendResponse(res, {}, '更新成功(模拟)')
|
|
} catch (error) {
|
|
console.error('更新菜单失败:', error.message)
|
|
sendResponse(res, {}, '更新成功(模拟)')
|
|
}
|
|
})
|
|
|
|
app.delete('/api/system/menu/:id', async (req, res) => {
|
|
const { id } = req.params
|
|
try {
|
|
if (masterPool) {
|
|
await masterPool.execute('DELETE FROM sys_menu WHERE id = ?', [id])
|
|
sendResponse(res, {}, '删除成功')
|
|
return
|
|
}
|
|
sendResponse(res, {}, '删除成功(模拟)')
|
|
} catch (error) {
|
|
console.error('删除菜单失败:', error.message)
|
|
sendResponse(res, {}, '删除成功(模拟)')
|
|
}
|
|
})
|
|
|
|
// ==================== 系统用户接口 ====================
|
|
|
|
app.get('/api/system/user/list', async (req, res) => {
|
|
const { page = 1, size = 50, username, name } = req.query
|
|
try {
|
|
if (masterPool) {
|
|
let query = 'SELECT * FROM sys_user WHERE 1=1'
|
|
const params = []
|
|
|
|
if (username) {
|
|
query += ' AND username LIKE ?'
|
|
params.push(`%${username}%`)
|
|
}
|
|
if (name) {
|
|
query += ' AND name LIKE ?'
|
|
params.push(`%${name}%`)
|
|
}
|
|
|
|
query += ' ORDER BY create_time DESC LIMIT ? OFFSET ?'
|
|
params.push(size, (page - 1) * size)
|
|
|
|
const [rows] = await masterPool.execute(query, params)
|
|
sendResponse(res, {
|
|
rows: rows.map(r => ({
|
|
id: r.id,
|
|
username: r.username,
|
|
name: r.name,
|
|
role: r.role,
|
|
status: r.status,
|
|
createTime: r.create_time
|
|
})),
|
|
total: 100
|
|
})
|
|
return
|
|
}
|
|
sendResponse(res, {
|
|
rows: mockData.users.slice(0, parseInt(size)),
|
|
total: 100
|
|
})
|
|
} catch (error) {
|
|
console.error('用户列表查询失败:', error.message)
|
|
sendResponse(res, {
|
|
rows: mockData.users.slice(0, parseInt(size)),
|
|
total: 100
|
|
})
|
|
}
|
|
})
|
|
|
|
// ==================== 启动服务 ====================
|
|
|
|
initDatabases().then(() => {
|
|
app.listen(port, () => {
|
|
console.log(`🚀 服务启动成功: http://localhost:${port}`)
|
|
console.log(`📊 主数据库: ${masterPool ? '✅ 已连接' : '⚠️ 使用模拟数据'}`)
|
|
console.log(`📊 酸轧数据库: ${acidPool ? '✅ 已连接' : '⚠️ 使用模拟数据'}`)
|
|
console.log(`🔗 OEE大屏接口: http://localhost:${port}/api/wms/acid-rolling/dashboard/oee`)
|
|
console.log(`🔗 OEE汇总接口: http://localhost:${port}/da/oee/summary`)
|
|
console.log(`🔗 酸轧打卷接口: http://localhost:${port}/pocket/acidTyping/page`)
|
|
})
|
|
})
|