Files
screen/server/app.js

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`)
})
})