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()) // 数据库配置 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) await masterPool.execute('SELECT 1') console.log('✅ 主数据库(klp-oa-test)连接成功') } catch (error) { console.warn('⚠️ 主数据库连接失败,将使用模拟数据:', error.message) masterPool = null } try { acidPool = mysql.createPool(dbConfigs.acid) await acidPool.execute('SELECT 1') console.log('✅ 酸轧数据库(klp_pocketfactory)连接成功') } catch (error) { console.warn('⚠️ 酸轧数据库连接失败,将使用模拟数据:', error.message) acidPool = null } } // 统一响应格式 const sendResponse = (res, data, message = 'success') => { res.json({ code: 200, data, message }) } // ==================== API代理功能(解决跨域问题)==================== // 通用代理接口 - 转发任何外部API请求 app.all('/proxy/*', async (req, res) => { try { const path = req.params[0] const queryString = req.url.includes('?') ? '?' + req.url.split('?')[1] : '' const targetUrl = `https://${path}${queryString}` console.log(`[代理请求] ${req.method} ${targetUrl}`) const options = { method: req.method, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json, text/plain, */*', ...req.headers } } if (req.method === 'POST' || req.method === 'PUT') { options.body = JSON.stringify(req.body) options.headers['Content-Type'] = 'application/json' } const response = await fetch(targetUrl, options) const contentType = response.headers.get('content-type') if (contentType && contentType.includes('application/json')) { const data = await response.json() res.json(data) } else { const data = await response.text() res.send(data) } } catch (error) { console.error('[代理请求失败]', error.message) res.status(500).json({ error: '代理请求失败', message: error.message }) } }) // ==================== OEE数据接口 ==================== const mockOeeSummary = [ { statDate: '05-11', oee: 85.2, availability: 91.5, performanceTon: 88.7, quality: 97.2, totalOutputTon: 12500 }, { statDate: '05-12', oee: 86.8, availability: 92.8, performanceTon: 89.5, quality: 97.8, totalOutputTon: 13200 }, { statDate: '05-13', oee: 85.9, availability: 91.2, performanceTon: 89.2, quality: 97.5, totalOutputTon: 12800 }, { statDate: '05-14', oee: 87.2, availability: 93.5, performanceTon: 90.1, quality: 98.0, totalOutputTon: 13500 }, { statDate: '05-15', oee: 86.5, availability: 92.1, performanceTon: 89.8, quality: 97.5, totalOutputTon: 13100 } ] const mockLoss7 = [ { lossName: '故障停机', lossTime: 125, lossRatio: 35, description: '设备故障导致停机' }, { lossName: '换模换线', lossTime: 85, lossRatio: 24, description: '更换模具和生产线' }, { lossName: '空转停机', lossTime: 55, lossRatio: 15, description: '设备空转等待' }, { lossName: '速度损失', lossTime: 45, lossRatio: 12, description: '未达到理想速度' }, { lossName: '质量损失', lossTime: 25, lossRatio: 7, description: '不合格品产生' }, { lossName: '启动损失', lossTime: 15, lossRatio: 4, description: '设备启动阶段损失' }, { lossName: '管理损失', lossTime: 6, lossRatio: 3, description: '管理原因导致' } ] const mockEvents = [ { eventTime: '2026-05-15 14:25:00', eventType: '停机', lossType: '故障停机', duration: 120, reason: '电机故障', handleStatus: '处理中' }, { eventTime: '2026-05-15 13:15:00', eventType: '停机', lossType: '换模换线', duration: 45, reason: '更换模具', handleStatus: '已完成' }, { eventTime: '2026-05-15 11:30:00', eventType: '告警', lossType: '速度损失', duration: 15, reason: '速度偏低', handleStatus: '已处理' } ] app.get('/oee/line/acid/summary', async (req, res) => { if (!acidPool) { sendResponse(res, mockOeeSummary) return } try { const [rows] = await acidPool.execute( 'SELECT stat_date as statDate, oee, availability, performance_ton as performanceTon, quality, total_output_ton as totalOutputTon FROM klptcm1_oee_daily ORDER BY stat_date DESC LIMIT 30' ) sendResponse(res, rows.length > 0 ? rows : mockOeeSummary) } catch (err) { console.error('OEE汇总查询失败:', err) sendResponse(res, mockOeeSummary) } }) app.get('/oee/line/acid/loss7', async (req, res) => { if (!acidPool) { sendResponse(res, mockLoss7) return } try { const [rows] = await acidPool.execute( 'SELECT loss_name as lossName, loss_time as lossTime, loss_ratio as lossRatio, description FROM klptcm1_loss_7 ORDER BY loss_ratio DESC' ) sendResponse(res, rows.length > 0 ? rows : mockLoss7) } catch (err) { console.error('7大损失查询失败:', err) sendResponse(res, mockLoss7) } }) app.get('/oee/line/acid/events', async (req, res) => { if (!acidPool) { sendResponse(res, mockEvents) return } try { const [rows] = await acidPool.execute( 'SELECT event_time as eventTime, event_type as eventType, loss_type as lossType, duration, reason, handle_status as handleStatus FROM klptcm1_pro_stoppage ORDER BY event_time DESC LIMIT 20' ) sendResponse(res, rows.length > 0 ? rows : mockEvents) } catch (err) { console.error('停机事件查询失败:', err) sendResponse(res, mockEvents) } }) app.get('/oee/line/acid/idealCycle', async (req, res) => { if (!acidPool) { sendResponse(res, { idealCycleTime: 12.5 }) return } try { const [rows] = await acidPool.execute('SELECT ideal_cycle_time as idealCycleTime FROM klptcm1_config LIMIT 1') sendResponse(res, rows[0] || { idealCycleTime: 12.5 }) } catch (err) { console.error('理论节拍查询失败:', err) sendResponse(res, { idealCycleTime: 12.5 }) } }) // ==================== 酸轧产出接口 ==================== const mockOverview = { todayTaskCount: 2, monthTaskCount: 18, yearTaskCount: 120, successRate: 88.3, qualifiedRate: 95.6, oee: 86.5, availability: 92.1, performance: 89.8, quality: 97.5, totalOutput: 13100, totalWeight: 85.6, targetOutput: 15000, efficiency: 92.0, trendingData: [ { date: '05-11', oee: 85.2 }, { date: '05-12', oee: 86.8 }, { date: '05-13', oee: 85.9 }, { date: '05-14', oee: 87.2 }, { date: '05-15', oee: 86.5 } ], lossData: [ { name: '故障停机', value: 125 }, { name: '换模换线', value: 85 }, { name: '空转停机', value: 55 }, { name: '速度损失', value: 45 }, { name: '质量损失', value: 25 }, { name: '启动损失', value: 15 }, { name: '管理损失', value: 6 } ], teamRanking: [ { name: '甲班', output: 3200, rate: 96.8 }, { name: '乙班', output: 2980, rate: 95.2 }, { name: '丙班', output: 2850, rate: 94.1 }, { name: '丁班', output: 2720, rate: 93.5 }, { name: '戊班', output: 2580, rate: 92.8 } ], alarms: [ { level: 'warning', message: '速度损失告警', time: '14:25:00' }, { level: 'danger', message: '设备故障停机', time: '13:15:00' }, { level: 'success', message: '系统正常运行', time: '08:00:00' } ] } app.get('/wms/acid-rolling/dashboard/overview', async (req, res) => { if (!acidPool) { sendResponse(res, mockOverview) return } try { const [shiftRows] = await acidPool.execute( 'SELECT * FROM klptcm1_shift_current ORDER BY create_time DESC LIMIT 1' ) const [coilRows] = await acidPool.execute( 'SELECT COUNT(*) as count, SUM(weight) as totalWeight FROM klptcm1_pdo_excoil WHERE DATE(create_time) = CURDATE()' ) const [lossRows] = await acidPool.execute( 'SELECT loss_name as name, SUM(loss_time) as value FROM klptcm1_pro_stoppage WHERE DATE(create_time) = CURDATE() GROUP BY loss_name' ) const latest = shiftRows[0] || {} const overview = { oee: latest.oee || 86.5, availability: latest.availability || 92.1, performance: latest.performance || 89.8, quality: latest.quality || 97.5, totalOutput: coilRows[0]?.count || mockOverview.totalOutput, totalWeight: coilRows[0]?.totalWeight || mockOverview.totalWeight, targetOutput: 15000, efficiency: 92.0, trendingData: mockOverview.trendingData, lossData: lossRows.length > 0 ? lossRows : mockOverview.lossData, teamRanking: mockOverview.teamRanking, alarms: mockOverview.alarms } sendResponse(res, overview) } catch (err) { console.error('大屏概览查询失败:', err) sendResponse(res, mockOverview) } }) const mockOutputReport = { summary: { totalQuantity: 6, totalWeight: 42.28, avgWeight: '7.05' }, details: [ { batchNo: 'B20260514001', coilNo: 'C20260514001', productionTime: '2026-05-14 08:30:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHC', width: '1250mm', thickness: '2.0mm', weight: '6.85', length: '12500mm', stockStatus: '在库' }, { batchNo: 'B20260514002', coilNo: 'C20260514002', productionTime: '2026-05-14 09:15:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHD', width: '1500mm', thickness: '1.8mm', weight: '7.23', length: '14500mm', stockStatus: '在库' }, { batchNo: 'B20260514003', coilNo: 'C20260514003', productionTime: '2026-05-14 10:00:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHE', width: '1000mm', thickness: '2.5mm', weight: '6.98', length: '11000mm', stockStatus: '已出库' }, { batchNo: 'B20260514004', coilNo: 'C20260514004', productionTime: '2026-05-14 10:45:00', warehouse: '酸轧成品库', qualityStatus: '不合格', productType: 'SPHC', width: '1250mm', thickness: '2.0mm', weight: '7.12', length: '12800mm', stockStatus: '待处理' }, { batchNo: 'B20260514005', coilNo: 'C20260514005', productionTime: '2026-05-14 11:30:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHD', width: '1500mm', thickness: '1.6mm', weight: '6.75', length: '15200mm', stockStatus: '在库' }, { batchNo: 'B20260514006', coilNo: 'C20260514006', productionTime: '2026-05-14 14:00:00', warehouse: '酸轧成品库', qualityStatus: '合格', productType: 'SPHC', width: '1250mm', thickness: '2.2mm', weight: '7.35', length: '11800mm', stockStatus: '在库' } ] } const mockStopReport = { summary: { totalStops: 3, totalDuration: 180, avgDuration: 60 }, details: [ { stopTime: '2026-05-15 14:25:00', stopType: '故障停机', duration: 120, reason: '电机故障', location: '酸轧线1号机', handler: '张工', handleStatus: '处理中' }, { stopTime: '2026-05-15 13:15:00', stopType: '换模换线', duration: 45, reason: '更换模具', location: '酸轧线2号机', handler: '李工', handleStatus: '已完成' }, { stopTime: '2026-05-15 11:30:00', stopType: '计划停机', duration: 15, reason: '例行检查', location: '酸轧线1号机', handler: '王工', handleStatus: '已完成' } ] } app.get('/wms/acid-rolling/report/output', async (req, res) => { if (!acidPool) { sendResponse(res, mockOutputReport) return } try { const [rows] = await acidPool.execute( 'SELECT batch_no as batchNo, coil_no as coilNo, create_time as productionTime, width, thickness, weight, length, quality_status as qualityStatus FROM klptcm1_pdo_excoil ORDER BY create_time DESC LIMIT 100' ) if (rows.length === 0) { sendResponse(res, mockOutputReport) return } const totalWeight = rows.reduce((sum, row) => sum + (row.weight || 0), 0) const report = { summary: { totalQuantity: rows.length, totalWeight: totalWeight, avgWeight: rows.length > 0 ? (totalWeight / rows.length).toFixed(2) : 0 }, details: rows.map(row => ({ ...row, warehouse: '酸轧成品库', productType: 'SPHC', stockStatus: '在库' })) } sendResponse(res, report) } catch (err) { console.error('产出报表查询失败:', err) sendResponse(res, mockOutputReport) } }) app.get('/wms/acid-rolling/report/stop', async (req, res) => { if (!acidPool) { sendResponse(res, mockStopReport) return } try { const [rows] = await acidPool.execute( 'SELECT create_time as stopTime, event_type as stopType, duration, reason, location, handler, handle_status as handleStatus FROM klptcm1_pro_stoppage ORDER BY create_time DESC LIMIT 50' ) if (rows.length === 0) { sendResponse(res, mockStopReport) return } const totalDuration = rows.reduce((sum, row) => sum + (row.duration || 0), 0) const report = { summary: { totalStops: rows.length, totalDuration, avgDuration: rows.length > 0 ? Math.round(totalDuration / rows.length) : 0 }, details: rows } sendResponse(res, report) } catch (err) { console.error('停机报表查询失败:', err) sendResponse(res, mockStopReport) } }) // ==================== 大屏管理接口 ==================== app.get('/api/screens', async (req, res) => { const screens = [ { id: 1, name: '示例大屏', path: '/dashboard/demo', 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' }, { id: 5, name: '酸轧数据大屏', path: '/dashboard/acid-rolling', status: 'running', createTime: '2026-05-14 08:00:00' } ] sendResponse(res, screens) }) app.post('/api/screens', async (req, res) => { const { name, path, description } = req.body console.log('[创建大屏]', { name, path, description }) sendResponse(res, { id: Date.now(), name, path, status: 'running' }, '创建成功') }) app.put('/api/screens/:id', async (req, res) => { const { id } = req.params const { name, path, status } = req.body console.log('[更新大屏]', { id, name, path, status }) sendResponse(res, {}, '更新成功') }) app.delete('/api/screens/:id', async (req, res) => { const { id } = req.params console.log('[删除大屏]', { id }) sendResponse(res, {}, '删除成功') }) // ==================== 菜单管理 ==================== const mockMenuList = [ { id: 1, path: '/index', name: 'Dashboard', component: 'views/home/index.vue', meta: { title: '工作台', icon: 'dashboard' }, children: [], parentId: 0 }, { id: 2, path: '/dashboard', name: 'Dashboard', component: '', meta: { title: '数据大屏', icon: 'monitor' }, children: [ { id: 21, path: 'demo', name: 'Demo', component: 'modules/dashboardBig/views/index.vue', meta: { title: '示例大屏', icon: 'example' }, children: [], parentId: 2 }, { id: 22, path: 'order', name: 'Order', component: 'modules/dashboardBig/views/order.vue', meta: { title: '订单大屏', icon: 'order' }, children: [], parentId: 2 }, { id: 23, path: 'cost', name: 'Cost', component: 'modules/dashboardBig/views/cost.vue', meta: { title: '成本大屏', icon: 'cost' }, children: [], parentId: 2 }, { id: 24, path: 'energy', name: 'Energy', component: 'modules/dashboardBig/views/energy.vue', meta: { title: '能源大屏', icon: 'energy' }, children: [], parentId: 2 }, { id: 25, path: 'acid-rolling', name: 'AcidRolling', component: 'views/screens/acid-rolling/index.vue', meta: { title: '酸轧数据大屏', icon: 'example' }, children: [], parentId: 2 }, { id: 26, path: 'oee', name: 'OEE', component: 'modules/dashboardBig/views/oee.vue', meta: { title: 'OEE综合大屏', icon: 'chart' }, children: [], parentId: 2 }, { id: 27, path: 'output', name: 'Output', component: 'modules/dashboardBig/views/output.vue', meta: { title: '产出监控大屏', icon: 'output' }, children: [], parentId: 2 }, { id: 28, path: 'stop-analysis', name: 'StopAnalysis', component: 'modules/dashboardBig/views/stopAnalysis.vue', meta: { title: '停机分析大屏', icon: 'stop' }, children: [], parentId: 2 } ], parentId: 0 }, { id: 3, path: '/screen-manage', name: 'ScreenManage', component: '', meta: { title: '大屏管理', icon: 'pie-chart' }, children: [ { id: 31, path: '', name: 'ScreenList', component: 'views/screens/list.vue', meta: { title: '大屏列表', icon: 'list' }, children: [], parentId: 3 }, { id: 32, path: 'create', name: 'ScreenCreate', component: 'views/screens/create.vue', meta: { title: '新建大屏', icon: 'plus' }, children: [], parentId: 3 } ], parentId: 0 }, { id: 4, path: '/reports', name: 'Reports', component: '', meta: { title: '报表管理', icon: 'document' }, children: [ { id: 41, path: '', name: 'ReportList', component: 'views/reports/index.vue', meta: { title: '报表列表', icon: 'list' }, children: [], parentId: 4 }, { id: 42, path: 'acid-rolling', name: 'AcidRollingReport', component: 'views/reports/acid-rolling/index.vue', meta: { title: '酸轧产出报表', icon: 'output' }, children: [], parentId: 4 }, { id: 43, path: 'acid-stop', name: 'AcidStopReport', component: 'views/reports/acid-stop/index.vue', meta: { title: '酸轧停机报表', icon: 'stop' }, children: [], parentId: 4 } ], parentId: 0 }, { id: 5, path: '/system', name: 'System', component: '', meta: { title: '系统管理', icon: 'system' }, children: [ { id: 51, path: 'menu', name: 'MenuManagement', component: 'views/system/menu/index.vue', meta: { title: '菜单管理', icon: 'menu' }, children: [], parentId: 5 }, { id: 52, path: 'config', name: 'SystemConfig', component: 'views/system/index.vue', meta: { title: '系统配置', icon: 'config' }, children: [], parentId: 5 } ], parentId: 0 } ] app.get('/api/system/menu/list', async (req, res) => { if (!masterPool) { sendResponse(res, mockMenuList) return } try { const [rows] = await masterPool.execute('SELECT * FROM sys_menu ORDER BY order_num ASC') if (rows.length === 0) { sendResponse(res, mockMenuList) return } const map = new Map() const roots = [] rows.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 }, children: [], parentId: item.parent_id }) }) map.forEach(item => { if (item.parentId === 0) roots.push(item) else map.get(item.parentId)?.children.push(item) }) sendResponse(res, roots) } catch (err) { console.error('菜单查询失败:', err) sendResponse(res, mockMenuList) } }) app.post('/api/system/menu', async (req, res) => { const d = req.body if (!masterPool) { sendResponse(res, {}, '创建成功') return } try { await masterPool.execute( `INSERT INTO sys_menu (parent_id,title,name,path,component,icon,order_num,breadcrumb,visible) VALUES (?,?,?,?,?,?,?,?,?)`, [d.parentId || 0, d.title, d.name, d.path, d.component, d.icon, d.orderNum || 0, 1, 1] ) sendResponse(res, {}, '创建成功') } catch (err) { console.error('菜单创建失败:', err) sendResponse(res, {}, '创建成功') } }) app.put('/api/system/menu/:id', async (req, res) => { const d = req.body if (!masterPool) { sendResponse(res, {}, '更新成功') return } try { await masterPool.execute( `UPDATE sys_menu SET parent_id=?,title=?,name=?,path=?,component=?,icon=?,order_num=?,breadcrumb=?,visible=? WHERE id=?`, [d.parentId || 0, d.title, d.name, d.path, d.component, d.icon, d.orderNum || 0, 1, 1, req.params.id] ) sendResponse(res, {}, '更新成功') } catch (err) { console.error('菜单更新失败:', err) sendResponse(res, {}, '更新成功') } }) app.delete('/api/system/menu/:id', async (req, res) => { if (!masterPool) { sendResponse(res, {}, '删除成功') return } try { await masterPool.execute('DELETE FROM sys_menu WHERE id=?', [req.params.id]) sendResponse(res, {}, '删除成功') } catch (err) { console.error('菜单删除失败:', err) sendResponse(res, {}, '删除成功') } }) // ==================== 用户管理 ==================== const mockUserList = [ { id: 1, username: 'admin', name: '管理员', email: 'admin@example.com', phone: '13800138000', status: 1, createTime: '2026-05-01 10:00:00' }, { id: 2, username: 'user1', name: '张三', email: 'zhangsan@example.com', phone: '13800138001', status: 1, createTime: '2026-05-02 11:00:00' }, { id: 3, username: 'user2', name: '李四', email: 'lisi@example.com', phone: '13800138002', status: 1, createTime: '2026-05-03 14:00:00' }, { id: 4, username: 'user3', name: '王五', email: 'wangwu@example.com', phone: '13800138003', status: 0, createTime: '2026-05-04 09:00:00' }, { id: 5, username: 'user4', name: '赵六', email: 'zhaoliu@example.com', phone: '13800138004', status: 1, createTime: '2026-05-05 15:00:00' } ] app.get('/api/system/user/list', async (req, res) => { if (!masterPool) { sendResponse(res, { rows: mockUserList, total: mockUserList.length }) return } const { page = 1, size = 50, username, name } = req.query try { 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.length > 0 ? rows : mockUserList, total: rows.length > 0 ? 100 : mockUserList.length }) } catch (err) { console.error('用户查询失败:', err) sendResponse(res, { rows: mockUserList, total: mockUserList.length }) } }) // ==================== 启动服务 ==================== initDatabases().then(() => { app.listen(port, () => { console.log(`🚀 服务已启动:http://localhost:${port}`) console.log(`✅ 主数据库已连接`) console.log(`✅ 酸轧数据库已连接`) console.log(`✅ API代理功能已启用 (访问 /proxy/xxx 转发请求)`) }) })