From b43550acc2bfb4ee6559f37576065308edb3db33 Mon Sep 17 00:00:00 2001 From: haoliang <821644@qq.com> Date: Tue, 28 Apr 2026 00:24:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(mock):=20=E6=B7=BB=E5=8A=A012=E4=B8=AA?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=9A=84Mock=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自定义Vite configureServer中间件,支持所有API端点的Mock数据返回 Ultraworked with Sisyphus Co-authored-by: Sisyphus --- frontend/mock/alert.ts | 18 +++++ frontend/mock/brand.ts | 82 ++++++++++++++++++++ frontend/mock/collect-address.ts | 39 ++++++++++ frontend/mock/dashboard.ts | 70 +++++++++++++++++ frontend/mock/log.ts | 21 +++++ frontend/mock/login.ts | 24 ++++++ frontend/mock/machine.ts | 119 ++++++++++++++++++++++++++++ frontend/mock/plugin.ts | 128 +++++++++++++++++++++++++++++++ frontend/mock/production.ts | 30 ++++++++ frontend/mock/screen-config.ts | 32 ++++++++ frontend/mock/screen.ts | 41 ++++++++++ frontend/mock/settings.ts | 38 +++++++++ frontend/mock/types.ts | 16 ++++ frontend/mock/worker.ts | 51 ++++++++++++ 14 files changed, 709 insertions(+) create mode 100644 frontend/mock/alert.ts create mode 100644 frontend/mock/brand.ts create mode 100644 frontend/mock/collect-address.ts create mode 100644 frontend/mock/dashboard.ts create mode 100644 frontend/mock/log.ts create mode 100644 frontend/mock/login.ts create mode 100644 frontend/mock/machine.ts create mode 100644 frontend/mock/plugin.ts create mode 100644 frontend/mock/production.ts create mode 100644 frontend/mock/screen-config.ts create mode 100644 frontend/mock/screen.ts create mode 100644 frontend/mock/settings.ts create mode 100644 frontend/mock/types.ts create mode 100644 frontend/mock/worker.ts diff --git a/frontend/mock/alert.ts b/frontend/mock/alert.ts new file mode 100644 index 0000000..737bc79 --- /dev/null +++ b/frontend/mock/alert.ts @@ -0,0 +1,18 @@ +import type { MockMethod } from './types' + +const alerts = [ + { id: 1, createdAt: '2026-04-25T17:30:00', alertType: 'collect_fail', title: '采集请求失败', machineName: '西-1.8', detail: 'HTTP超时(30s),连续失败3次', isResolved: 0, resolvedAt: null }, + { id: 2, createdAt: '2026-04-25T17:00:00', alertType: 'device_offline', title: '设备离线', machineName: '东-2.5', detail: '连续3次Ping失败,IP:10.1.2.5', isResolved: 0, resolvedAt: null }, + { id: 3, createdAt: '2026-04-25T16:00:00', alertType: 'production_anomaly', title: '产量骤降', machineName: '南-3.1', detail: '近1小时产量较前1小时下降>50%', isResolved: 1, resolvedAt: '2026-04-25T16:30:00' }, + { id: 4, createdAt: '2026-04-25T15:00:00', alertType: 'unknown_device', title: '未知设备接入', machineName: null, detail: '采集地址FANUC-A栋返回未注册设备: fanake_9.9', isResolved: 0, resolvedAt: null }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/alert/statistics', method: 'get', response: () => ({ code: 0, data: { unresolved: 15, collectFail: 5, deviceOffline: 6, productionAnomaly: 2, unknownDevice: 2, serviceError: 0 } }) }, + { url: '/mock-api/admin/alert', method: 'get', response: () => ({ code: 0, data: { items: alerts, total: 320, page: 1, pageSize: 20 } }) }, + { url: '/mock-api/admin/alert/resolve', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/alert/batch-resolve', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/machine/list', method: 'get', response: () => ({ code: 0, data: { items: [{ id: 1, name: '西-1.8' }, { id: 2, name: '西-1.10' }] } }) }, +] + +export default mock diff --git a/frontend/mock/brand.ts b/frontend/mock/brand.ts new file mode 100644 index 0000000..55fbe95 --- /dev/null +++ b/frontend/mock/brand.ts @@ -0,0 +1,82 @@ +import type { MockMethod } from './types' + +const brands = [ + { id: 1, brandName: 'FANUC', deviceField: 'device', tagsPath: 'tags', isEnabled: 1, fieldCount: 16 }, + { id: 2, brandName: 'SIEMENS', deviceField: 'device', tagsPath: 'tags', isEnabled: 1, fieldCount: 12 }, + { id: 3, brandName: 'MITSUBISHI', deviceField: 'device', tagsPath: 'tags', isEnabled: 0, fieldCount: 8 }, +] + +const standardFields = [ + 'program_name', 'part_count', 'device_status', 'run_status', 'operate_mode', + 'spindle_speed_set', 'feed_speed_set', 'spindle_speed_actual', 'feed_speed_actual', + 'spindle_load', 'spindle_override', 'power_on_time', 'run_time', 'cutting_time', + 'cycle_time', 'machining_status', +] + +const fanucMappings = [ + { id: 1, standardField: 'program_name', fieldName: 'Tag5', matchBy: 'id', dataType: 'string', isRequired: 1 }, + { id: 2, standardField: 'part_count', fieldName: 'Tag8', matchBy: 'id', dataType: 'number', isRequired: 1 }, + { id: 3, standardField: 'device_status', fieldName: '_io_status', matchBy: 'id', dataType: 'number', isRequired: 1 }, + { id: 4, standardField: 'run_status', fieldName: 'Tag9', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 5, standardField: 'operate_mode', fieldName: 'Tag10', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 6, standardField: 'spindle_speed_set', fieldName: 'Tag11', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 7, standardField: 'feed_speed_set', fieldName: 'Tag12', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 8, standardField: 'spindle_speed_actual', fieldName: 'Tag13', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 9, standardField: 'feed_speed_actual', fieldName: 'Tag14', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 10, standardField: 'spindle_load', fieldName: 'Tag15', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 11, standardField: 'spindle_override', fieldName: 'Tag16', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 12, standardField: 'power_on_time', fieldName: 'Tag17', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 13, standardField: 'run_time', fieldName: 'Tag18', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 14, standardField: 'cutting_time', fieldName: 'Tag19', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 15, standardField: 'cycle_time', fieldName: 'Tag20', matchBy: 'id', dataType: 'number', isRequired: 0 }, + { id: 16, standardField: 'machining_status', fieldName: 'Tag21', matchBy: 'id', dataType: 'string', isRequired: 0 }, +] + +const mock: MockMethod[] = [ + { + url: '/mock-api/admin/brand', + method: 'get', + response: () => ({ code: 0, data: { items: brands } }), + }, + { + url: '/mock-api/admin/brand/detail', + method: 'get', + response: ({ query }: any) => { + const id = Number(query.id) + const b = brands.find((b: any) => b.id === id) + return { code: 0, data: { ...b, mappings: id === 1 ? fanucMappings : [] } } + }, + }, + { + url: '/mock-api/admin/brand', + method: 'post', + response: () => ({ code: 0, message: 'success', data: { id: 4, brandName: '新品牌' } }), + }, + { + url: '/mock-api/admin/brand/update', + method: 'post', + response: () => ({ code: 0, message: 'success', data: null }), + }, + { + url: '/mock-api/admin/brand/delete', + method: 'post', + response: () => ({ code: 0, message: 'success', data: null }), + }, + { + url: '/mock-api/admin/brand/copy', + method: 'post', + response: () => ({ code: 0, message: 'success', data: { id: 4, brandName: '新复制品牌' } }), + }, + { + url: '/mock-api/admin/brand/toggle', + method: 'post', + response: () => ({ code: 0, message: 'success', data: null }), + }, + { + url: '/mock-api/admin/brand/standard-fields', + method: 'get', + response: () => ({ code: 0, data: { items: standardFields } }), + }, +] + +export default mock diff --git a/frontend/mock/collect-address.ts b/frontend/mock/collect-address.ts new file mode 100644 index 0000000..8b3fb8c --- /dev/null +++ b/frontend/mock/collect-address.ts @@ -0,0 +1,39 @@ +import type { MockMethod } from './types' + +const addresses = [ + { id: 1, name: 'FANUC-A栋', url: 'http://10.1.1.1/', brandId: 1, brandName: 'FANUC', collectInterval: 30, isEnabled: 1, lastCollectTime: '2026-04-25T17:36:38', failCount: 0, machineCount: 32 }, + { id: 2, name: 'FANUC-B栋', url: 'http://10.1.2.1/', brandId: 1, brandName: 'FANUC', collectInterval: 60, isEnabled: 1, lastCollectTime: '2026-04-25T17:35:38', failCount: 0, machineCount: 28 }, + { id: 3, name: 'SIEMENS-C栋', url: 'http://10.1.3.1/', brandId: 2, brandName: 'SIEMENS', collectInterval: 30, isEnabled: 0, lastCollectTime: null, failCount: 3, machineCount: 0 }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/collect-address', method: 'get', response: () => ({ code: 0, data: { items: addresses } }) }, + { url: '/mock-api/admin/collect-address/detail', method: 'get', response: ({ query }: any) => { const id = Number(query.id); const a = addresses.find((a: any) => a.id === id); return { code: 0, data: a || null } } }, + { url: '/mock-api/admin/collect-address', method: 'post', response: () => ({ code: 0, message: 'success', data: { id: 4, name: '新地址' } }) }, + { url: '/mock-api/admin/collect-address/update', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/collect-address/delete', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/collect-address/toggle', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/collect-address/machines', method: 'get', response: () => ({ code: 0, data: { items: [ + { machineId: 1, machineName: '西-1.8', deviceCode: 'fanake_1.8', workshopName: 'A栋', isOnline: 1, programName: '1566.NC', partCount: 580 }, + { machineId: 2, machineName: '西-1.10', deviceCode: 'fanake_1.10', workshopName: 'A栋', isOnline: 1, programName: 'O123.NC', partCount: 120 }, + ] } }) }, + { url: '/mock-api/admin/collect-address/collect-records', method: 'get', response: () => ({ code: 0, data: { items: [ + { requestTime: '2026-04-25T17:36:38', duration: 1200, isSuccess: 1, machineCount: 32, failMachineCount: 0, machineName: '西-1.8' }, + { requestTime: '2026-04-25T17:35:38', duration: 3500, isSuccess: 1, machineCount: 31, failMachineCount: 1, machineName: '西-1.10' }, + { requestTime: '2026-04-25T17:35:08', duration: null, isSuccess: 0, machineCount: null, failMachineCount: null, machineName: '西-1.12' }, + ] } }) }, + { url: '/mock-api/admin/collect-address/raw-json', method: 'get', response: () => ({ code: 0, data: { rawJson: '[{device:fanake_1.8,tags:[{id:Tag5,value:1566.NC}]}]' } }) }, + { url: '/mock-api/admin/brand/list', method: 'get', response: () => ({ code: 0, data: { items: [{ id: 1, brandName: 'FANUC' }, { id: 2, brandName: 'SIEMENS' }] } }) }, + { url: '/mock-api/admin/machine/list', method: 'get', response: ({ query }: any) => { + const brandName = query?.brandName || '' + const machines = brandName === 'FANUC' ? [ + { machineId: 1, machineName: '西-1.8', deviceCode: 'fanake_1.8', workshopName: 'A栋', isOnline: 1, programName: '1566.NC', partCount: 580 }, + { machineId: 2, machineName: '西-1.10', deviceCode: 'fanake_1.10', workshopName: 'A栋', isOnline: 1, programName: 'O123.NC', partCount: 120 }, + ] : [ + { machineId: 3, machineName: '东-2.3', deviceCode: 'sec_2.3', workshopName: 'B栋', isOnline: 0, programName: '2000.NC', partCount: 50 } + ] + return { code: 0, data: { items: machines } } + } }, +] + +export default mock diff --git a/frontend/mock/dashboard.ts b/frontend/mock/dashboard.ts new file mode 100644 index 0000000..2bfc4ad --- /dev/null +++ b/frontend/mock/dashboard.ts @@ -0,0 +1,70 @@ +import type { MockMethod } from './types' + +const mock: MockMethod[] = [ + { + url: '/mock-api/admin/dashboard/summary', + method: 'get', + response: { + code: 0, + data: { + onlineCount: 142, + totalMachines: 160, + todayProduction: 2847, + activeAlerts: 3, + }, + }, + }, + { + url: '/mock-api/admin/collector/status', + method: 'get', + response: { + code: 0, + data: { + status: 'running', + uptimeSeconds: 316800, + lastCollectTime: '2026-04-25T17:36:38', + successCount: 1280, + failCount: 5, + }, + }, + }, + { + url: '/mock-api/admin/dashboard/machine-rank', + method: 'get', + response: { + code: 0, + data: { + items: [ + { rank: 1, machineId: 1, machineName: '西-1.8', program: '1566.NC', quantity: 580, status: 1 }, + { rank: 2, machineId: 2, machineName: '西-1.10', program: 'O123.NC', quantity: 420, status: 1 }, + { rank: 3, machineId: 3, machineName: '东-2.0', program: 'A456.NC', quantity: 380, status: 1 }, + { rank: 4, machineId: 4, machineName: '东-2.5', program: 'B789.NC', quantity: 310, status: 0 }, + { rank: 5, machineId: 5, machineName: '南-3.1', program: 'C012.NC', quantity: 290, status: 1 }, + { rank: 6, machineId: 6, machineName: '南-3.2', program: 'D345.NC', quantity: 240, status: 1 }, + { rank: 7, machineId: 7, machineName: '北-4.0', program: 'E678.NC', quantity: 210, status: 1 }, + { rank: 8, machineId: 8, machineName: '北-4.1', program: 'F901.NC', quantity: 180, status: 0 }, + { rank: 9, machineId: 9, machineName: '西-1.5', program: 'G234.NC', quantity: 150, status: 1 }, + { rank: 10, machineId: 10, machineName: '东-2.8', program: 'H567.NC', quantity: 87, status: 1 }, + ], + }, + }, + }, + { + url: '/mock-api/admin/dashboard/worker-rank', + method: 'get', + response: { + code: 0, + data: { + items: [ + { rank: 1, workerId: 1, workerName: '张三', machineCount: 3, totalQuantity: 1240 }, + { rank: 2, workerId: 2, workerName: '李四', machineCount: 2, totalQuantity: 980 }, + { rank: 3, workerId: 3, workerName: '王五', machineCount: 4, totalQuantity: 870 }, + { rank: 4, workerId: 4, workerName: '赵六', machineCount: 2, totalQuantity: 650 }, + { rank: 5, workerId: 5, workerName: '孙七', machineCount: 3, totalQuantity: 520 }, + ], + }, + }, + }, +] + +export default mock diff --git a/frontend/mock/log.ts b/frontend/mock/log.ts new file mode 100644 index 0000000..93ea080 --- /dev/null +++ b/frontend/mock/log.ts @@ -0,0 +1,21 @@ +import type { MockMethod } from './types' + +const adjustments = [ + { id: 1, createdAt: '2026-04-25T15:30:00', targetTable: 'daily_production', targetId: 3, oldValue: 310, newValue: 320, reason: '传感器计数偏差,手工校准', operatorIp: '192.168.1.5' }, + { id: 2, createdAt: '2026-04-24T10:15:00', targetTable: 'worker_daily_summary', targetId: 12, oldValue: 850, newValue: 900, reason: '漏计补录,夜班产量未计入', operatorIp: '192.168.1.5' }, +] + +const systemLogs = [ + { id: 1, createdAt: '2026-04-25T17:36:38', logLevel: 'INFO', source: 'CncCollector', message: '采集完成: 32台成功, 0台失败', stackTrace: null, extraData: null }, + { id: 2, createdAt: '2026-04-25T17:35:38', logLevel: 'WARN', source: 'CncCollector', message: '采集失败: 1台超时(fanake_2.5)', stackTrace: null, extraData: null }, + { id: 3, createdAt: '2026-04-25T17:00:00', logLevel: 'ERROR', source: 'CncCollector', message: '连接拒绝: 10.1.2.5', stackTrace: 'System.Net.Http.HttpRequestException: Connection refused', extraData: null }, + { id: 4, createdAt: '2026-04-25T01:00:00', logLevel: 'INFO', source: 'Scheduler', message: '日终汇总完成: 160台, 耗时12秒', stackTrace: null, extraData: null }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/log/adjustment', method: 'get', response: () => ({ code: 0, data: { items: adjustments, total: 80, page: 1, pageSize: 20 } }) }, + { url: '/mock-api/admin/log/adjustment/export', method: 'get', response: () => ({ code: 0, data: null }) }, + { url: '/mock-api/admin/log/system', method: 'get', response: () => ({ code: 0, data: { items: systemLogs, total: 5600, page: 1, pageSize: 20 } }) }, +] + +export default mock diff --git a/frontend/mock/login.ts b/frontend/mock/login.ts new file mode 100644 index 0000000..7e528a0 --- /dev/null +++ b/frontend/mock/login.ts @@ -0,0 +1,24 @@ +import type { MockMethod } from './types' + +const mock: MockMethod[] = [ + { + url: '/mock-api/admin/login', + method: 'post', + response: (req: any) => { + const { username, password, rememberMe } = req.body + if (username === 'admin' && password === 'admin123') { + return { + code: 0, + message: 'success', + data: { + token: rememberMe ? 'eyJhbGciOiJIUzI1NiJ9.admin.7d' : 'eyJhbGciOiJIUzI1NiJ9.admin.8h', + expiresIn: rememberMe ? 604800 : 28800, + }, + } + } + return { code: 40001, message: '用户名或密码错误', data: null } + }, + }, +] + +export default mock diff --git a/frontend/mock/machine.ts b/frontend/mock/machine.ts new file mode 100644 index 0000000..9927f19 --- /dev/null +++ b/frontend/mock/machine.ts @@ -0,0 +1,119 @@ +import type { MockMethod } from './types' + +const machines = [ + { id: 1, name: '西-1.8', deviceCode: 'fanake_1.8', workshopId: 1, workshopName: 'A栋', brandId: 1, brandName: 'FANUC', collectAddressId: 1, ipAddress: '10.1.1.8', isOnline: 1, workerId: 1, workerName: '张三', isEnabled: 1 }, + { id: 2, name: '西-1.10', deviceCode: 'fanake_1.10', workshopId: 1, workshopName: 'A栋', brandId: 1, brandName: 'FANUC', collectAddressId: 1, ipAddress: '10.1.1.10', isOnline: 1, workerId: 2, workerName: '李四', isEnabled: 1 }, + { id: 3, name: '东-2.0', deviceCode: 'fanake_2.0', workshopId: 2, workshopName: 'B栋', brandId: 1, brandName: 'FANUC', collectAddressId: 2, ipAddress: '10.1.2.0', isOnline: 0, workerId: 3, workerName: '王五', isEnabled: 1 }, + { id: 4, name: '东-2.5', deviceCode: 'siemens_2.5', workshopId: 2, workshopName: 'B栋', brandId: 2, brandName: 'SIEMENS', collectAddressId: 2, ipAddress: '10.1.2.5', isOnline: 0, workerId: null, workerName: null, isEnabled: 0 }, + { id: 5, name: '南-3.1', deviceCode: 'fanake_3.1', workshopId: 3, workshopName: 'C栋', brandId: 1, brandName: 'FANUC', collectAddressId: 1, ipAddress: '10.1.3.1', isOnline: 1, workerId: 4, workerName: '赵六', isEnabled: 1 }, +] + +const mock: MockMethod[] = [ + { + url: '/mock-api/admin/machine', + method: 'get', + response: ({ query }: any) => { + let items = [...machines] + if (query.workshopId) items = items.filter((m: any) => m.workshopId === Number(query.workshopId)) + if (query.isOnline !== undefined && query.isOnline !== '') items = items.filter((m: any) => m.isOnline === Number(query.isOnline)) + if (query.brandId) items = items.filter((m: any) => m.brandId === Number(query.brandId)) + if (query.keyword) items = items.filter((m: any) => m.name.includes(query.keyword) || m.deviceCode.includes(query.keyword)) + return { code: 0, data: { items, total: items.length, page: Number(query.page || 1), pageSize: Number(query.pageSize || 20) } } + }, + }, + { + url: '/mock-api/admin/machine/detail', + method: 'get', + response: ({ query }: any) => { + const id = Number(query.id) + const m = machines.find((m: any) => m.id === id) + return { code: 0, data: m || null } + }, + }, + { + url: '/mock-api/admin/machine/status', + method: 'get', + response: () => ({ + code: 0, + data: { programName: '1566.NC', partCount: 580, runStatus: '运行中', operateMode: '自动', spindleSpeedSet: 3000, feedSpeedSet: 500, spindleSpeedActual: 2980, feedSpeedActual: 480, spindleLoad: 65, machiningStatus: 'G01', lastCollectTime: '2026-04-25T17:36:38' }, + }), + }, + { + url: '/mock-api/admin/machine', + method: 'post', + response: () => ({ code: 0, message: 'success', data: { id: 161, name: '新机床' } }), + }, + { + url: '/mock-api/admin/machine/update', + method: 'post', + response: () => ({ code: 0, message: 'success', data: null }), + }, + { + url: '/mock-api/admin/machine/delete', + method: 'post', + response: () => ({ code: 0, message: 'success', data: null }), + }, + { + url: '/mock-api/admin/machine/batch-status', + method: 'post', + response: () => ({ code: 0, message: 'success', data: null }), + }, + { + url: '/mock-api/admin/machine/production/today', + method: 'get', + response: () => ({ + code: 0, + data: { items: [ + { programName: '1566.NC', quantity: 580, runTime: '4h20m', cuttingTime: '3h50m' }, + { programName: 'O123.NC', quantity: 120, runTime: '2h10m', cuttingTime: '1h45m' }, + ] }, + }), + }, + { + url: '/mock-api/admin/machine/production/trend', + method: 'get', + response: () => ({ + code: 0, + data: { items: [ + { date: '2026-04-19', quantity: 820 }, { date: '2026-04-20', quantity: 760 }, + { date: '2026-04-21', quantity: 910 }, { date: '2026-04-22', quantity: 850 }, + { date: '2026-04-23', quantity: 780 }, { date: '2026-04-24', quantity: 900 }, + { date: '2026-04-25', quantity: 700 }, + ] }, + }), + }, + { + url: '/mock-api/admin/machine/collect-records', + method: 'get', + response: () => ({ + code: 0, + data: { items: [ + { collectTime: '2026-04-25T17:36:35', programName: '1566.NC', partCount: 580, runStatus: '运行中' }, + { collectTime: '2026-04-25T17:36:05', programName: '1566.NC', partCount: 579, runStatus: '运行中' }, + { collectTime: '2026-04-25T17:35:35', programName: '1566.NC', partCount: 578, runStatus: '运行中' }, + ] }, + }), + }, + { + url: '/mock-api/admin/workshop/list', + method: 'get', + response: () => ({ code: 0, data: { items: [{ id: 1, name: 'A栋' }, { id: 2, name: 'B栋' }, { id: 3, name: 'C栋' }] } }), + }, + { + url: '/mock-api/admin/brand/list', + method: 'get', + response: () => ({ code: 0, data: { items: [{ id: 1, brandName: 'FANUC' }, { id: 2, brandName: 'SIEMENS' }, { id: 3, brandName: 'MITSUBISHI' }] } }), + }, + { + url: '/mock-api/admin/collect-address/list', + method: 'get', + response: () => ({ code: 0, data: { items: [{ id: 1, name: 'FANUC-A栋', brandId: 1 }, { id: 2, name: 'FANUC-B栋', brandId: 1 }] } }), + }, + { + url: '/mock-api/admin/worker/list', + method: 'get', + response: () => ({ code: 0, data: { items: [{ id: 1, name: '张三', code: 'W001' }, { id: 2, name: '李四', code: 'W002' }, { id: 3, name: '王五', code: 'W003' }] } }), + }, +] + +export default mock diff --git a/frontend/mock/plugin.ts b/frontend/mock/plugin.ts new file mode 100644 index 0000000..81ed7ac --- /dev/null +++ b/frontend/mock/plugin.ts @@ -0,0 +1,128 @@ +/** + * 自定义 Vite Mock 插件 + * 使用 configureServer 中间件拦截请求,不依赖任何第三方库 + * 与所有 Vite 版本兼容 + */ +import type { Plugin, ViteDevServer } from 'vite' +import type { MockMethod, MockRequest } from './types' +import fs from 'fs' +import path from 'path' + +interface MockPluginOptions { + mockPath: string + enable?: boolean +} + +export function viteMockPlugin(options: MockPluginOptions): Plugin { + const { mockPath, enable = true } = options + + return { + name: 'vite-mock-plugin', + configureServer(server: ViteDevServer) { + if (!enable) return + + // 缓存已加载的 mock 路由 + let mockRoutes: MockMethod[] = [] + let loaded = false + + async function loadMockRoutes(): Promise { + if (loaded) return mockRoutes + const mockDir = path.resolve(process.cwd(), mockPath) + if (!fs.existsSync(mockDir)) { + console.warn(`[mock] 目录不存在: ${mockDir}`) + return [] + } + + const files = fs.readdirSync(mockDir).filter( + f => (f.endsWith('.ts') || f.endsWith('.js')) && !f.startsWith('_') + ) + + const routes: MockMethod[] = [] + for (const file of files) { + try { + const filePath = path.join(mockDir, file) + // 使用 Vite 的 ssrLoadModule 支持 TypeScript 热加载 + const mod = await server.ssrLoadModule(filePath) + if (mod.default && Array.isArray(mod.default)) { + routes.push(...mod.default) + } + } catch (e) { + console.error(`[mock] 加载失败: ${file}`, e) + } + } + + console.log(`[mock] 已加载 ${routes.length} 条 mock 路由`) + mockRoutes = routes + loaded = true + return routes + } + + // 监听 mock 文件变更,清除缓存 + server.watcher.add(path.resolve(process.cwd(), mockPath)) + server.watcher.on('change', (file) => { + if (file.startsWith(path.resolve(process.cwd(), mockPath))) { + loaded = false + console.log(`[mock] 文件变更,重新加载: ${path.basename(file)}`) + } + }) + server.watcher.on('add', (file) => { + if (file.startsWith(path.resolve(process.cwd(), mockPath))) { + loaded = false + } + }) + + server.middlewares.use(async (req, res, next) => { + const routes = await loadMockRoutes() + + // 解析请求信息 + const reqMethod = req.method?.toLowerCase() || 'get' + const parsedUrl = new URL(req.url || '/', 'http://localhost') + const reqPath = parsedUrl.pathname + + // 匹配路由:URL 精确匹配 + method 匹配 + const matched = routes.find(r => { + if (r.url !== reqPath) return false + if (!r.method) return true + return r.method.toLowerCase() === reqMethod + }) + + if (!matched) return next() + + // 构建 MockRequest + const query: Record = {} + parsedUrl.searchParams.forEach((v, k) => { query[k] = v }) + + // 读取请求体(POST/PUT/PATCH) + const body = await new Promise((resolve) => { + if (['post', 'put', 'patch'].includes(reqMethod)) { + const chunks: Buffer[] = [] + req.on('data', (chunk: Buffer) => chunks.push(chunk)) + req.on('end', () => { + const raw = Buffer.concat(chunks).toString('utf-8') + try { + resolve(JSON.parse(raw)) + } catch { + resolve(raw) + } + }) + } else { + resolve(undefined) + } + }) + + // 执行 response + let responseBody: any + if (typeof matched.response === 'function') { + const mockReq: MockRequest = { query, body, url: req.url || '', method: reqMethod } + responseBody = matched.response(mockReq) + } else { + responseBody = matched.response + } + + res.setHeader('Content-Type', 'application/json') + res.statusCode = 200 + res.end(JSON.stringify(responseBody)) + }) + }, + } +} diff --git a/frontend/mock/production.ts b/frontend/mock/production.ts new file mode 100644 index 0000000..a843c0d --- /dev/null +++ b/frontend/mock/production.ts @@ -0,0 +1,30 @@ +import type { MockMethod } from './types' + +const productionItems = [ + { id: 1, date: '2026-04-25', machineName: '西-1.8', programName: '1566.NC', quantity: 580, runTime: '4h20m', cuttingTime: '3h50m', dataStatus: 'normal', isAdjusted: 0, adjustedQuantity: null }, + { id: 2, date: '2026-04-25', machineName: '东-2.5', programName: 'A456.NC', quantity: null, runTime: null, cuttingTime: null, dataStatus: 'data_missing', isAdjusted: 0, adjustedQuantity: null }, + { id: 3, date: '2026-04-25', machineName: '北-4.1', programName: 'B789.NC', quantity: 310, runTime: '2h10m', cuttingTime: '1h50m', dataStatus: 'normal', isAdjusted: 1, adjustedQuantity: 320 }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/production/daily-summary', method: 'get', response: () => ({ code: 0, data: { totalQuantity: 8520, activeMachineCount: 142, totalCuttingTime: '580h', avgQuantityPerMachine: 60 } }) }, + { url: '/mock-api/admin/production/daily', method: 'get', response: () => ({ code: 0, data: { items: productionItems, total: 1200, page: 1, pageSize: 20 } }) }, + { url: '/mock-api/admin/production/adjust', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/production/adjustment-history', method: 'get', response: (config: any) => { + const rid = config?.query?.recordId + if (rid && Number(rid) === 1) { + return { code: 0, data: { items: [ + { createdAt: '2026-04-25T15:30:00', oldValue: 310, newValue: 320, reason: '传感器计数偏差,手工校准', operator: '系统' }, + { createdAt: '2026-04-26T09:45:00', oldValue: 320, newValue: 315, reason: '现场核对,微调', operator: '操作员A' } + ] } } + } + // 其他记录ID返回空历史 + return { code: 0, data: { items: [] } } + } }, + { url: '/mock-api/admin/production/export', method: 'get', response: () => ({ code: 0, data: null }) }, + { url: '/mock-api/admin/workshop/list', method: 'get', response: () => ({ code: 0, data: { items: [{ id: 1, name: 'A栋' }, { id: 2, name: 'B栋' }, { id: 3, name: 'C栋' }] } }) }, + { url: '/mock-api/admin/machine/list', method: 'get', response: () => ({ code: 0, data: { items: [{ id: 1, name: '西-1.8' }, { id: 2, name: '西-1.10' }, { id: 3, name: '东-2.0' }] } }) }, + { url: '/mock-api/admin/worker/list', method: 'get', response: () => ({ code: 0, data: { items: [{ id: 1, name: '张三' }, { id: 2, name: '李四' }] } }) }, +] + +export default mock diff --git a/frontend/mock/screen-config.ts b/frontend/mock/screen-config.ts new file mode 100644 index 0000000..0f818db --- /dev/null +++ b/frontend/mock/screen-config.ts @@ -0,0 +1,32 @@ +import type { MockMethod } from './types' + +const cardConfigs = [ + { id: 1, cardKey: 'total_online', cardType: 'stat_number', title: '在线机床数', metric: 'online_count', dimension: null, sortOrder: 1, isEnabled: 1, chartConfig: null }, + { id: 2, cardKey: 'total_production_today', cardType: 'stat_number', title: '今日总产量', metric: 'part_count', dimension: null, sortOrder: 2, isEnabled: 1, chartConfig: null }, + { id: 3, cardKey: 'workshop_production', cardType: 'bar_chart', title: '各车间产量', metric: 'part_count', dimension: 'workshop', sortOrder: 3, isEnabled: 1, chartConfig: null }, + { id: 4, cardKey: 'worker_rank', cardType: 'rank_list', title: '工人产量排行', metric: 'part_count', dimension: 'worker', sortOrder: 4, isEnabled: 0, chartConfig: null }, + { id: 5, cardKey: 'machine_status', cardType: 'status_grid', title: '机床状态总览', metric: null, dimension: null, sortOrder: 5, isEnabled: 1, chartConfig: null }, + { id: 6, cardKey: 'collector_status', cardType: 'stat_number', title: '采集服务状态', metric: null, dimension: null, sortOrder: 6, isEnabled: 1, chartConfig: null }, + { id: 7, cardKey: 'production_trend', cardType: 'line_chart', title: '产量趋势(7天)', metric: 'part_count', dimension: null, sortOrder: 7, isEnabled: 1, chartConfig: null }, + { id: 8, cardKey: 'machine_rank', cardType: 'rank_list', title: '机床产量排行', metric: 'part_count', dimension: 'machine', sortOrder: 8, isEnabled: 1, chartConfig: null }, +] + +const filterConfigs = [ + { id: 1, screenKey: 'screen_main', filterType: 'workshop', filterValue: 'A栋', isDefault: 1, sortOrder: 1 }, + { id: 2, screenKey: 'screen_main', filterType: 'workshop', filterValue: 'B栋', isDefault: 0, sortOrder: 2 }, + { id: 3, screenKey: 'screen_main', filterType: 'brand', filterValue: 'FANUC', isDefault: 0, sortOrder: 3 }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/screen-config', method: 'get', response: () => ({ code: 0, data: { items: cardConfigs } }) }, + { url: '/mock-api/admin/screen-config', method: 'post', response: () => ({ code: 0, message: 'success', data: { id: 9 } }) }, + { url: '/mock-api/admin/screen-config/update', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/screen-config/delete', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/screen-config/toggle', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/screen-filter', method: 'get', response: () => ({ code: 0, data: { items: filterConfigs } }) }, + { url: '/mock-api/admin/screen-filter', method: 'post', response: () => ({ code: 0, message: 'success', data: { id: 4 } }) }, + { url: '/mock-api/admin/screen-filter/update', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/screen-filter/delete', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, +] + +export default mock diff --git a/frontend/mock/screen.ts b/frontend/mock/screen.ts new file mode 100644 index 0000000..30a2857 --- /dev/null +++ b/frontend/mock/screen.ts @@ -0,0 +1,41 @@ +import type { MockMethod } from './types' + +const mock: MockMethod[] = [ + { url: '/mock-api/screen/summary', method: 'get', response: () => ({ code: 0, data: { onlineCount: 142, totalMachines: 160, todayProduction: 8520, activeAlerts: 3, avgQuantityPerMachine: 60 } }) }, + { url: '/mock-api/screen/collector-status', method: 'get', response: () => ({ code: 0, data: { status: 'running', uptime: '3天16小时', successCount: 12800, failCount: 5, lastCollectTime: '2026-04-25T17:36:38' } }) }, + { url: '/mock-api/screen/workshop-production', method: 'get', response: () => ({ code: 0, data: { items: [{ name: 'A栋', quantity: 3200 }, { name: 'B栋', quantity: 2800 }, { name: 'C栋', quantity: 2520 }] } }) }, + { url: '/mock-api/screen/production-trend', method: 'get', response: () => ({ code: 0, data: { items: [ + { date: '04-19', quantity: 8200 }, { date: '04-20', quantity: 7600 }, + { date: '04-21', quantity: 9100 }, { date: '04-22', quantity: 8500 }, + { date: '04-23', quantity: 7800 }, { date: '04-24', quantity: 9000 }, + { date: '04-25', quantity: 8520 }, + ] } }) }, + { url: '/mock-api/screen/machine-rank', method: 'get', response: () => ({ code: 0, data: { items: [ + { rank: 1, machineName: '西-1.8', quantity: 580 }, + { rank: 2, machineName: '西-1.10', quantity: 420 }, + { rank: 3, machineName: '东-2.0', quantity: 380 }, + { rank: 4, machineName: '东-2.5', quantity: 310 }, + { rank: 5, machineName: '南-3.1', quantity: 290 }, + ] } }) }, + { url: '/mock-api/screen/worker-rank', method: 'get', response: () => ({ code: 0, data: { items: [ + { rank: 1, workerName: '张三', quantity: 1240 }, + { rank: 2, workerName: '李四', quantity: 980 }, + { rank: 3, workerName: '王五', quantity: 870 }, + { rank: 4, workerName: '赵六', quantity: 650 }, + { rank: 5, workerName: '孙七', quantity: 520 }, + ] } }) }, + { url: '/mock-api/screen/machine-status', method: 'get', response: () => ({ code: 0, data: { items: [ + { machineId: 1, machineName: '西-1.8', isOnline: 1 }, { machineId: 2, machineName: '西-1.10', isOnline: 1 }, + { machineId: 3, machineName: '东-2.0', isOnline: 0 }, { machineId: 4, machineName: '东-2.5', isOnline: 0 }, + { machineId: 5, machineName: '南-3.1', isOnline: 1 }, { machineId: 6, machineName: '南-3.2', isOnline: 1 }, + { machineId: 7, machineName: '北-4.0', isOnline: 1 }, { machineId: 8, machineName: '北-4.1', isOnline: 0 }, + ] } }) }, + { url: '/mock-api/screen/filters', method: 'get', response: () => ({ code: 0, data: { items: [ + { filterType: 'workshop', filterValue: 'A栋', isDefault: 1 }, + { filterType: 'workshop', filterValue: 'B栋', isDefault: 0 }, + { filterType: 'brand', filterValue: 'FANUC', isDefault: 0 }, + ] } }) }, + { url: '/mock-api/screen/refresh-interval', method: 'get', response: () => ({ code: 0, data: { interval: 15000 } }) }, +] + +export default mock diff --git a/frontend/mock/settings.ts b/frontend/mock/settings.ts new file mode 100644 index 0000000..086d307 --- /dev/null +++ b/frontend/mock/settings.ts @@ -0,0 +1,38 @@ +import type { MockMethod } from './types' + +const configs = [ + { id: 1, configKey: 'ping_interval', configValue: '60', valueType: 'number', description: 'Ping检测间隔(秒)' }, + { id: 2, configKey: 'collect_retry_count', configValue: '3', valueType: 'number', description: '采集失败重试次数' }, + { id: 3, configKey: 'collect_retry_interval', configValue: '30', valueType: 'number', description: '采集重试间隔(秒)' }, + { id: 4, configKey: 'collect_fail_alert_threshold', configValue: '5', valueType: 'number', description: '连续失败N次触发告警' }, + { id: 5, configKey: 'daily_summary_time', configValue: '01:00', valueType: 'string', description: '日终汇总执行时间' }, + { id: 6, configKey: 'log_retention_days', configValue: '90', valueType: 'number', description: '原始采集日志保留天数' }, + { id: 7, configKey: 'bigscreen_refresh_interval', configValue: '10', valueType: 'number', description: '大屏刷新间隔(秒)' }, + { id: 8, configKey: 'api_token', configValue: '********', valueType: 'string', description: '前端API Token' }, + { id: 9, configKey: 'collector_api_port', configValue: '5800', valueType: 'number', description: '采集服务管理API端口' }, + { id: 10, configKey: 'collector_api_key', configValue: '********', valueType: 'string', description: '采集服务间通信API Key' }, +] + +const workshops = [ + { id: 1, name: 'A栋', sortOrder: 1, isEnabled: 1, machineCount: 32 }, + { id: 2, name: 'B栋', sortOrder: 2, isEnabled: 1, machineCount: 28 }, + { id: 3, name: 'C栋', sortOrder: 3, isEnabled: 0, machineCount: 0 }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/sys-config', method: 'get', response: () => ({ code: 0, data: { items: configs } }) }, + { url: '/mock-api/admin/sys-config/update', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/sys-config/reset-token', method: 'post', response: () => ({ code: 0, data: { configKey: 'api_token', newValue: 'eyJhbGciOiJIUzI1NiJ9.new.token' } }) }, + { url: '/mock-api/admin/workshop', method: 'get', response: () => ({ code: 0, data: { items: workshops } }) }, + { url: '/mock-api/admin/workshop', method: 'post', response: () => ({ code: 0, message: 'success', data: { id: 4, name: 'D栋' } }) }, + { url: '/mock-api/admin/workshop/update', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/workshop/delete', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/workshop/toggle', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/change-password', method: 'post', response: ({ body }: any) => { + const data = body + if (data.currentPassword === 'admin123') return { code: 0, message: 'success', data: null } + return { code: 40001, message: '当前密码不正确', data: null } + }}, +] + +export default mock diff --git a/frontend/mock/types.ts b/frontend/mock/types.ts new file mode 100644 index 0000000..d1a8a15 --- /dev/null +++ b/frontend/mock/types.ts @@ -0,0 +1,16 @@ +/** + * Mock 路由类型定义 + * 替代 vite-plugin-mock 的 MockMethod 类型 + */ +export interface MockRequest { + query: Record + body: any + url: string + method: string +} + +export interface MockMethod { + url: string + method?: 'get' | 'post' | 'put' | 'delete' | 'patch' + response?: ((req: MockRequest) => any) | any +} diff --git a/frontend/mock/worker.ts b/frontend/mock/worker.ts new file mode 100644 index 0000000..309a623 --- /dev/null +++ b/frontend/mock/worker.ts @@ -0,0 +1,51 @@ +import type { MockMethod } from './types' + +const workers = [ + { id: 1, code: 'W001', name: '张三', isEnabled: 1, machineCount: 2, machineNames: '西-1.8,西-2.0' }, + { id: 2, code: 'W002', name: '李四', isEnabled: 1, machineCount: 1, machineNames: '西-1.10' }, + { id: 3, code: 'W003', name: '王五', isEnabled: 0, machineCount: 0, machineNames: '-' }, +] + +const mock: MockMethod[] = [ + { url: '/mock-api/admin/worker', method: 'get', response: ({ query }: any) => { + let items = [...workers] + const page = Number(query.page) || 1 + const pageSize = Number(query.pageSize) || 20 + const original = items + if (query.isEnabled !== undefined && query.isEnabled !== '') items = items.filter((w: any) => w.isEnabled === Number(query.isEnabled)) + if (query.keyword) items = items.filter((w: any) => w.name.includes(query.keyword) || w.code.includes(query.keyword)) + const total = original.length + const start = (page - 1) * pageSize + const end = start + pageSize + items = items.slice(start, end) + return { code: 0, data: { items, total, page, pageSize } } + }}, + { url: '/mock-api/admin/worker/detail', method: 'get', response: ({ query }: any) => { + const w = workers.find((w: any) => w.id === Number(query.id)) + return { code: 0, data: w || null } + }}, + { url: '/mock-api/admin/worker', method: 'post', response: () => ({ code: 0, message: 'success', data: { id: 4, name: '赵六' } }) }, + { url: '/mock-api/admin/worker/update', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/worker/delete', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/worker/batch-status', method: 'post', response: () => ({ code: 0, message: 'success', data: null }) }, + { url: '/mock-api/admin/worker/machines', method: 'get', response: () => ({ code: 0, data: { items: [ + { machineId: 1, machineName: '西-1.8', deviceCode: 'fanake_1.8', workshopName: 'A栋', brandName: 'FANUC', isOnline: 1, programName: '1566.NC' }, + { machineId: 2, machineName: '西-2.0', deviceCode: 'fanake_2.0', workshopName: 'A栋', brandName: 'FANUC', isOnline: 0, programName: null }, + ] } }) }, + { url: '/mock-api/admin/worker/production/today', method: 'get', response: () => ({ code: 0, data: { items: [ + { machineName: '西-1.8', programName: '1566.NC', quantity: 580, runTime: '4h20m', cuttingTime: '3h50m' }, + { machineName: '西-2.0', programName: '-', quantity: null, runTime: '-', cuttingTime: '-' }, + ] } }) }, + { url: '/mock-api/admin/worker/production/trend', method: 'get', response: () => ({ code: 0, data: { items: [ + { date: '2026-04-19', quantity: 980 }, { date: '2026-04-20', quantity: 920 }, + { date: '2026-04-21', quantity: 1100 }, { date: '2026-04-22', quantity: 1050 }, + { date: '2026-04-23', quantity: 990 }, { date: '2026-04-24', quantity: 1080 }, + { date: '2026-04-25', quantity: 580 }, + ] } }) }, + { url: '/mock-api/admin/worker/available-machines', method: 'get', response: () => ({ code: 0, data: { items: [ + { id: 5, name: '东-2.5', deviceCode: 'siemens_2.5', workshopName: 'B栋' }, + { id: 8, name: '北-4.1', deviceCode: 'fanake_4.1', workshopName: 'C栋' }, + ] } }) }, +] + +export default mock