404 lines
16 KiB
JavaScript
404 lines
16 KiB
JavaScript
/**
|
|
* 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;
|
|
})();
|