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 @@