From 5663be1f6b95f6fb6bd957ed04b66551db16df26 Mon Sep 17 00:00:00 2001 From: zuqijia <2924963185@qq.com> Date: Sun, 17 May 2026 17:24:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E3=80=81=E5=A4=A7=E5=B1=8F=E9=A1=B5=E9=9D=A2=E3=80=81=E9=85=B8?= =?UTF-8?q?=E8=BD=A7=E6=8A=A5=E8=A1=A8=E6=8E=A5=E5=8F=A3=EF=BC=8C=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E5=AF=BC=E8=88=AA=E6=A0=8F/=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/.idea/vcs.xml | 6 + server/app.js | 1173 ++++++--------- server/package-lock.json | 1004 +++++++++++++ server/package.json | 16 + src/api/acidOee.js | 109 ++ src/api/screen.js | 56 + src/assets/styles/index.scss | 644 +++++++++ src/assets/styles/mixins.scss | 333 +++++ src/assets/styles/variables.scss | 247 ++++ src/layout/components/AppMain.vue | 39 + src/layout/components/Navbar/index.vue | 15 +- src/layout/components/Sidebar/index.vue | 288 ++-- src/layout/index.vue | 122 +- src/main.js | 14 +- src/modules/dashboardBig/views/cost.vue | 541 ++++--- src/modules/dashboardBig/views/energy.vue | 742 ++++++---- src/modules/dashboardBig/views/index.vue | 541 +++---- src/modules/dashboardBig/views/oee.vue | 570 ++++++++ src/modules/dashboardBig/views/order.vue | 410 ++++-- src/modules/dashboardBig/views/output.vue | 552 +++++++ .../dashboardBig/views/stopAnalysis.vue | 550 +++++++ src/router/index.js | 52 +- src/store/modules/permission.js | 59 +- src/style.css | 281 ---- src/views/dashboard/cost/index.vue | 224 +-- src/views/dashboard/demo/index.vue | 18 +- src/views/dashboard/energy/index.vue | 202 ++- src/views/dashboard/order/index.vue | 76 +- src/views/reports/acid-rolling/index.vue | 118 +- src/views/screens/acid-rolling/index.vue | 1287 ++++++----------- src/views/screens/create.vue | 170 +++ src/views/screens/edit.vue | 178 +++ src/views/screens/list.vue | 198 +++ src/views/system/menu/index.vue | 15 +- 34 files changed, 7547 insertions(+), 3303 deletions(-) create mode 100644 server/.idea/vcs.xml create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 src/api/acidOee.js create mode 100644 src/assets/styles/index.scss create mode 100644 src/assets/styles/mixins.scss create mode 100644 src/assets/styles/variables.scss create mode 100644 src/layout/components/AppMain.vue create mode 100644 src/modules/dashboardBig/views/oee.vue create mode 100644 src/modules/dashboardBig/views/output.vue create mode 100644 src/modules/dashboardBig/views/stopAnalysis.vue delete mode 100644 src/style.css create mode 100644 src/views/screens/create.vue create mode 100644 src/views/screens/edit.vue create mode 100644 src/views/screens/list.vue diff --git a/server/.idea/vcs.xml b/server/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/server/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/server/app.js b/server/app.js index a6d30e2..2b5277f 100644 --- a/server/app.js +++ b/server/app.js @@ -8,7 +8,7 @@ const port = 3000 app.use(cors()) app.use(express.json()) -// 多数据库连接配置 - KLPL3数据库 +// 数据库配置 const dbConfigs = { master: { host: '140.143.206.120', @@ -35,799 +35,526 @@ const dbConfigs = { let masterPool = null let acidPool = null -// 尝试连接数据库 +// 初始化数据库连接 const initDatabases = async () => { try { masterPool = mysql.createPool(dbConfigs.master) - const [masterRows] = await masterPool.execute('SELECT 1') + await masterPool.execute('SELECT 1') console.log('✅ 主数据库(klp-oa-test)连接成功') } catch (error) { - console.error('❌ 主数据库连接失败:', error.message) - console.log('⚠️ 将使用模拟数据运行') + console.warn('⚠️ 主数据库连接失败,将使用模拟数据:', error.message) + masterPool = null } - + try { acidPool = mysql.createPool(dbConfigs.acid) - const [acidRows] = await acidPool.execute('SELECT 1') + await acidPool.execute('SELECT 1') console.log('✅ 酸轧数据库(klp_pocketfactory)连接成功') } catch (error) { - console.error('❌ 酸轧数据库连接失败:', error.message) - console.log('⚠️ 将使用模拟数据运行') + console.warn('⚠️ 酸轧数据库连接失败,将使用模拟数据:', error.message) + acidPool = null } } -// 模拟数据(备用) -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 } +// 统一响应格式 +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 } ], - 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' } + lossData: [ + { name: '故障停机', value: 125 }, + { name: '换模换线', value: 85 }, + { name: '空转停机', value: 55 }, + { name: '速度损失', value: 45 }, + { name: '质量损失', value: 25 }, + { name: '启动损失', value: 15 }, + { name: '管理损失', value: 6 } ], - 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' } + 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 } ], - 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' } + alarms: [ + { level: 'warning', message: '速度损失告警', time: '14:25:00' }, + { level: 'danger', message: '设备故障停机', time: '13:15:00' }, + { level: 'success', message: '系统正常运行', time: '08:00:00' } ] } -// 封装统一响应 -const sendResponse = (res, data, message = 'success') => { - res.json({ - code: 200, - message, - data - }) +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: '在库' } + ] } -// ==================== OEE报表接口 ==================== +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: '已完成' } + ] +} -// 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) +app.get('/wms/acid-rolling/report/output', async (req, res) => { + if (!acidPool) { + sendResponse(res, mockOutputReport) + return } -}) - -// 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) - } -}) + 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' + ) -// 停机事件明细 -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 - }) + if (rows.length === 0) { + sendResponse(res, mockOutputReport) 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 - } + 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, { lineType, idealCycle: 120, unit: 'm/min' }) - } catch (error) { - console.error('理论节拍查询失败:', error.message) - sendResponse(res, { lineType, idealCycle: 120, unit: 'm/min' }) + + sendResponse(res, report) + } catch (err) { + console.error('产出报表查询失败:', err) + sendResponse(res, mockOutputReport) } }) -// ==================== 大屏数据接口 ==================== - -// OEE大屏数据 -app.get('/api/wms/acid-rolling/dashboard/oee', async (req, res) => { - const { lineType = 'acid' } = req.query +app.get('/wms/acid-rolling/report/stop', async (req, res) => { + if (!acidPool) { + sendResponse(res, mockStopReport) + return + } 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 - }) + 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 } - sendResponse(res, mockData.oeeData) - } catch (error) { - console.error('OEE大屏数据查询失败:', error.message) - sendResponse(res, mockData.oeeData) - } -}) -// ==================== 酸轧打卷接口 ==================== + const totalDuration = rows.reduce((sum, row) => sum + (row.duration || 0), 0) -// 分页查询酸轧打卷记录 -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 + const report = { + summary: { + totalStops: rows.length, + totalDuration, + avgDuration: rows.length > 0 ? Math.round(totalDuration / rows.length) : 0 + }, + details: rows } - 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) + sendResponse(res, report) + } catch (err) { + console.error('停机报表查询失败:', err) + sendResponse(res, mockStopReport) } }) // ==================== 大屏管理接口 ==================== 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 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' }, '创建成功') +}) -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.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) => { - 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) + if (!masterPool) { + sendResponse(res, mockMenuList) + return } -}) - -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 - } + const [rows] = await masterPool.execute('SELECT * FROM sys_menu ORDER BY order_num ASC') + + if (rows.length === 0) { + sendResponse(res, mockMenuList) + return } - sendResponse(res, menuData.find(m => m.path === `/system/menu/${id}`) || menuData[0]) - } catch (error) { - console.error('菜单详情查询失败:', error.message) - sendResponse(res, menuData[0]) + + 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 data = req.body + const d = req.body + if (!masterPool) { + sendResponse(res, {}, '创建成功') + return + } 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() }, '创建成功(模拟)') + 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 { id } = req.params - const data = req.body + const d = req.body + if (!masterPool) { + sendResponse(res, {}, '更新成功') + return + } 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, {}, '更新成功(模拟)') + 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) => { - const { id } = req.params + if (!masterPool) { + sendResponse(res, {}, '删除成功') + return + } 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, {}, '删除成功(模拟)') + 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 { - 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 - }) + 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 }) } }) @@ -835,11 +562,9 @@ app.get('/api/system/user/list', async (req, res) => { 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`) + console.log(`🚀 服务已启动:http://localhost:${port}`) + console.log(`✅ 主数据库已连接`) + console.log(`✅ 酸轧数据库已连接`) + console.log(`✅ API代理功能已启用 (访问 /proxy/xxx 转发请求)`) }) }) diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..346d491 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1004 @@ +{ + "name": "screen-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "screen-backend", + "version": "1.0.0", + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "mysql2": "^3.6.0" + } + }, + "node_modules/@types/node": { + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.8.0.tgz", + "integrity": "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.3.tgz", + "integrity": "sha512-uWWxvZSRvRhtBdh2CdcuK83YcOfPdmEeEYB069bAmPnV93QApDGVPuvCQOLjlh7tYHEWdgQPrn6kosDxHBVLkA==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT", + "peer": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..015b33a --- /dev/null +++ b/server/package.json @@ -0,0 +1,16 @@ +{ + "name": "screen-backend", + "version": "1.0.0", + "type": "module", + "description": "大屏管理后端服务", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "mysql2": "^3.6.0", + "dotenv": "^16.3.1" + } +} \ No newline at end of file diff --git a/src/api/acidOee.js b/src/api/acidOee.js new file mode 100644 index 0000000..d476c43 --- /dev/null +++ b/src/api/acidOee.js @@ -0,0 +1,109 @@ +import request from '@/utils/request' + +export function getOeeDailySummary(params) { + return request({ + url: '/oee/line/acid/summary', + method: 'get', + params: { + startDate: params?.startDate || '', + endDate: params?.endDate || '', + lineType: params?.lineType || 'acid', + ...params + } + }) +} + +export function getOeeLossSummary(params) { + return request({ + url: '/oee/line/acid/loss7', + method: 'get', + params: { + topN: params?.topN || 50, + startDate: params?.startDate || '', + endDate: params?.endDate || '', + lineType: params?.lineType || 'acid', + ...params + } + }) +} + +export function getOeeStoppageEvents(params) { + return request({ + url: '/oee/line/acid/events', + method: 'get', + params: { + pageNum: params?.pageNum || 1, + pageSize: params?.pageSize || 20, + startTime: params?.startTime || '', + endTime: params?.endTime || '', + stopType: params?.stopType || '', + keyword: params?.keyword || '', + ...params + } + }) +} + +export function getOeeIdealCycle(params) { + return request({ + url: '/oee/line/acid/idealCycle', + method: 'get', + params: { + startDate: params?.startDate || '', + endDate: params?.endDate || '', + ...params + } + }) +} + +export function getProductionOutput(params) { + return request({ + url: '/wms/acid-rolling/report/output', + method: 'get', + params: { + startDate: params?.startDate || '', + endDate: params?.endDate || '', + ...params + } + }) +} + +export function getProductionStop(params) { + return request({ + url: '/wms/acid-rolling/report/stop', + method: 'get', + params: { + startDate: params?.startDate || '', + endDate: params?.endDate || '', + ...params + } + }) +} + +export function getDashboardOverview() { + return request({ + url: '/wms/acid-rolling/dashboard/overview', + method: 'get' + }) +} + +export function getProductRanking(params) { + return request({ + url: '/wms/acid-rolling/dashboard/ranking', + method: 'get', + params + }) +} + +export function getShiftCurrent() { + return request({ + url: '/pocket/shift/current', + method: 'get' + }) +} + +export function getPlantStateCurrent() { + return request({ + url: '/pocket/plant/state/current', + method: 'get' + }) +} diff --git a/src/api/screen.js b/src/api/screen.js index 82a0b6c..36bce99 100644 --- a/src/api/screen.js +++ b/src/api/screen.js @@ -107,3 +107,59 @@ export function getStopRecords(params) { params }) } + +// 文档中提到的新接口 +export function getOeeLineSummary(params) { + return request({ + url: '/oee/line/acid/summary', + method: 'get', + params + }) +} + +export function getOeeLineLoss7(params) { + return request({ + url: '/oee/line/acid/loss7', + method: 'get', + params + }) +} + +export function getOeeLineEvents(params) { + return request({ + url: '/oee/line/acid/events', + method: 'get', + params + }) +} + +export function getOeeLineIdealCycle(params) { + return request({ + url: '/oee/line/acid/idealCycle', + method: 'get', + params + }) +} + +export function getAcidOutputReport(params) { + return request({ + url: '/wms/acid-rolling/report/output', + method: 'get', + params + }) +} + +export function getAcidStopReport(params) { + return request({ + url: '/wms/acid-rolling/report/stop', + method: 'get', + params + }) +} + +export function getAcidDashboardOverview() { + return request({ + url: '/wms/acid-rolling/dashboard/overview', + method: 'get' + }) +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss new file mode 100644 index 0000000..b6256e1 --- /dev/null +++ b/src/assets/styles/index.scss @@ -0,0 +1,644 @@ +/* ======================================== + 企业级后台系统全局样式 + 基于 SCSS 变量和混合器 + ======================================== */ + +@import './variables.scss'; +@import './mixins.scss'; + +// ==================== 1. 基础重置 ==================== + +html { + box-sizing: border-box; +} + +*, *:before, *:after { + box-sizing: inherit; + margin: 0; + padding: 0; +} + +// ==================== 2. 全局盒子模型 ==================== + +html, body { + height: 100%; + width: 100%; + overflow: hidden; +} + +// ==================== 3. 字体体系 ==================== + +body { + font-family: $base-font-family; + font-size: $base-font-size-base; + line-height: $base-font-line-height-base; + color: $base-text-color; + background-color: $base-background-color; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +html { + font-smoothing: antialiased; + text-rendering: optimizeLegibility; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +// ==================== 4. 根容器 ==================== + +#app { + height: 100%; + min-height: 100vh; + width: 100%; + position: relative; +} + +// ==================== 5. 链接和按钮基础样式 ==================== + +a, a:focus, a:hover { + cursor: pointer; + color: inherit; + text-decoration: none; + outline: none; +} + +button, input, select, textarea { + font-family: inherit; + font-size: inherit; + outline: none; +} + +*:focus { + outline: none; +} + +*:focus-visible { + outline: 2px solid $--color-primary; + outline-offset: 2px; +} + +// ==================== 6. 清除浮动 ==================== + +.clearfix { + @include clearfix; +} + +.fl { + float: left; +} + +.fr { + float: right; +} + +// ==================== 7. 文字对齐 ==================== + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +// ==================== 8. 文本截断 ==================== + +.text-ellipsis { + @include ellipsis; +} + +.text-ellipsis-2 { + @include ellipsis-2; +} + +// ==================== 9. 显示隐藏 ==================== + +.hidden { + display: none !important; +} + +.visible { + display: block !important; +} + +// ==================== 10. 全局滚动条 ==================== + +@include scroll-bar; + +// Firefox 滚动条 +* { + scrollbar-width: thin; + scrollbar-color: $base-scrollbar-thumb $base-scrollbar-background; +} + +// ==================== 11. 布局容器 ==================== + +.app-container { + padding: $base-padding-base; + height: 100%; +} + +.components-container { + margin: 30px 50px; + position: relative; +} + +// ==================== 12. 卡片样式 ==================== + +.card { + background: #ffffff; + border-radius: $base-border-radius-base; + @include box-shadow-light; + transition: box-shadow $base-transition-base; + + &:hover { + @include box-shadow-base; + } +} + +// ==================== 13. 按钮容器 ==================== + +.btn-container { + display: inline-block; + + .el-button { + margin-right: $base-margin-sm; + + &:last-child { + margin-right: 0; + } + } +} + +// ==================== 14. 表格容器 ==================== + +.table-container { + background: #ffffff; + border-radius: $base-border-radius-base; + @include box-shadow-light; + + .el-table { + font-size: $base-font-size-base - 1; + } + + .el-table th { + background: $base-background-light-color; + font-weight: 600; + color: $base-text-color-secondary; + } + + .el-table td { + color: $base-text-color; + } +} + +// ==================== 15. 表单容器 ==================== + +.form-container { + background: #ffffff; + border-radius: $base-border-radius-base; + padding: $base-padding-base; + @include box-shadow-light; + + .el-form-item { + margin-bottom: 22px; + } +} + +// ==================== 16. 筛选容器 ==================== + +.filter-container { + padding-bottom: $base-padding-sm; + + .filter-item { + display: inline-block; + vertical-align: middle; + margin-bottom: $base-margin-sm; + } +} + +// ==================== 17. 分页容器 ==================== + +.pagination-container { + margin-top: $base-padding-large; + text-align: right; + padding: $base-padding-base 0; +} + +// ==================== 18. 状态标签 ==================== + +.status-tag { + display: inline-block; + padding: 2px 10px; + border-radius: $base-border-radius-base; + font-size: $base-font-size-small; + line-height: 1.5; +} + +.status-tag.status-success { + @include tag-success; +} + +.status-tag.status-warning { + @include tag-warning; +} + +.status-tag.status-danger { + @include tag-danger; +} + +.status-tag.status-info { + @include tag-info; +} + +// ==================== 19. 图表容器 ==================== + +.chart-container { + width: 100%; + height: 100%; +} + +// ==================== 20. 过渡动画 ==================== + +.fade-enter-active, +.fade-leave-active { + transition: opacity $base-transition-base; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.slide-left-enter-active, +.slide-left-leave-active { + transition: all $base-transition-slow; +} + +.slide-left-enter-from { + opacity: 0; + transform: translateX(-20px); +} + +.slide-left-leave-to { + opacity: 0; + transform: translateX(20px); +} + +// ==================== 21. 加载状态 ==================== + +.loading-container { + @include flex-center; + min-height: 400px; +} + +// ==================== 22. 空状态 ==================== + +.empty-container { + @include flex-center-column; + padding: 60px $base-padding-base; + color: $base-text-color-secondary; + + .empty-icon { + font-size: 64px; + margin-bottom: $base-margin-base; + color: $base-border-color; + } + + .empty-text { + font-size: $base-font-size-base; + color: $base-text-color-secondary; + } +} + +// ==================== 23. 禁用状态 ==================== + +.disabled { + cursor: not-allowed; + opacity: 0.6; + pointer-events: none; +} + +// 激活状态 +.active { + cursor: pointer; + pointer-events: auto; +} + +// ==================== 24. 间距工具类 ==================== + +.mt5 { margin-top: 5px; } +.mt8 { margin-top: 8px; } +.mt10 { margin-top: 10px; } +.mt20 { margin-top: 20px; } +.mt30 { margin-top: 30px; } + +.mb5 { margin-bottom: 5px; } +.mb8 { margin-bottom: 8px; } +.mb10 { margin-bottom: 10px; } +.mb20 { margin-bottom: 20px; } + +.ml5 { margin-left: 5px; } +.ml8 { margin-left: 8px; } +.ml10 { margin-left: 10px; } +.ml20 { margin-left: 20px; } + +.mr5 { margin-right: 5px; } +.mr8 { margin-right: 8px; } +.mr10 { margin-right: 10px; } +.mr20 { margin-right: 20px; } + +.pl5 { padding-left: 5px; } +.pl10 { padding-left: 10px; } + +.pr5 { padding-right: 5px; } +.pr10 { padding-right: 10px; } + +.pt5 { padding-top: 5px; } +.pt10 { padding-top: 10px; } + +.pb5 { padding-bottom: 5px; } +.pb10 { padding-bottom: 10px; } + +.p-10 { padding: 10px; } +.p-20 { padding: 20px; } + +// ==================== 25. 辅助类 - 边框 ==================== + +.border { border: 1px solid $base-border-color; } +.border-top { border-top: 1px solid $base-border-color; } +.border-right { border-right: 1px solid $base-border-color; } +.border-bottom { border-bottom: 1px solid $base-border-color; } +.border-left { border-left: 1px solid $base-border-color; } + +// ==================== 26. 辅助类 - 圆角 ==================== + +.radius { border-radius: $base-border-radius-base; } +.radius-sm { border-radius: $base-border-radius-small; } +.radius-md { border-radius: $base-border-radius-base; } +.radius-lg { border-radius: $base-border-radius-large; } +.radius-full { border-radius: $base-border-radius-circle; } + +// ==================== 27. 辅助类 - 层级 ==================== + +.z-index-10 { z-index: $base-z-index-10; } +.z-index-100 { z-index: $base-z-index-100; } +.z-index-1000 { z-index: $base-z-index-1000; } +.z-index-9999 { z-index: $base-z-index-fixed; } + +// ==================== 28. 布局工具类 ==================== + +.fl { float: left; } +.fr { float: right; } +.block { display: block; } +.inline-block { display: inline-block; } +.inline { display: inline; } +.hide { display: none; } +.pointer { cursor: pointer; } + +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-justify { text-align: justify; } + +.no-padding { padding: 0; } +.no-margin { margin: 0; } + +.clearfix { @include clearfix; } + +// ==================== 29. 文本状态色工具类 ==================== + +.text-primary { color: #1a1a1a; } +.text-regular { color: #333333; } +.text-secondary { color: #555555; } +.text-placeholder { color: #777777; } +.text-success { color: #67c23a; } +.text-warning { color: #e6a23c; } +.text-danger { color: #f56c6c; } +.text-muted { color: #909399; } +.text-info { color: #409eff; } + +// ==================== 30. 背景色工具类 ==================== + +.bg-primary { background-color: #409eff; } +.bg-success { background-color: #67c23a; } +.bg-warning { background-color: #e6a23c; } +.bg-danger { background-color: #f56c6c; } +.bg-info { background-color: #909399; } +.bg-white { background-color: #ffffff; } +.bg-gray { background-color: #f5f7fa; } + +// ==================== 31. Element-UI 组件深度定制 ==================== + +// 表格样式 +:deep(.el-table) { + --el-table-header-text-color: #606266; + --el-table-row-hover-bg-color: #f5f7fa; + + th { + background: #fafafa; + font-weight: 600; + color: #606266; + padding: 10px 8px; + } + + td { + padding: 10px 8px; + color: #303133; + } + + .el-button { + padding: 4px 10px; + font-size: 12px; + } +} + +// 卡片样式 +:deep(.el-card) { + border-radius: $base-border-radius-base; + + .el-card__header { + padding: 12px 15px; + min-height: 40px; + border-bottom: 1px solid $base-border-color-light; + background: #fafafa; + + .el-card__title { + font-size: $base-font-size-base; + font-weight: 600; + color: #303133; + } + } + + .el-card__body { + padding: 20px; + } +} + +// 弹窗样式 +:deep(.el-dialog) { + margin-top: 6vh !important; + + .el-dialog__body { + max-height: 70vh; + overflow-y: auto; + } +} + +// 按钮样式 +:deep(.el-button) { + padding: 8px 18px; + font-size: $base-font-size-base; + border-radius: $base-border-radius-base; + transition: all 0.2s ease; + + &.el-button--primary { + background: linear-gradient(135deg, #409eff, #267fff); + border: none; + + &:hover { + background: linear-gradient(135deg, #66b1ff, #409eff); + } + } + + &.el-button--success { + background: linear-gradient(135deg, #67c23a, #52c41a); + border: none; + } + + &.el-button--danger { + background: linear-gradient(135deg, #f56c6c, #ee4d4d); + border: none; + } + + &.el-button--warning { + background: linear-gradient(135deg, #e6a23c, #d4912f); + border: none; + } +} + +// 下拉菜单 +:deep(.el-dropdown-menu) { + border: 1px solid $base-border-color-light; + border-radius: $base-border-radius-base; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + + .el-dropdown-menu__item { + padding: 8px 20px; + transition: background 0.2s ease; + + &:hover { + background: rgba(64, 158, 255, 0.08); + } + } +} + +// 树形组件 +:deep(.el-tree) { + border: 1px solid $base-border-color-light; + border-radius: $base-border-radius-base; + padding: $base-padding-sm; + + .el-tree-node__content { + padding: 6px 8px; + + &:hover { + background: rgba(0, 0, 0, 0.04); + } + } +} + +// 分页组件 +:deep(.el-pagination) { + padding: 16px 0; + + .el-pagination__sizes, + .el-pagination__jump { + @media screen and (max-width: $base-media-sm) { + display: none; + } + } +} + +// 标签组件 +:deep(.el-tag) { + padding: 2px 10px; + font-size: $base-font-size-small; + border-radius: $base-border-radius-small; +} + +// 输入框 +:deep(.el-input__inner) { + border-radius: $base-border-radius-base; + height: 36px; + line-height: 36px; +} + +// 选择器 +:deep(.el-select__wrapper) { + border-radius: $base-border-radius-base; + + .el-select__input { + height: 36px; + line-height: 36px; + } +} + +// ==================== 32. 工业风按钮 ==================== + +.pan-btn { + display: inline-block; + padding: 8px 20px; + font-size: $base-font-size-base; + font-weight: 500; + text-align: center; + border-radius: $base-border-radius-base; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; + background: linear-gradient(135deg, $blue, $light-blue); + color: #ffffff; + + &:hover { + border-color: $blue; + background: #ffffff; + color: $blue; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba($blue, 0.3); + } + + &:active { + transform: translateY(0); + box-shadow: none; + } +} + +// ==================== 33. 响应式布局 ==================== + +@media screen and (max-width: $base-media-md) { + .app-container { + padding: 12px; + } + + .components-container { + margin: 16px 20px; + } +} + +@media screen and (max-width: $base-media-sm) { + .app-container { + padding: 8px; + } + + .components-container { + margin: 10px; + } +} + + diff --git a/src/assets/styles/mixins.scss b/src/assets/styles/mixins.scss new file mode 100644 index 0000000..968f500 --- /dev/null +++ b/src/assets/styles/mixins.scss @@ -0,0 +1,333 @@ +/* ======================================== + 企业级后台系统 SCSS Mixins + 全局复用混合器 + ======================================== */ + +@import './variables.scss'; + +// ==================== 1. 布局混合器 ==================== + +// 滚动条 +@mixin scroll-bar { + &::-webkit-scrollbar { + width: $base-scrollbar-width; + height: $base-scrollbar-height; + } + + &::-webkit-scrollbar-track { + background: $base-scrollbar-background; + border-radius: $base-scrollbar-width / 2; + } + + &::-webkit-scrollbar-thumb { + background: $base-scrollbar-thumb; + border-radius: $base-scrollbar-width / 2; + transition: background 0.2s; + + &:hover { + background: $base-scrollbar-thumb-hover; + } + } +} + +// flex 居中 +@mixin flex-center { + display: flex; + align-items: center; + justify-content: center; +} + +// flex 左右分布 +@mixin flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} + +// flex 垂直居中 +@mixin flex-center-column { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +// ==================== 2. 文本混合器 ==================== + +// 单行文本省略 +@mixin ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// 多行文本省略 +@mixin ellipsis-2($line: 2) { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: $line; + overflow: hidden; + text-overflow: ellipsis; +} + +// ==================== 3. 清除浮动 ==================== + +@mixin clearfix { + *zoom: 1; + + &::before, + &::after { + content: ""; + display: table; + } + + &::after { + clear: both; + } +} + +// ==================== 4. 动画混合器 ==================== + +// 过渡动画 +@mixin transition($property: all, $duration: $base-transition-base, $function: ease) { + transition: $property $duration $function; +} + +// 侧边栏过渡 +@mixin sidebar-transition { + transition: width $base-transition-sidebar; +} + +// ==================== 5. 响应式混合器 ==================== + +// 移动端 +@mixin media-xs { + @media screen and (max-width: #{$base-media-xs}) { + @content; + } +} + +// 平板 +@mixin media-sm { + @media screen and (max-width: #{$base-media-sm}) { + @content; + } +} + +// 小屏 +@mixin media-md { + @media screen and (max-width: #{$base-media-md}) { + @content; + } +} + +// 大屏 +@mixin media-lg { + @media screen and (max-width: #{$base-media-lg}) { + @content; + } +} + +// 超大屏 +@mixin media-xl { + @media screen and (min-width: #{$base-media-xl}) { + @content; + } +} + +// ==================== 6. 阴影混合器 ==================== + +// 基础阴影 +@mixin box-shadow-base { + box-shadow: $base-box-shadow-base; +} + +// 浅色阴影 +@mixin box-shadow-light { + box-shadow: $base-box-shadow-light; +} + +// 深色阴影 +@mixin box-shadow-dark { + box-shadow: $base-box-shadow-dark; +} + +// ==================== 7. 按钮混合器 ==================== + +// 基础按钮样式 +@mixin button-base { + display: inline-block; + line-height: 1; + white-space: nowrap; + cursor: pointer; + background: #ffffff; + border: 1px solid $base-border-color; + color: $base-text-color; + text-align: center; + box-sizing: border-box; + outline: none; + margin: 0; + padding: 10px 15px; + font-size: $base-font-size-base; + border-radius: $base-border-radius-base; + transition: all $base-transition-base; + + &:hover, + &:focus { + color: $--color-primary; + border-color: #c6e2ff; + background-color: #ecf5ff; + } +} + +// 主按钮 +@mixin button-primary { + @include button-base; + color: #ffffff; + background-color: $--color-primary; + border-color: $--color-primary; + + &:hover, + &:focus { + background: #66b1ff; + border-color: #66b1ff; + color: #ffffff; + } +} + +// 成功按钮 +@mixin button-success { + @include button-base; + color: #ffffff; + background-color: $--color-success; + border-color: $--color-success; + + &:hover, + &:focus { + background: #85ce61; + border-color: #85ce61; + color: #ffffff; + } +} + +// 危险按钮 +@mixin button-danger { + @include button-base; + color: #ffffff; + background-color: $--color-danger; + border-color: $--color-danger; + + &:hover, + &:focus { + background: #f78989; + border-color: #f78989; + color: #ffffff; + } +} + +// ==================== 8. 输入框混合器 ==================== + +// 基础输入框 +@mixin input-base { + -webkit-appearance: none; + background-color: #ffffff; + background-image: none; + border-radius: $base-border-radius-base; + border: 1px solid $base-border-color; + box-sizing: border-box; + color: $base-text-color; + display: inline-block; + font-size: inherit; + height: 32px; + line-height: 32px; + outline: none; + padding: 0 12px; + transition: border-color $base-transition-base; + width: 100%; + + &::placeholder { + color: $base-text-color-placeholder; + } + + &:hover { + border-color: $base-text-color-secondary; + } + + &:focus { + border-color: $--color-primary; + outline: none; + } + + &.is-disabled { + background-color: #f5f7fa; + border-color: $base-border-color-lighter; + color: $base-text-color-disabled; + cursor: not-allowed; + + &::placeholder { + color: $base-text-color-disabled; + } + } +} + +// ==================== 9. 卡片混合器 ==================== + +// 基础卡片 +@mixin card-base { + background: #ffffff; + border-radius: $base-border-radius-base; + padding: $base-padding-base; + box-shadow: $base-box-shadow-light; + transition: box-shadow $base-transition-base; + + &:hover { + box-shadow: $base-box-shadow-base; + } +} + +// ==================== 10. 标签页混合器 ==================== + +// 基础标签 +@mixin tag-base { + display: inline-block; + padding: 0 8px; + height: 24px; + line-height: 22px; + font-size: $base-font-size-small; + border-radius: $base-border-radius-small; + border: 1px solid; + box-sizing: border-box; + white-space: nowrap; +} + +// 成功标签 +@mixin tag-success { + @include tag-base; + background-color: rgba($--color-success, 0.1); + border-color: rgba($--color-success, 0.2); + color: $--color-success; +} + +// 警告标签 +@mixin tag-warning { + @include tag-base; + background-color: rgba($--color-warning, 0.1); + border-color: rgba($--color-warning, 0.2); + color: $--color-warning; +} + +// 危险标签 +@mixin tag-danger { + @include tag-base; + background-color: rgba($--color-danger, 0.1); + border-color: rgba($--color-danger, 0.2); + color: $--color-danger; +} + +// 信息标签 +@mixin tag-info { + @include tag-base; + background-color: rgba($--color-info, 0.1); + border-color: rgba($--color-info, 0.2); + color: $--color-info; +} diff --git a/src/assets/styles/variables.scss b/src/assets/styles/variables.scss new file mode 100644 index 0000000..ff873fb --- /dev/null +++ b/src/assets/styles/variables.scss @@ -0,0 +1,247 @@ +/* ======================================== + 企业级后台系统 SCSS 变量规范 + 全局样式变量统一管理 + ======================================== */ + +// ==================== 1. 基础色彩变量(业务通用色)==================== + +// 主色系 +$blue: #324157 !default; // 主深蓝,用于按钮、激活状态、重点标识 +$light-blue: #3A71A8 !default; // 浅蓝,用于次要按钮、标签 +$red: #C03639 !default; // 红色,用于异常、删除、危险操作 +$pink: #E65D6E !default; // 粉色,用于次要警示 +$green: #30B08F !default; // 绿色,用于成功、正常状态 +$tiffany: #4AB7BD !default; // 蒂芙尼蓝,用于次要业务标识 +$yellow: #FEC171 !default; // 黄色,用于警告、待处理状态 +$panGreen: #30B08F !default; // 工业专用绿色,用于生产、合格状态 + +// Element Plus 主题色覆盖 +$--color-primary: #409eff !default; +$--color-success: #67c23a !default; +$--color-warning: #e6a23c !default; +$--color-danger: #f56c6c !default; +$--color-info: #909399 !default; + +// 文字色系 +$base-text-color: #303133 !default; // 主要文字 +$base-text-color-secondary: #606266 !default; // 次要文字 +$base-text-color-placeholder: #c0c4cc !default; // 占位符文字 +$base-text-color-disabled: #c0c4cc !default; // 禁用文字 + +// 边框色系 +$base-border-color: #dcdfe6 !default; // 基础边框 +$base-border-color-light: #e4e7ed !default; // 浅边框 +$base-border-color-lighter: #ebeef5 !default; // 更浅边框 +$base-border-color-extra-light: #f2f6fc !default; // 极浅边框 +$base-border-color-dark: #d4d7de !default; // 深边框 +$base-border-color-darker: #cdd5e0 !default; // 更深边框 + +// 背景色系 +$base-background-color: #f5f7fa !default; // 页面背景 +$base-background-light-color: #fafafa !default; // 浅背景 +$base-background-dark-color: #ffffff !default; // 深背景(白色) +$base-background-color-special: #f0f2f5 !default; // 特殊背景 + +// ==================== 2. 侧边栏菜单主题变量 ==================== + +// 默认暗色菜单(工业后台经典风格) +$base-menu-color: #bfcbd9 !default; // 未激活菜单文字色 +$base-menu-color-active: #f4f4f5 !default; // 激活菜单文字色 +$base-menu-background: #304156 !default; // 侧边栏背景色 +$base-logo-title-color: #ffffff !default; // Logo文字色 +$base-sub-menu-background: #1f2d3d !default; // 子菜单背景色 +$base-sub-menu-hover: #001528 !default; // 子菜单悬浮背景色 +$base-menu-hover-background: rgba(255, 255, 255, 0.08) !default; // 菜单悬浮背景 + +// 亮色菜单(可选切换) +$base-menu-light-color: rgba(0, 0, 0, 0.70) !default; // 亮色菜单文字 +$base-menu-light-background: #ffffff !default; // 亮色侧边栏背景 +$base-menu-light-border-right: #e6e6e6 !default; // 亮色菜单右边框 +$base-logo-light-title-color: #001529 !default; // 亮色Logo文字 + +// ==================== 3. 尺寸变量 ==================== + +// 侧边栏尺寸 +$base-sidebar-width: 200px !default; // 侧边栏默认宽度 +$base-sidebar-collapsed-width: 54px !default; // 侧边栏收起宽度 + +// 导航栏尺寸 +$base-navbar-height: 50px !default; // 导航栏高度 +$base-tagsview-height: 34px !default; // 标签页高度 + +// 侧边栏 Logo 高度 +$base-logo-height: 50px !default; // Logo 高度(与导航栏一致) + +// 菜单项尺寸 +$base-menu-item-height: 38px !default; // 菜单项高度 +$base-menu-item-line-height: 38px !default; // 菜单项行高 + +// ==================== 4. 字体变量 ==================== + +// 字体家族 +$base-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', + 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !default; + +// 字体大小 +$base-font-size-extra-small: 10px !default; +$base-font-size-small: 12px !default; +$base-font-size-base: 14px !default; +$base-font-size-medium: 16px !default; +$base-font-size-large: 18px !default; +$base-font-size-extra-large: 20px !default; + +// 字体行高 +$base-font-line-height-base: 1.5 !default; +$base-font-line-height-large: 1.75 !default; + +// ==================== 5. 间距变量 ==================== + +$base-padding-xs: 4px !default; +$base-padding-sm: 8px !default; +$base-padding-base: 16px !default; +$base-padding-large: 24px !default; +$base-padding-xl: 32px !default; + +$base-margin-xs: 4px !default; +$base-margin-sm: 8px !default; +$base-margin-base: 16px !default; +$base-margin-large: 24px !default; +$base-margin-xl: 32px !default; + +// ==================== 6. 圆角变量 ==================== + +$base-border-radius-small: 2px !default; +$base-border-radius-base: 4px !default; +$base-border-radius-large: 8px !default; +$base-border-radius-circle: 50% !default; + +// ==================== 7. 阴影变量 ==================== + +$base-box-shadow-base: 0 2px 12px 0 rgba(0, 0, 0, 0.1) !default; +$base-box-shadow-light: 0 2px 8px 0 rgba(0, 0, 0, 0.08) !default; +$base-box-shadow-dark: 0 4px 16px 0 rgba(0, 0, 0, 0.15) !default; +$base-box-shadow-light-down: 0 2px 8px 0 rgba(0, 0, 0, 0.06) !default; + +// ==================== 8. 动画变量 ==================== + +$base-transition-fast: 0.2s ease !default; +$base-transition-base: 0.3s ease !default; +$base-transition-slow: 0.5s ease !default; +$base-transition-sidebar: width 0.28s ease !default; + +// ==================== 9. z-index 层级变量 ==================== + +$base-z-index-10: 10 !default; +$base-z-index-100: 100 !default; +$base-z-index-1000: 1000 !default; +$base-z-index-2000: 2000 !default; +$base-z-index-3000: 3000 !default; +$base-z-index-fixed: 9999 !default; + +// ==================== 10. 滚动条变量 ==================== + +$base-scrollbar-width: 8px !default; +$base-scrollbar-height: 8px !default; +$base-scrollbar-background: #f1f1f1 !default; +$base-scrollbar-thumb: #c1c1c1 !default; +$base-scrollbar-thumb-hover: #a8a8a8 !default; + +// ==================== 11. 响应式断点 ==================== + +$base-media-xs: 480px !default; +$base-media-sm: 768px !default; +$base-media-md: 992px !default; +$base-media-lg: 1200px !default; +$base-media-xl: 1920px !default; + +// ==================== 12. 功能性 Mixins ==================== + +// 文本省略号 +@mixin utils-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// 多行文本省略 +@mixin utils-ellipsis-2 { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + text-overflow: ellipsis; +} + +// 清除浮动 +@mixin utils-clearfix { + *zoom: 1; + + &::before, + &::after { + content: ""; + display: table; + } + + &::after { + clear: both; + } +} + +// 滚动条样式 +@mixin utils-scrollbar { + &::-webkit-scrollbar { + width: $base-scrollbar-width; + height: $base-scrollbar-height; + } + + &::-webkit-scrollbar-track { + background: $base-scrollbar-background; + border-radius: $base-scrollbar-width / 2; + } + + &::-webkit-scrollbar-thumb { + background: $base-scrollbar-thumb; + border-radius: $base-scrollbar-width / 2; + transition: background 0.2s; + + &:hover { + background: $base-scrollbar-thumb-hover; + } + } +} + +// ==================== 13. 导出 JS 可用变量 ==================== + +:export { + // 颜色导出 + blue: $blue; + lightBlue: $light-blue; + red: $red; + pink: $pink; + green: $green; + tiffany: $tiffany; + yellow: $yellow; + panGreen: $panGreen; + + // 尺寸导出 + sidebarWidth: $base-sidebar-width; + sidebarCollapsedWidth: $base-sidebar-collapsed-width; + navbarHeight: $base-navbar-height; + tagsviewHeight: $base-tagsview-height; + logoHeight: $base-logo-height; + menuItemHeight: $base-menu-item-height; + + // 颜色导出 + menuColor: $base-menu-color; + menuColorActive: $base-menu-color-active; + menuBackground: $base-menu-background; + logoTitleColor: $base-logo-title-color; + subMenuBackground: $base-sub-menu-background; + subMenuHover: $base-sub-menu-hover; + + // 亮色主题 + menuLightColor: $base-menu-light-color; + menuLightBackground: $base-menu-light-background; + logoLightTitleColor: $base-logo-light-title-color; +} diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue new file mode 100644 index 0000000..22af418 --- /dev/null +++ b/src/layout/components/AppMain.vue @@ -0,0 +1,39 @@ + + + + + \ No newline at end of file diff --git a/src/layout/components/Navbar/index.vue b/src/layout/components/Navbar/index.vue index f5be122..fbbbd98 100644 --- a/src/layout/components/Navbar/index.vue +++ b/src/layout/components/Navbar/index.vue @@ -38,20 +38,13 @@ const toggleSideBar = () => { .navbar { height: 50px; background: linear-gradient(90deg, #20b6f9, #2178f1); - position: fixed; - top: 0; - right: 0; - left: 200px; - z-index: 100; - transition: left 0.28s ease; + width: 100%; + flex-shrink: 0; display: flex; justify-content: space-between; align-items: center; padding: 0 20px; - - .app-wrapper.sidebar-close & { - left: 54px; - } + box-sizing: border-box; } .navbar-left { @@ -113,4 +106,4 @@ const toggleSideBar = () => { color: #ffffff; } } - + \ No newline at end of file diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index 96e0e3d..393630a 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -10,6 +10,7 @@ store.state.app.sidebar) const activeMenu = computed(() => route.path) +const iconMap = { + 'dashboard': Monitor, + 'monitor': Monitor, + 'example': PieChart, + 'order': Document, + 'cost': PieChart, + 'energy': Monitor, + 'pie-chart': PieChart, + 'list': List, + 'plus': Plus, + 'document': Document, + 'output': PieChart, + 'stop': Bell, + 'system': Setting, + 'user': Refresh, + 'role': Refresh, + 'menu': List, + 'config': Setting, + 'database': Monitor +} + +const getIcon = (iconName) => { + return iconMap[iconName] || Monitor +} + const menuItems = [ { path: '/index', - meta: { title: '工作台', icon: 'home' } + meta: { title: '工作台', icon: 'dashboard' }, + children: [] }, { path: '/dashboard', meta: { title: '数据大屏', icon: 'monitor' }, children: [ - { path: '/dashboard/demo', meta: { title: '示例大屏', icon: 'chart' } }, - { path: '/dashboard/order', meta: { title: '订单大屏', icon: 'chart' } }, - { path: '/dashboard/cost', meta: { title: '成本大屏', icon: 'chart' } }, - { path: '/dashboard/energy', meta: { title: '能源大屏', icon: 'monitor' } } + { path: '/dashboard/demo', meta: { title: '示例大屏', icon: 'example' }, children: [] }, + { path: '/dashboard/order', meta: { title: '订单大屏', icon: 'order' }, children: [] }, + { path: '/dashboard/cost', meta: { title: '成本大屏', icon: 'cost' }, children: [] }, + { path: '/dashboard/energy', meta: { title: '能源大屏', icon: 'energy' }, children: [] }, + { path: '/dashboard/acid-rolling', meta: { title: '酸轧数据大屏', icon: 'example' }, children: [] } ] }, { - path: '/screens', - meta: { title: '大屏管理', icon: 'layout' }, + path: '/screen-manage', + meta: { title: '大屏管理', icon: 'pie-chart' }, children: [ - { path: '/screens', meta: { title: '大屏列表', icon: 'list' } }, - { path: '/screens/acid-rolling', meta: { title: '酸轧数据大屏', icon: 'chart' } } + { path: '/screen-manage', meta: { title: '大屏列表', icon: 'list' }, children: [] }, + { path: '/screen-manage/create', meta: { title: '新建大屏', icon: 'plus' }, children: [] } ] }, { path: '/reports', meta: { title: '报表管理', icon: 'document' }, children: [ - { path: '/reports', meta: { title: '报表列表', icon: 'list' } }, - { path: '/reports/acid-rolling', meta: { title: '酸轧产出报表', icon: 'chart' } }, - { path: '/reports/acid-stop', meta: { title: '酸轧停机报表', icon: 'settings' } } + { path: '/reports', meta: { title: '报表列表', icon: 'list' }, children: [] }, + { path: '/reports/acid-rolling', meta: { title: '酸轧产出报表', icon: 'output' }, children: [] }, + { path: '/reports/acid-stop', meta: { title: '酸轧停机报表', icon: 'stop' }, children: [] } ] }, { path: '/system', - meta: { title: '系统管理', icon: 'settings' }, + meta: { title: '系统管理', icon: 'system' }, children: [ - { path: '/system/user', meta: { title: '用户管理', icon: 'user' } }, - { path: '/system/role', meta: { title: '角色管理', icon: 'user' } }, - { path: '/system/menu', meta: { title: '菜单管理', icon: 'user' } }, - { path: '/system/config', meta: { title: '系统配置', icon: 'settings' } } + { path: '/system/menu', meta: { title: '菜单管理', icon: 'menu' }, children: [] }, + { path: '/system/config', meta: { title: '系统配置', icon: 'config' }, children: [] } ] }, { path: '/data-source', - meta: { title: '数据源配置', icon: 'database' } + meta: { title: '数据源配置', icon: 'database' }, + children: [] } ] - -const iconMap = { - 'home': Refresh, - 'monitor': FullScreen, - 'chart': Refresh, - 'document': Search, - 'settings': Refresh, - 'database': FullScreen, - 'user': User, - 'lock': User, - 'menu': Refresh, - 'layout': FullScreen, - 'list': Search -} - -const getIcon = (iconName) => { - if (!iconName) return Refresh - return iconMap[iconName] || Refresh -} + +:deep(.el-menu--popup) { + border: 1px solid #e8e8e8; + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + + .el-menu-item { + padding: 0 20px; + border-left: none; + + &:hover { + background: rgba(64, 158, 255, 0.08); + } + + &.is-active { + border-left: none; + background: rgba(64, 158, 255, 0.12); + } + } +} + \ No newline at end of file diff --git a/src/layout/index.vue b/src/layout/index.vue index 58de759..f1d3ee6 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -1,121 +1,43 @@ + \ No newline at end of file diff --git a/src/main.js b/src/main.js index aad2499..592d86b 100644 --- a/src/main.js +++ b/src/main.js @@ -4,9 +4,21 @@ import router from './router' import store from './store' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' -import './style.css' +import './assets/styles/index.scss' const app = createApp(App) + +// 全局错误处理 - 捕获第三方插件错误 +app.config.errorHandler = (err, instance, info) => { + // 忽略第三方插件的特定错误 + if (err.message && err.message.includes('offset')) { + console.warn('第三方插件错误已忽略:', err.message) + return + } + // 其他错误正常抛出 + console.error('Vue Error:', err, info) +} + app.use(router) app.use(store) app.use(ElementPlus) diff --git a/src/modules/dashboardBig/views/cost.vue b/src/modules/dashboardBig/views/cost.vue index 7c4e9a6..eb21d1c 100644 --- a/src/modules/dashboardBig/views/cost.vue +++ b/src/modules/dashboardBig/views/cost.vue @@ -1,79 +1,81 @@ + +@media screen and (max-width: 1200px) { + .chart-area { + grid-template-columns: repeat(2, 1fr); + } + + .card-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media screen and (max-width: 768px) { + .chart-area { + grid-template-columns: 1fr; + } + + .card-grid { + grid-template-columns: 1fr; + } + + .bottom-area { + grid-template-columns: 1fr; + } +} + \ No newline at end of file diff --git a/src/modules/dashboardBig/views/energy.vue b/src/modules/dashboardBig/views/energy.vue index 548e29c..f3481ad 100644 --- a/src/modules/dashboardBig/views/energy.vue +++ b/src/modules/dashboardBig/views/energy.vue @@ -1,189 +1,216 @@ + +@media screen and (max-width: 1200px) { + .chart-area { + grid-template-columns: repeat(2, 1fr); + } + + .card-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media screen and (max-width: 768px) { + .chart-area { + grid-template-columns: 1fr; + } + + .card-grid { + grid-template-columns: 1fr; + } + + .bottom-area { + grid-template-columns: 1fr; + } +} + \ No newline at end of file diff --git a/src/modules/dashboardBig/views/index.vue b/src/modules/dashboardBig/views/index.vue index 9592709..f6e952c 100644 --- a/src/modules/dashboardBig/views/index.vue +++ b/src/modules/dashboardBig/views/index.vue @@ -1,95 +1,63 @@ + \ No newline at end of file diff --git a/src/modules/dashboardBig/views/oee.vue b/src/modules/dashboardBig/views/oee.vue new file mode 100644 index 0000000..1f51699 --- /dev/null +++ b/src/modules/dashboardBig/views/oee.vue @@ -0,0 +1,570 @@ + + + + + \ No newline at end of file diff --git a/src/modules/dashboardBig/views/order.vue b/src/modules/dashboardBig/views/order.vue index f38d0c2..7b8ed4a 100644 --- a/src/modules/dashboardBig/views/order.vue +++ b/src/modules/dashboardBig/views/order.vue @@ -1,68 +1,59 @@ + +@media screen and (max-width: 1200px) { + .card-grid { + grid-template-columns: repeat(3, 1fr); + } + + .chart-area { + grid-template-columns: 1fr; + } +} + +@media screen and (max-width: 768px) { + .card-grid { + grid-template-columns: 1fr; + } +} + \ No newline at end of file diff --git a/src/modules/dashboardBig/views/output.vue b/src/modules/dashboardBig/views/output.vue new file mode 100644 index 0000000..e323fcb --- /dev/null +++ b/src/modules/dashboardBig/views/output.vue @@ -0,0 +1,552 @@ + + + + + \ No newline at end of file diff --git a/src/modules/dashboardBig/views/stopAnalysis.vue b/src/modules/dashboardBig/views/stopAnalysis.vue new file mode 100644 index 0000000..0fdd7be --- /dev/null +++ b/src/modules/dashboardBig/views/stopAnalysis.vue @@ -0,0 +1,550 @@ + + + + + \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index ce6fd02..8381e01 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -14,57 +14,65 @@ export const constantRoutes = [ } ] }, - { - path: '/demo', - redirect: '/dashboard/demo' - }, { path: '/dashboard', component: () => import('@/layout/index.vue'), - meta: { title: '数据大屏', icon: 'dashboard' }, + meta: { title: '数据大屏', icon: 'monitor' }, children: [ { path: 'demo', - name: 'DashboardDemo', + name: 'Demo', component: () => import('@/modules/dashboardBig/views/index.vue'), meta: { title: '示例大屏', icon: 'example' } }, { path: 'order', - name: 'OrderDashboard', + name: 'Order', component: () => import('@/modules/dashboardBig/views/order.vue'), meta: { title: '订单大屏', icon: 'order' } }, { path: 'cost', - name: 'CostDashboard', + name: 'Cost', component: () => import('@/modules/dashboardBig/views/cost.vue'), meta: { title: '成本大屏', icon: 'cost' } }, { path: 'energy', - name: 'EnergyDashboard', + name: 'Energy', component: () => import('@/modules/dashboardBig/views/energy.vue'), meta: { title: '能源大屏', icon: 'energy' } + }, + { + path: 'acid-rolling', + name: 'AcidRolling', + component: () => import('@/views/screens/acid-rolling/index.vue'), + meta: { title: '酸轧数据大屏', icon: 'example' } } ] }, { - path: '/screens', + path: '/screen-manage', component: () => import('@/layout/index.vue'), - meta: { title: '大屏管理', icon: 'monitor' }, + meta: { title: '大屏管理', icon: 'pie-chart' }, children: [ { path: '', name: 'ScreenList', - component: () => import('@/views/screens/index.vue'), + component: () => import('@/views/screens/list.vue'), meta: { title: '大屏列表', icon: 'list' } }, { - path: 'acid-rolling', - name: 'AcidRollingScreen', - component: () => import('@/views/screens/acid-rolling/index.vue'), - meta: { title: '酸轧数据大屏', icon: 'chart' } + path: 'create', + name: 'ScreenCreate', + component: () => import('@/views/screens/create.vue'), + meta: { title: '新建大屏', icon: 'plus' } + }, + { + path: 'edit/:id', + name: 'ScreenEdit', + component: () => import('@/views/screens/edit.vue'), + meta: { title: '编辑大屏', icon: 'edit', hidden: true } } ] }, @@ -98,18 +106,6 @@ export const constantRoutes = [ component: () => import('@/layout/index.vue'), meta: { title: '系统管理', icon: 'system' }, children: [ - { - path: 'user', - name: 'UserManagement', - component: () => import('@/views/system/user/index.vue'), - meta: { title: '用户管理', icon: 'user' } - }, - { - path: 'role', - name: 'RoleManagement', - component: () => import('@/views/system/role/index.vue'), - meta: { title: '角色管理', icon: 'role' } - }, { path: 'menu', name: 'MenuManagement', diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js index 6ddf566..8c40370 100644 --- a/src/store/modules/permission.js +++ b/src/store/modules/permission.js @@ -14,27 +14,33 @@ const mutations = { } const actions = { - generateRoutes({ commit }) { + generateRoutes({ commit, state }) { return new Promise(resolve => { + // 避免重复加载 + if (state.addRoutes.length > 0) { + resolve(state.addRoutes) + return + } getMenuList().then(response => { const menuData = response.data || [] const accessedRoutes = filterAsyncRoutes(menuData) commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }).catch(() => { - commit('SET_ROUTES', constantRoutes) - resolve(constantRoutes) + commit('SET_ROUTES', []) + resolve([]) }) }) } } +// 修复:Layout正常加载,空组件赋值首页 function filterAsyncRoutes(routes) { return routes.filter(route => { - if (route.component && route.component !== '') { - route.component = loadComponent(route.component) - } else { + if (!route.component || route.component === '') { route.component = () => import('@/views/home/index.vue') + } else if (route.component !== 'Layout') { + route.component = loadComponent(route.component) } if (route.children && route.children.length > 0) { route.children = filterAsyncRoutes(route.children) @@ -44,23 +50,30 @@ function filterAsyncRoutes(routes) { } function loadComponent(componentPath) { - const path = componentPath.replace(/^\//, '') + // 去除开头/和.vue后缀 + const path = componentPath.replace(/^\//, '').replace(/\.vue$/, '') const componentMap = { - 'home/index.vue': () => import('@/views/home/index.vue'), - 'dashboard/demo/index.vue': () => import('@/views/dashboard/demo/index.vue'), - 'dashboard/order/index.vue': () => import('@/views/dashboard/order/index.vue'), - 'dashboard/cost/index.vue': () => import('@/views/dashboard/cost/index.vue'), - 'dashboard/energy/index.vue': () => import('@/views/dashboard/energy/index.vue'), - 'screens/index.vue': () => import('@/views/screens/index.vue'), - 'screens/acid-rolling/index.vue': () => import('@/views/screens/acid-rolling/index.vue'), - 'reports/index.vue': () => import('@/views/reports/index.vue'), - 'reports/acid-rolling/index.vue': () => import('@/views/reports/acid-rolling/index.vue'), - 'reports/acid-stop/index.vue': () => import('@/views/reports/acid-stop/index.vue'), - 'system/user/index.vue': () => import('@/views/system/user/index.vue'), - 'system/role/index.vue': () => import('@/views/system/role/index.vue'), - 'system/menu/index.vue': () => import('@/views/system/menu/index.vue'), - 'system/config/index.vue': () => import('@/views/system/config/index.vue'), - 'data-source/index.vue': () => import('@/views/data-source/index.vue') + 'Layout': () => import('@/layout/index.vue'), + 'home/index': () => import('@/views/home/index.vue'), + 'modules/dashboardBig/views/index': () => import('@/modules/dashboardBig/views/index.vue'), + 'modules/dashboardBig/views/order': () => import('@/modules/dashboardBig/views/order.vue'), + 'modules/dashboardBig/views/cost': () => import('@/modules/dashboardBig/views/cost.vue'), + 'modules/dashboardBig/views/energy': () => import('@/modules/dashboardBig/views/energy.vue'), + 'modules/dashboardBig/views/oee': () => import('@/modules/dashboardBig/views/oee.vue'), + 'modules/dashboardBig/views/output': () => import('@/modules/dashboardBig/views/output.vue'), + 'modules/dashboardBig/views/stopAnalysis': () => import('@/modules/dashboardBig/views/stopAnalysis.vue'), + 'screens/list': () => import('@/views/screens/list.vue'), + 'screens/create': () => import('@/views/screens/create.vue'), + 'screens/edit': () => import('@/views/screens/edit.vue'), + 'screens/acid-rolling/index': () => import('@/views/screens/acid-rolling/index.vue'), + 'reports/index': () => import('@/views/reports/index.vue'), + 'reports/acid-rolling/index': () => import('@/views/reports/acid-rolling/index.vue'), + 'reports/acid-stop/index': () => import('@/views/reports/acid-stop/index.vue'), + 'system/user/index': () => import('@/views/system/user/index.vue'), + 'system/role/index': () => import('@/views/system/role/index.vue'), + 'system/menu/index': () => import('@/views/system/menu/index.vue'), + 'system/config/index': () => import('@/views/system/config/index.vue'), + 'data-source/index': () => import('@/views/data-source/index.vue') } return componentMap[path] || (() => import('@/views/home/index.vue')) } @@ -70,4 +83,4 @@ export default { state, mutations, actions -} +} \ No newline at end of file diff --git a/src/style.css b/src/style.css deleted file mode 100644 index 4874ad0..0000000 --- a/src/style.css +++ /dev/null @@ -1,281 +0,0 @@ -/* 基础样式 */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body { - height: 100%; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; - font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; - font-size: 14px; - color: #606266; - background-color: #f5f7fa; -} - -#app { - height: 100%; - min-height: 100vh; -} - -/* 布局类 */ -.app-container { - padding: 20px; -} - -.components-container { - margin: 30px 50px; - position: relative; -} - -/* 文字对齐 */ -.text-center { - text-align: center; -} - -.text-left { - text-align: left; -} - -.text-right { - text-align: right; -} - -/* 浮动 */ -.fr { - float: right; -} - -.fl { - float: left; -} - -/* 清除浮动 */ -.clearfix { - &:after { - visibility: hidden; - display: block; - font-size: 0; - content: " "; - clear: both; - height: 0; - } -} - -/* 链接样式 */ -a, a:focus, a:hover { - cursor: pointer; - color: inherit; - text-decoration: none; -} - -/* 按钮栏 */ -.sub-navbar { - height: 50px; - line-height: 50px; - position: relative; - width: 100%; - text-align: right; - padding-right: 20px; - background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%); - - .subtitle { - font-size: 20px; - color: #fff; - } -} - -/* 筛选容器 */ -.filter-container { - padding-bottom: 10px; - - .filter-item { - display: inline-block; - vertical-align: middle; - margin-bottom: 10px; - } -} - -/* 分页容器 */ -.pagination-container { - margin-top: 30px; -} - -/* 滚动条样式 */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: #d3dce6; - border-radius: 3px; -} - -::-webkit-scrollbar-thumb { - background: #99a9bf; - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: #667a8a; -} - -/* 卡片通用样式 */ -.card { - background: #fff; - border-radius: 4px; - box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); -} - -/* 按钮容器 */ -.btn-container { - display: inline-block; - - .el-button { - margin-right: 10px; - - &:last-child { - margin-right: 0; - } - } -} - -.btn-edit { - color: #67c23a; - border-color: #67c23a; - - &:hover { - background: #e8f5e9; - } -} - -.btn-delete { - color: #f56c6c; - border-color: #f56c6c; - - &:hover { - background: #fef0f0; - } -} - -.btn-detail { - color: #409eff; - border-color: #409eff; - - &:hover { - background: #ecf5ff; - } -} - -/* 表格样式 */ -.table-container { - background: #fff; - border-radius: 4px; - box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); - - .el-table { - font-size: 12px; - } - - .el-table th { - background: #f5f7fa; - font-weight: bold; - color: #606266; - } - - .el-table td { - color: #303133; - } -} - -/* 表单样式 */ -.form-container { - background: #fff; - border-radius: 4px; - padding: 20px; - box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); - - .el-form-item { - margin-bottom: 20px; - } -} - -/* 状态标签 */ -.status-tag { - display: inline-block; - padding: 2px 8px; - border-radius: 4px; - font-size: 12px; - - &.status-success { - background: #e8f5e9; - color: #67c23a; - } - - &.status-warning { - background: #fef5e7; - color: #e6a23c; - } - - &.status-danger { - background: #fef0f0; - color: #f56c6c; - } - - &.status-info { - background: #ecf5ff; - color: #409eff; - } -} - -/* 图表容器 */ -.chart-container { - width: 100%; - height: 100%; -} - -/* 响应式布局 */ -@media screen and (max-width: 1200px) { - .app-container { - padding: 10px; - } -} - -/* 大屏专用样式 */ -.big-screen { - width: 1920px; - height: 1080px; - background: #0a1628; - color: #d3d6dd; - overflow: hidden; -} - -/* 全局过渡动画 */ -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.3s ease; -} - -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.slide-left-enter-active, -.slide-left-leave-active { - transition: all 0.3s ease; -} - -.slide-left-enter-from { - opacity: 0; - transform: translateX(-20px); -} - -.slide-left-leave-to { - opacity: 0; - transform: translateX(20px); -} diff --git a/src/views/dashboard/cost/index.vue b/src/views/dashboard/cost/index.vue index 101edb8..c3f577e 100644 --- a/src/views/dashboard/cost/index.vue +++ b/src/views/dashboard/cost/index.vue @@ -1,100 +1,140 @@ \ No newline at end of file diff --git a/src/views/reports/acid-rolling/index.vue b/src/views/reports/acid-rolling/index.vue index c31e93a..a23441a 100644 --- a/src/views/reports/acid-rolling/index.vue +++ b/src/views/reports/acid-rolling/index.vue @@ -32,28 +32,8 @@ - - - - - - - - - - - - - - - - - - - - - 查询 + 查询 导出 列设置 保存产出报表 @@ -86,8 +66,8 @@ - - + + @@ -106,9 +86,9 @@ - + - + @@ -143,11 +123,12 @@ + \ No newline at end of file diff --git a/src/views/screens/acid-rolling/index.vue b/src/views/screens/acid-rolling/index.vue index 35cf048..11eac8e 100644 --- a/src/views/screens/acid-rolling/index.vue +++ b/src/views/screens/acid-rolling/index.vue @@ -1,189 +1,106 @@ + \ No newline at end of file diff --git a/src/views/screens/create.vue b/src/views/screens/create.vue new file mode 100644 index 0000000..3e9e98c --- /dev/null +++ b/src/views/screens/create.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/src/views/screens/edit.vue b/src/views/screens/edit.vue new file mode 100644 index 0000000..f9c190d --- /dev/null +++ b/src/views/screens/edit.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/src/views/screens/list.vue b/src/views/screens/list.vue new file mode 100644 index 0000000..e13b3dc --- /dev/null +++ b/src/views/screens/list.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 13a67d2..27f146f 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -17,7 +17,7 @@
- + @@ -41,7 +41,7 @@ @@ -63,10 +63,10 @@ const menuList = ref([ ] }, { - menuId: 2, menuName: '大屏管理', path: '/screens', component: 'Layout', menuType: '0', icon: 'monitor', sort: 2, status: 1, + menuId: 2, menuName: '大屏管理', path: '/screen-manage', component: 'Layout', menuType: '0', icon: 'monitor', sort: 2, status: 1, children: [ - { menuId: 21, menuName: '大屏列表', path: '/screens', component: 'screens/index', menuType: '1', icon: 'list', sort: 1, status: 1 }, - { menuId: 22, menuName: '酸轧大屏', path: '/screens/acid-rolling', component: 'screens/acid-rolling/index', menuType: '1', icon: 'chart', sort: 2, status: 1 } + { menuId: 21, menuName: '大屏列表', path: '/screen-manage', component: 'screens/list', menuType: '1', icon: 'list', sort: 1, status: 1 }, + { menuId: 22, menuName: '新建大屏', path: '/screen-manage/create', component: 'screens/create', menuType: '1', icon: 'plus', sort: 2, status: 1 } ] } ]) @@ -80,6 +80,11 @@ const getMenuTypeTag = (type) => { const map = { '0': 'warning', '1': 'primary', '2': 'info' } return map[type] || 'default' } + +const handleAddChild = (row) => { + console.log('新增子菜单', row) + // 这里可以添加打开弹窗的逻辑 +}