初始化:静态菜单版 数据大屏管理系统,对接KLPL3数据库
This commit is contained in:
10
server/.idea/.gitignore
generated
vendored
Normal file
10
server/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 已忽略包含查询文件的默认文件夹
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
6
server/.idea/misc.xml
generated
Normal file
6
server/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
server/.idea/modules.xml
generated
Normal file
8
server/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/server.iml" filepath="$PROJECT_DIR$/.idea/server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
9
server/.idea/server.iml
generated
Normal file
9
server/.idea/server.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
845
server/app.js
Normal file
845
server/app.js
Normal file
@@ -0,0 +1,845 @@
|
||||
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`)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user