/** * APS 测试数据与状态持久化 */ const apsData = (function () { const STORAGE_KEY = 'aps_frontend_data_v1'; // ---------- ID 生成 ---------- let idCounters = {}; function nextId(type) { idCounters[type] = (idCounters[type] || 0) + 1; return idCounters[type]; } function resetCounters(data) { idCounters = {}; const scan = (arr, type) => { if (!Array.isArray(arr)) return; arr.forEach(item => { if (item && item.id) idCounters[type] = Math.max(idCounters[type] || 0, Number(item.id) || 0); }); }; scan(data.factories, 'factory'); scan(data.workshops, 'workshop'); scan(data.lines, 'line'); scan(data.workstations, 'workstation'); scan(data.operations, 'operation'); scan(data.routings, 'routing'); scan(data.materials, 'material'); scan(data.boms, 'bom'); scan(data.equipment, 'equipment'); scan(data.teams, 'team'); scan(data.shifts, 'shift'); scan(data.salesOrders, 'salesOrder'); scan(data.productionOrders, 'productionOrder'); scan(data.workOrders, 'workOrder'); scan(data.scheduleVersions, 'scheduleVersion'); scan(data.conflicts, 'conflict'); scan(data.logs, 'log'); } // ---------- 日期工具 ---------- function fmtDate(d) { if (!d) return ''; const dt = new Date(d); const y = dt.getFullYear(); const m = String(dt.getMonth() + 1).padStart(2, '0'); const day = String(dt.getDate()).padStart(2, '0'); return `${y}-${m}-${day}`; } function fmtTime(d) { if (!d) return ''; const dt = new Date(d); return String(dt.getHours()).padStart(2, '0') + ':' + String(dt.getMinutes()).padStart(2, '0'); } function fmtDateTime(d) { if (!d) return ''; return fmtDate(d) + ' ' + fmtTime(d); } function parseDate(s) { if (!s) return null; if (s instanceof Date) return s; return new Date(s.replace(' ', 'T')); } function addMinutes(d, minutes) { return new Date(new Date(d).getTime() + minutes * 60000); } function today() { const d = new Date(); d.setHours(0, 0, 0, 0); return d; } function dayStart(d) { const dt = new Date(d); dt.setHours(0, 0, 0, 0); return dt; } // ---------- 种子数据 ---------- function seed() { const data = { factories: [], workshops: [], lines: [], workstations: [], operations: [], routings: [], routingSteps: [], materials: [], boms: [], bomItems: [], lineProducts: [], workstationOperations: [], equipment: [], teams: [], shifts: [], shiftCalendar: [], maintenance: [], salesOrders: [], productionOrders: [], workOrders: [], scheduleVersions: [], conflicts: [], logs: [], scheduleParams: { defaultEngine: 'HYBRID', planningHorizonDays: 30, timeGranularityMinutes: 15, deliveryBufferRatio: 0.95, weights: { tardiness: 0.4, cost: 0.3, utilization: 0.2, balance: 0.1 }, constraints: { materialKit: true, equipment: true, personnel: true, changeover: true }, rushStrategy: 'STRATEGY_SHIFT', freezeWindowHours: 24 } }; // 工厂 data.factories.push({ id: 1, code: 'F001', name: '青岛工厂', timezone: 'Asia/Shanghai', address: '青岛市高新区', status: 'ACTIVE' }); // 车间 data.workshops.push({ id: 1, factoryId: 1, code: 'WS01', name: '一车间', status: 'ACTIVE' }); data.workshops.push({ id: 2, factoryId: 1, code: 'WS02', name: '二车间', status: 'ACTIVE' }); // 产线 data.lines.push({ id: 1, workshopId: 1, code: 'L001', name: '装配产线A', capacityPerDay: 1000, taktTime: 0.5, efficiencyFactor: 1.0, status: 'ACTIVE', alternativeLineIds: [3] }); data.lines.push({ id: 2, workshopId: 1, code: 'L002', name: '装配产线B', capacityPerDay: 800, taktTime: 0.6, efficiencyFactor: 0.95, status: 'ACTIVE', alternativeLineIds: [1] }); data.lines.push({ id: 3, workshopId: 2, code: 'L003', name: '装配产线C', capacityPerDay: 600, taktTime: 0.8, efficiencyFactor: 0.9, status: 'ACTIVE', alternativeLineIds: [1] }); // 工位 [ [1, 'WS001', 'SMT-01', 1], [1, 'WS002', 'DIP-01', 2], [1, 'WS003', 'ASSY-01', 3], [1, 'WS004', 'TEST-01', 4], [1, 'WS005', 'PKG-01', 5], [2, 'WS006', 'SMT-02', 1], [2, 'WS007', 'ASSY-02', 2], [2, 'WS008', 'TEST-02', 3], [2, 'WS009', 'PKG-02', 4], [3, 'WS010', 'SMT-03', 1], [3, 'WS011', 'DIP-03', 2], [3, 'WS012', 'TEST-03', 3], [3, 'WS013', 'PKG-03', 4] ].forEach((x, i) => { data.workstations.push({ id: i + 1, lineId: x[0], code: x[1], name: x[2], sequenceNo: x[3], status: 'ACTIVE' }); }); // 工序 data.operations.push( { id: 1, code: 'OP10', name: 'SMT贴片', type: 'INTERNAL', standardTime: 0.5 }, { id: 2, code: 'OP20', name: 'DIP插件', type: 'INTERNAL', standardTime: 0.8 }, { id: 3, code: 'OP30', name: '组装', type: 'INTERNAL', standardTime: 1.0 }, { id: 4, code: 'OP40', name: '测试', type: 'INTERNAL', standardTime: 0.6 }, { id: 5, code: 'OP50', name: '包装', type: 'INTERNAL', standardTime: 0.3 } ); // 工艺路线 data.routings.push({ id: 1, productId: 1, version: 'V1.0', versionName: '控制器A工艺V1.0', isDefault: true, status: 'ACTIVE' }); data.routings.push({ id: 2, productId: 2, version: 'V1.0', versionName: '控制器B工艺V1.0', isDefault: true, status: 'ACTIVE' }); data.routings.push({ id: 3, productId: 3, version: 'V1.0', versionName: '控制器C工艺V1.0', isDefault: true, status: 'ACTIVE' }); const steps = [ [1, 1, 1, null, 30, 0.5, 0, 10, false], [1, 2, 2, 1, 30, 0.8, 0, 10, false], [1, 3, 3, 2, 20, 1.0, 0, 10, false], [1, 4, 4, 3, 20, 0.6, 0, 10, false], [1, 5, 5, 4, 10, 0.3, 0, 10, false], [2, 1, 1, null, 30, 0.4, 0, 10, false], [2, 2, 3, 1, 20, 0.9, 0, 10, false], [2, 3, 4, 2, 20, 0.5, 0, 10, false], [2, 4, 5, 3, 10, 0.3, 0, 10, false], [3, 1, 1, null, 30, 0.6, 0, 10, false], [3, 2, 2, 1, 30, 1.0, 0, 10, false], [3, 3, 4, 2, 20, 0.7, 0, 10, false], [3, 4, 5, 3, 10, 0.3, 0, 10, false] ]; steps.forEach((x, i) => { data.routingSteps.push({ id: i + 1, routingId: x[0], operationId: x[1], sequenceNo: x[2], prevStepId: x[3], setupTime: x[4], runTimePerUnit: x[5], waitTime: x[6], transferTime: x[7], isExternal: x[8] }); }); // 物料 data.materials.push( { id: 1, code: 'CTRL-A', name: '智能控制器A型', spec: '标准版', type: 'FINISHED_PRODUCT', unit: '件', safetyStock: 50, procurementLeadTime: 0, stock: 20, inTransit: 0 }, { id: 2, code: 'CTRL-B', name: '智能控制器B型', spec: '经济版', type: 'FINISHED_PRODUCT', unit: '件', safetyStock: 50, procurementLeadTime: 0, stock: 30, inTransit: 0 }, { id: 3, code: 'CTRL-C', name: '智能控制器C型', spec: '高频版', type: 'FINISHED_PRODUCT', unit: '件', safetyStock: 30, procurementLeadTime: 0, stock: 10, inTransit: 0 }, { id: 10, code: 'PCB-001', name: '标准PCB板', spec: 'FR-4', type: 'RAW_MATERIAL', unit: '块', safetyStock: 500, procurementLeadTime: 7, stock: 1800, inTransit: 500 }, { id: 11, code: 'RC-001', name: '电阻电容包', spec: '混合包', type: 'RAW_MATERIAL', unit: '包', safetyStock: 300, procurementLeadTime: 5, stock: 900, inTransit: 300 }, { id: 12, code: 'CASE-001', name: '塑料外壳', spec: '白色', type: 'RAW_MATERIAL', unit: '个', safetyStock: 400, procurementLeadTime: 10, stock: 1200, inTransit: 0 }, { id: 13, code: 'SCR-001', name: '螺丝包', spec: 'M3', type: 'RAW_MATERIAL', unit: '包', safetyStock: 200, procurementLeadTime: 3, stock: 800, inTransit: 0 }, { id: 14, code: 'WIRE-001', name: '线材', spec: '22AWG', type: 'RAW_MATERIAL', unit: '米', safetyStock: 1000, procurementLeadTime: 4, stock: 3500, inTransit: 0 }, { id: 15, code: 'LABEL-001', name: '标签', spec: '防伪', type: 'RAW_MATERIAL', unit: '张', safetyStock: 500, procurementLeadTime: 2, stock: 2500, inTransit: 0 }, { id: 16, code: 'PCB-002', name: '高频PCB板', spec: '高频', type: 'RAW_MATERIAL', unit: '块', safetyStock: 200, procurementLeadTime: 10, stock: 350, inTransit: 200 } ); // BOM data.boms.push({ id: 1, productId: 1, version: 'V2.1', versionName: 'A型BOM V2.1', isDefault: true, status: 'ACTIVE' }); data.boms.push({ id: 2, productId: 2, version: 'V1.0', versionName: 'B型BOM V1.0', isDefault: true, status: 'ACTIVE' }); data.boms.push({ id: 3, productId: 3, version: 'V1.0', versionName: 'C型BOM V1.0', isDefault: true, status: 'ACTIVE' }); const bomItems = [ [1, 10, 1, 1, false], [1, 11, 1, 1, false], [1, 12, 1, 3, false], [1, 13, 1, 3, false], [1, 14, 0.5, 3, false], [1, 15, 1, 5, false], [2, 10, 1, 1, false], [2, 11, 1.2, 1, false], [2, 12, 1, 3, false], [2, 13, 1, 3, false], [2, 14, 0.6, 3, false], [2, 15, 1, 5, false], [3, 16, 1, 1, true], [3, 11, 1.5, 1, false], [3, 12, 1, 3, false], [3, 13, 1, 3, false], [3, 14, 0.4, 3, false], [3, 15, 1, 5, false] ]; bomItems.forEach((x, i) => { data.bomItems.push({ id: i + 1, bomId: x[0], materialId: x[1], quantity: x[2], operationId: x[3], isKeyMaterial: x[4] }); }); // 产线产品配置 data.lineProducts.push( { id: 1, lineId: 1, productId: 1, standardCapacity: 1000, priority: 1, setupTime: 30 }, { id: 2, lineId: 1, productId: 3, standardCapacity: 600, priority: 2, setupTime: 40 }, { id: 3, lineId: 2, productId: 2, standardCapacity: 800, priority: 1, setupTime: 30 }, { id: 4, lineId: 2, productId: 1, standardCapacity: 700, priority: 2, setupTime: 40 }, { id: 5, lineId: 3, productId: 1, standardCapacity: 500, priority: 3, setupTime: 45 }, { id: 6, lineId: 3, productId: 3, standardCapacity: 600, priority: 1, setupTime: 30 } ); // 工位工序配置 const wsOps = [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 1], [7, 3], [8, 4], [9, 5], [10, 1], [11, 2], [12, 4], [13, 5] ]; wsOps.forEach((x, i) => { data.workstationOperations.push({ id: i + 1, workstationId: x[0], operationId: x[1], setupTime: 20, runTimePerUnit: 0.5, isPrimary: true }); }); // 设备 data.workstations.forEach((ws, i) => { data.equipment.push({ id: i + 1, code: 'EQ' + String(i + 1).padStart(3, '0'), name: ws.name + '设备', model: 'MDL-' + ws.code, workstationId: ws.id, capacityPerHour: 120, efficiencyFactor: 0.95 + Math.random() * 0.05, availabilityRate: 0.92 + Math.random() * 0.07, status: Math.random() > 0.9 ? 'MAINTENANCE' : 'RUNNING' }); }); // 班组 data.teams.push( { id: 1, code: 'T001', name: '贴片班组A', workshopId: 1, memberCount: 12, skillLevel: 'L3', status: 'ACTIVE' }, { id: 2, code: 'T002', name: '插件班组A', workshopId: 1, memberCount: 8, skillLevel: 'L2', status: 'ACTIVE' }, { id: 3, code: 'T003', name: '组装班组A', workshopId: 1, memberCount: 15, skillLevel: 'L3', status: 'ACTIVE' }, { id: 4, code: 'T004', name: '测试班组A', workshopId: 1, memberCount: 10, skillLevel: 'L4', status: 'ACTIVE' }, { id: 5, code: 'T005', name: '包装班组A', workshopId: 1, memberCount: 8, skillLevel: 'L2', status: 'ACTIVE' }, { id: 6, code: 'T006', name: '综合班组B', workshopId: 1, memberCount: 20, skillLevel: 'L3', status: 'ACTIVE' } ); // 班次 data.shifts.push( { id: 1, code: 'D', name: '早班', startTime: '08:00', endTime: '16:00', breakPeriods: [{ start: '12:00', end: '13:00' }], isOvertime: false, status: 'ACTIVE' }, { id: 2, code: 'N', name: '中班', startTime: '16:00', endTime: '24:00', breakPeriods: [{ start: '18:00', end: '18:30' }], isOvertime: false, status: 'ACTIVE' } ); // 班次日历 (未来14天) const base = today(); for (let i = 0; i < 14; i++) { const date = new Date(base); date.setDate(date.getDate() + i); const dateStr = fmtDate(date); const isWeekend = date.getDay() === 0 || date.getDay() === 6; data.lines.forEach(line => { data.shifts.forEach(shift => { data.shiftCalendar.push({ id: data.shiftCalendar.length + 1, lineId: line.id, date: dateStr, shiftId: shift.id, isWorking: !isWeekend, teamId: line.id === 2 ? 6 : (shift.id === 1 ? [1, 2, 3, 4, 5][line.id - 1] || 1 : 6), maxWorkers: 10 }); }); }); } // 维保计划 data.maintenance.push({ id: 1, equipmentId: 3, type: 'MAINTENANCE', plannedStart: fmtDate(addMinutes(base, 2 * 24 * 60 + 8 * 60)) + ' 09:00', plannedEnd: fmtDate(addMinutes(base, 2 * 24 * 60 + 8 * 60)) + ' 12:00', status: 'PLANNED', description: 'ASSY-01 季度保养' }); // 销售订单 const orderSeed = [ ['SO20260714001', '青岛科技', 'A', '2026-07-14', '2026-07-25', 3, 'CONFIRMED', false, 1, 500], ['SO20260714002', '海尔智家', 'VIP', '2026-07-13', '2026-07-22', 1, 'CONFIRMED', false, 2, 300], ['SO20260714003', '海信电子', 'B', '2026-07-14', '2026-07-24', 5, 'CONFIRMED', false, 1, 800], ['SO20260714004', '中车集团', 'A', '2026-07-12', '2026-07-28', 4, 'CONFIRMED', false, 3, 400], ['SO20260714005', '北汽新能源', 'C', '2026-07-15', '2026-07-30', 8, 'SUBMITTED', false, 2, 600], ['SO20260714006', '比亚迪', 'VIP', '2026-07-15', '2026-07-20', 1, 'CONFIRMED', true, 3, 200], ['SO20260714007', '青岛科技', 'A', '2026-07-16', '2026-07-26', 3, 'CONFIRMED', false, 2, 500] ]; orderSeed.forEach((x, i) => { const pid = x[8]; const product = data.materials.find(m => m.id === pid); data.salesOrders.push({ id: i + 1, orderNo: x[0], customerId: 'CUST' + String(i + 1).padStart(3, '0'), customerName: x[1], customerLevel: x[2], orderDate: x[3], deliveryDate: x[4], priority: x[5], manualPriority: null, status: x[6], source: i % 2 === 0 ? 'MANUAL' : 'API', specialRequirements: i === 0 ? '需使用环保材料包装' : '', totalAmount: x[9] * (100 + Math.floor(Math.random() * 200)), isRush: x[7], rushStrategy: x[7] ? 'STRATEGY_SHIFT' : null, changes: [], createdBy: 'admin', createdAt: x[3] + ' 09:00', updatedAt: x[3] + ' 09:00', items: [{ id: i * 10 + 1, orderId: i + 1, lineNo: 1, productId: pid, productName: product.name, productCode: product.code, quantity: x[9], unit: '件', bomVersion: 'V2.1', routingVersion: 'V1.0', status: 'PENDING', note: '' }] }); }); // 日志 data.logs.push( { id: 1, type: 'INFO', category: 'SCHEDULE', operationType: 'SYSTEM', targetType: 'SCHEDULE', targetId: 1, action: '系统初始化', description: '测试数据加载完成', operator: 'system', createdAt: fmtDateTime(new Date()) }, { id: 2, type: 'INFO', category: 'ORDER', operationType: 'CREATE', targetType: 'SALES_ORDER', targetId: 1, action: '创建销售订单', description: '订单 SO20260714001 已创建', operator: 'admin', createdAt: '2026-07-14 09:05' } ); resetCounters(data); return data; } // ---------- localStorage 持久化 ---------- function load() { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch (e) {} return null; } function save(data) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (e) {} } let data = load(); if (!data) { data = seed(); save(data); } resetCounters(data); // 兼容旧数据:补全变更历史字段 (data.salesOrders || []).forEach(o => { if (!o.changes) o.changes = []; }); // 暴露工具方法 data.$utils = { nextId, fmtDate, fmtTime, fmtDateTime, parseDate, addMinutes, today, dayStart, save }; return data; })();