<script>
function processFile() {
const fileInput = document.getElementById("fileInput");
if (!fileInput.files.length) {
alert("请上传 Excel 文件!");
return;
}
const reader = new FileReader();
reader.onload = function (e) {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: "array" });
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const jsonData = XLSX.utils.sheet_to_json(sheet, { header: 1 });
if (jsonData.length < 2) {
alert("Excel 文件数据格式不正确!");
return;
}
const formattedData = jsonData.slice(1).map(row => [
row[0],
Number(row[1]),
formatExcelDate(row[2]) // 转换Excel日期
]);
const result = splitProduction(formattedData);
displayResult(result);
};
reader.readAsArrayBuffer(fileInput.files[0]);
}
function splitProduction(data) {
const groupedByDate = {};
data.forEach(row => {
const [productId, quantity, date] = row;
if (!groupedByDate[date]) groupedByDate[date] = [];
groupedByDate[date].push({ productId, quantity });
});
const result = {};
Object.keys(groupedByDate).forEach(date => {
const products = groupedByDate[date].sort((a, b) => b.quantity - a.quantity);
let A = [], B = [];
let sumA = 0, sumB = 0;
products.forEach(item => {
if (sumA <= sumB) {
A.push(item);
sumA += item.quantity;
} else {
B.push(item);
sumB += item.quantity;
}
});
result[date] = { A, B, sumA, sumB, typesA: new Set(A.map(p => p.productId)).size, typesB: new Set(B.map(p => p.productId)).size };
});
return result;
}
function displayResult(data) {
let output = document.getElementById("output");
output.innerHTML = "";
Object.keys(data).forEach(date => {
let summary = `<h3>排产日期:${date}</h3>
<p class="summary">A 班:总数量 ${data[date].sumA},种类数 ${data[date].typesA} | B 班:总数量 ${data[date].sumB},种类数 ${data[date].typesB}</p>`;
let table = `<table>
<tr>
<th>A 班 - 产品编号</th>
<th>A 班 - 数量</th>
<th>B 班 - 产品编号</th>
<th>B 班 - 数量</th>
</tr>`;
const maxLength = Math.max(data[date].A.length, data[date].B.length);
for (let i = 0; i < maxLength; i++) {
const Aitem = data[date].A[i] || { productId: "", quantity: "" };
const Bitem = data[date].B[i] || { productId: "", quantity: "" };
table += `<tr>
<td>${Aitem.productId}</td>
<td>${Aitem.quantity}</td>
<td>${Bitem.productId}</td>
<td>${Bitem.quantity}</td>
</tr>`;
}
table += `</table>`;
output.innerHTML += summary + table;
});
}
function formatExcelDate(excelSerial) {
if (typeof excelSerial === "number") {
const date = new Date((excelSerial - 25569) * 86400 * 1000);
return date.toISOString().split("T")[0]; // 转换为 YYYY-MM-DD 格式
}
return excelSerial; // 非数字,可能已经是日期字符串
}
</script>
- 主要功能
这是一个生产排班系统,能够:
读取Excel文件中的生产数据
按日期对产品进行分组
将每个日期的生产任务智能分配到A班和B班
尽量平衡两班的工作量(按产品数量)
显示分配结果
- 核心函数解析
processFile() - 文件处理入口
获取用户上传的Excel文件
使用FileReader读取文件内容
调用XLSX库解析Excel数据
转换数据格式并调用分配逻辑
splitProduction(data) - 核心分配逻辑
按日期分组:groupedByDate
对每个日期的产品按数量降序排序
使用贪心算法分配:
始终将当前产品分配给总数量较少的班次
保持两班总数量尽可能平衡
displayResult(data) - 结果展示
为每个日期创建表格显示:
A班和B班各自的产品列表
两班的总数量对比
产品种类数统计
formatExcelDate() - 日期格式转换
将Excel的序列日期值(如44197)转换为"YYYY-MM-DD"格式
Excel日期是从1900年1月1日开始的天数 - 数据结构
输入数据格式:[[产品ID, 数量, 日期], ...]
分组后结构:{ 日期: [{productId, quantity}, ...] }
输出结果结构:{ 日期: { A:[], B:[], sumA, sumB, typesA, typesB } } - 算法特点
使用贪心算法实现近似最优分配
先排序再分配,确保大任务优先处理
平衡两班总工作量,同时统计产品种类 使用说明
用户上传包含生产数据的Excel文件
系统自动处理并显示分配结果
结果展示包括:
按日期分组的排班表
两班工作量对比
详细的产品分配情况
这个系统适合生产制造企业用于日常生产任务分配,能有效平衡各班次的工作负荷,提高排班效率。
核心分配逻辑function splitProduction(data) { 定义名为 splitProduction 的函数,接收 data 参数(二维数组,格式为 [[产品ID, 数量, 日期], ...]) const groupedByDate = {}; 创建空对象 groupedByDate,用于按日期分组存储数据 javascript Copy Code data.forEach(row => { 遍历 data 数组中的每个元素 row const [productId, quantity, date] = row; 解构数组元素,将每行数据分解为 productId(产品ID)、quantity(数量)、date(日期) if (!groupedByDate[date]) groupedByDate[date] = []; 如果当前日期不存在于 groupedByDate 中,则创建空数组 groupedByDate[date].push({ productId, quantity }); 将当前行的产品ID和数量以对象形式存入对应日期的数组 }); 结束 data.forEach const result = {}; 创建空对象 result,用于存储最终结果 Object.keys(groupedByDate).forEach(date => { 遍历 groupedByDate 的所有日期键 const products = groupedByDate[date].sort((a, b) => b.quantity - a.quantity); 将当前日期的产品按数量降序排序(从大到小) let A = [], B = []; 初始化分组数组 A 和 B let sumA = 0, sumB = 0; 初始化分组总数量统计变量 products.forEach(item => { 遍历排序后的产品数组 if (sumA <= sumB) { 比较两组当前总数量,如果 A 组总和不大于 B 组 A.push(item); sumA += item.quantity; 将当前产品加入 A 组,并累加数量到 sumA } else { B.push(item); sumB += item.quantity; 否则加入 B 组,并累加数量到 sumB } }); 结束产品遍历 result[date] = { A, B, sumA, sumB, typesA: new Set(A.map(p => p.productId)).size, typesB: new Set(B.map(p => p.productId)).size }; 将结果存入 result 对象,包含: 分组数组 A/B 分组总数量 sumA/sumB 使用 Set 计算每组中不同产品ID的数量(去重后的种类数) }); 结束日期遍历 return result; } 返回最终结果对象
主要功能:将每日的生产数据按数量降序排列后,通过贪心算法将产品分配到 A/B 两个组,尽可能平衡两组的总数量,同时统计每组的产品种类数量。返回按日期分组的结果。
算法特点:优先分配大数量产品到当前总和较小的组,可有效减少分组后的数量差异,但不一定是最优解。时间复杂度为 O(n log n)(主要来自排序操作)。
评论 (0)