feat: 规格体系统一化阶段四设备端 + 阶段五辅助页改造
- 设备端 9 任务 14 文件:毛菜验收/加工秤、净菜组配/包装秤、净菜加工秤设置统一为克存储 + formatGram 展示层兼容 + cutSpec/packSpec 结构化 - 辅助页 3 任务 6 文件:质控同步真落地 + 菜品下拉跨页共享 + 同步日志驱动 + 分析页字典语义对齐 + 净菜进柜 spec 多形态结构化 - 新增 spec-utils-reference.md 权威源、改造任务清单与研发同步说明 - 删除旧的部门下周工作计划文档 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
2b8a35d5a3
commit
4fd44f1005
@@ -228,6 +228,80 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
/* 规格标签格式化:{baseGram:500, unit:'袋'} → "500g/袋" */
|
||||
function formatSpec(spec) {
|
||||
if (!spec) return '—';
|
||||
if (typeof spec === 'string') return spec;
|
||||
if (!spec.baseGram) return spec.unit || '—';
|
||||
var w = formatGram(spec.baseGram);
|
||||
if (spec.unit === '散装') return '散装/' + w;
|
||||
return w + '/' + (spec.unit || '');
|
||||
}
|
||||
/* 切配规格展示:{length,width,height,shape,technique} → "20×1×1mm · 丝 · 平刀" */
|
||||
function formatCutSpec(cut) {
|
||||
if (!cut) return '—';
|
||||
if (typeof cut === 'string') return cut;
|
||||
var dims = [cut.length, cut.width, cut.height].filter(function(v){
|
||||
return v !== undefined && v !== null && v !== '' && !isNaN(v);
|
||||
});
|
||||
var dimStr = dims.length ? dims.join('×') + 'mm · ' : '';
|
||||
var shapeStr = cut.shape || '';
|
||||
var techStr = cut.technique ? ' · ' + cut.technique : '';
|
||||
return dimStr + shapeStr + techStr;
|
||||
}
|
||||
/* 历史切配规格字符串解析(兼容 4 种格式)
|
||||
* "3.5mm*3.5mm*50mm·细丝·平刀" (无空格,* 分隔)
|
||||
* "20mm*1mm*1mm · 细丝 · 平刀" (带空格,* 分隔)
|
||||
* "20mm×1mm×1mm · 细丝 · 平刀" (带空格,× 分隔)
|
||||
* "50mm*30mm*30mm · 宽段 · 平刀" (ratio-scale 同 02-labeling) */
|
||||
function parseLegacyCutSpec(s) {
|
||||
if (!s) return null;
|
||||
if (typeof s === 'object') return s;
|
||||
var normalized = String(s).replace(/×/g, '*').replace(/\s/g, '');
|
||||
var m = normalized.match(/^(?:(\d+(?:\.\d+)?)mm\*(\d+(?:\.\d+)?)mm\*(\d+(?:\.\d+)?)mm)?·?([^·]+)·?([^·]+)?$/);
|
||||
if (!m) return { shape: s };
|
||||
return {
|
||||
length: m[1] ? parseFloat(m[1]) : undefined,
|
||||
width: m[2] ? parseFloat(m[2]) : undefined,
|
||||
height: m[3] ? parseFloat(m[3]) : undefined,
|
||||
shape: m[4] || '',
|
||||
technique: m[5] || ''
|
||||
};
|
||||
}
|
||||
/* 历史包装规格字符串解析:'500g' / '大份800g' / '真空包装'
|
||||
* 返回 {baseGram, unit, packMethod, portion?} */
|
||||
function parseLegacyPackSpec(s) {
|
||||
if (!s) return { baseGram: 0, unit: '', packMethod: '' };
|
||||
if (typeof s === 'object') return s;
|
||||
var str = String(s).trim();
|
||||
/* 纯描述(无数字):如"真空包装"、"盒装" → 视为 packMethod */
|
||||
if (!/\d/.test(str)) {
|
||||
return { baseGram: 0, unit: '', packMethod: str };
|
||||
}
|
||||
/* 提取份型前缀(可选)+ 克数 */
|
||||
var m = str.match(/^(?:(标准份|小份|大份|迷你份|加大份))?(?:(\d+(?:\.\d+)?)\s*(g|kg))?\/?(袋|盒|箱|盘|份|筐|瓶|罐|桶|散装)?$/);
|
||||
if (!m) return { baseGram: 0, unit: '', packMethod: str };
|
||||
var baseGram = 0;
|
||||
if (m[2]) {
|
||||
baseGram = parseFloat(m[2]);
|
||||
if (m[3] === 'kg') baseGram *= 1000;
|
||||
}
|
||||
return {
|
||||
baseGram: baseGram,
|
||||
unit: m[4] || '',
|
||||
portion: m[1] || '',
|
||||
packMethod: ''
|
||||
};
|
||||
}
|
||||
|
||||
/* 获取 URL 参数 */
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -245,7 +319,12 @@
|
||||
/* 当前 Tab */
|
||||
var currentFilter = 'pending';
|
||||
|
||||
/* 模拟数据:待打印订单(来自配比秤已完成配比的订单) */
|
||||
/* 模拟数据:待打印订单(来自配比秤已完成配比的订单)
|
||||
* 字段说明:
|
||||
* cutSpec: 结构化对象 {length,width,height,shape,technique}(任务 4.4)
|
||||
* packSpec: 结构化对象 {baseGram, unit, portion?, packMethod?}(任务 4.4)
|
||||
* packMethod: 独立字段"真空袋/保鲜盒/散装托盘"等(任务 4.4)
|
||||
*/
|
||||
var mockData = [
|
||||
{
|
||||
orderNo: 'PO-20260625-001',
|
||||
@@ -254,9 +333,9 @@
|
||||
taskNo: 'L-001',
|
||||
labeled: false,
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '土豆', packSize: 3, packSpec: '500g', vegCutType: '3.5mm*3.5mm*50mm·细丝·平刀', ingredients: [{ name: '土豆', weightPerPack: 500, weighed: 1500 }] },
|
||||
{ type: '净菜包', packName: '胡萝卜', packSize: 3, packSpec: '500g', vegCutType: '3mm*3mm*30mm·菱形片·斜刀', ingredients: [{ name: '胡萝卜', weightPerPack: 500, weighed: 1500 }] },
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '宫保鸡丁', weightPerPack: 800, weighed: 1600 }] }
|
||||
{ type: '净菜包', packName: '土豆', packSize: 3, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:50, width:3.5, height:3.5, shape:'细丝', technique:'平刀'}, ingredients: [{ name: '土豆', weightPerPack: 500, weighed: 1500 }] },
|
||||
{ type: '净菜包', packName: '胡萝卜', packSize: 3, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:30, width:3, height:3, shape:'菱形片', technique:'斜刀'}, ingredients: [{ name: '胡萝卜', weightPerPack: 500, weighed: 1500 }] },
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: {baseGram:800, unit:'份', portion:'大份'}, ingredients: [{ name: '宫保鸡丁', weightPerPack: 800, weighed: 1600 }] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -266,8 +345,8 @@
|
||||
taskNo: 'L-002',
|
||||
labeled: false,
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: '1000g', vegCutType: '5mm*5mm*50mm·段切·平刀', ingredients: [{ name: '白菜', weightPerPack: 1000, weighed: 2000 }] },
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [{ name: '鱼香肉丝', weightPerPack: 800, weighed: 1600 }] }
|
||||
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: {baseGram:1000, unit:'袋'}, cutSpec: {length:50, width:5, height:5, shape:'段切', technique:'平刀'}, ingredients: [{ name: '白菜', weightPerPack: 1000, weighed: 2000 }] },
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: {baseGram:800, unit:'份', portion:'大份'}, ingredients: [{ name: '鱼香肉丝', weightPerPack: 800, weighed: 1600 }] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -277,12 +356,12 @@
|
||||
taskNo: 'L-003',
|
||||
labeled: false,
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', ingredients: [{ name: '番茄炒蛋', weightPerPack: 500, weighed: 2000 }] }
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: {baseGram:500, unit:'份'}, packMethod: '真空袋', ingredients: [{ name: '番茄炒蛋', weightPerPack: 500, weighed: 2000 }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/* 待发货订单(已包装完成,等待发货) */
|
||||
/* 待发货订单(已包装完成,等待发货) */
|
||||
var mockDoneData = [
|
||||
{
|
||||
orderNo: 'PO-20260624-001',
|
||||
@@ -294,8 +373,8 @@
|
||||
operator: '刘九',
|
||||
shipped: false,
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '黄瓜', packSize: 3, packSpec: '500g', vegCutType: '2mm*2mm*40mm·薄片·平刀', ingredients: [{ name: '黄瓜', weightPerPack: 500, weighed: 1500 }] },
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 2, packSpec: '盒装', ingredients: [{ name: '凉拌黄瓜', weightPerPack: 600, weighed: 1200 }] }
|
||||
{ type: '净菜包', packName: '黄瓜', packSize: 3, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:40, width:2, height:2, shape:'薄片', technique:'平刀'}, ingredients: [{ name: '黄瓜', weightPerPack: 500, weighed: 1500 }] },
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 2, packSpec: {baseGram:600, unit:'盒'}, packMethod: '保鲜盒', ingredients: [{ name: '凉拌黄瓜', weightPerPack: 600, weighed: 1200 }] }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -308,8 +387,8 @@
|
||||
operator: '陈十',
|
||||
shipped: false,
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '芹菜', packSize: 2, packSpec: '500g', vegCutType: '4mm*4mm*30mm·条·平刀', ingredients: [{ name: '芹菜', weightPerPack: 500, weighed: 1000 }] },
|
||||
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
|
||||
{ type: '净菜包', packName: '芹菜', packSize: 2, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:30, width:4, height:4, shape:'条', technique:'平刀'}, ingredients: [{ name: '芹菜', weightPerPack: 500, weighed: 1000 }] },
|
||||
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: {baseGram:800, unit:'份', portion:'大份'}, ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -330,8 +409,8 @@
|
||||
shippingCompany: '顺丰速运',
|
||||
shippingNo: 'SF1234567890',
|
||||
mealPacks: [
|
||||
{ type: '净菜包', packName: '西红柿', packSize: 5, packSpec: '500g', vegCutType: '15mm*15mm*15mm·滚刀块·平刀', ingredients: [{ name: '西红柿', weightPerPack: 500, weighed: 2500 }] },
|
||||
{ type: '餐品净菜包', packName: '番茄牛腩套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '番茄牛腩', weightPerPack: 800, weighed: 2400 }] }
|
||||
{ type: '净菜包', packName: '西红柿', packSize: 5, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:15, width:15, height:15, shape:'滚刀块', technique:'平刀'}, ingredients: [{ name: '西红柿', weightPerPack: 500, weighed: 2500 }] },
|
||||
{ type: '餐品净菜包', packName: '番茄牛腩套餐', packSize: 3, packSpec: {baseGram:800, unit:'份', portion:'大份'}, ingredients: [{ name: '番茄牛腩', weightPerPack: 800, weighed: 2400 }] }
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -425,11 +504,18 @@
|
||||
? { text: '待包装', cls: 'status-tag--wait' }
|
||||
: { text: '已打印', cls: 'status-tag--done' };
|
||||
var ingWeight = pack.ingredients[0] ? pack.ingredients[0].weightPerPack : 0;
|
||||
var specMatch = pack.packSpec && pack.packSpec.match(/(\d+)g/);
|
||||
var perDue = specMatch ? parseInt(specMatch[1]) : ingWeight;
|
||||
/* 解析 packSpec:已是对象直接用,字符串则 parseLegacyPackSpec */
|
||||
var specObj = typeof pack.packSpec === 'object'
|
||||
? pack.packSpec
|
||||
: parseLegacyPackSpec(pack.packSpec);
|
||||
var perDue = specObj.baseGram || ingWeight;
|
||||
var totalDue = perDue * pack.packSize;
|
||||
var totalWeighed = (currentFilter === 'done' || currentFilter === 'shipped') ? totalDue : (pack.weighed || 0);
|
||||
var weightClass = (currentFilter === 'done' || currentFilter === 'shipped') ? 'parent-weight--done' : '';
|
||||
/* 解析 cutSpec:已是对象直接用,字符串则 parseLegacyCutSpec */
|
||||
var cutObj = pack.cutSpec
|
||||
? (typeof pack.cutSpec === 'object' ? pack.cutSpec : parseLegacyCutSpec(pack.cutSpec))
|
||||
: null;
|
||||
|
||||
var parentRow = document.createElement('div');
|
||||
parentRow.className = 'item-parent-row';
|
||||
@@ -448,8 +534,8 @@
|
||||
parentRow.innerHTML =
|
||||
'<div class="parent-name"><div class="parent-icon">📦</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
|
||||
'<div style="text-align:center;font-size:11px;color:' + (pack.type === '餐品净菜包' ? '#faad16' : '#4a90d9') + '">' + pack.type + '</div>' +
|
||||
'<div style="text-align:center;font-size:11px;color:rgba(255,255,255,0.55)">' + (pack.packSpec || '—') + '</div>' +
|
||||
'<div style="text-align:center;font-size:11px;color:rgba(255,255,255,0.55)">' + (pack.vegCutType || '—') + '</div>' +
|
||||
'<div style="text-align:center;font-size:11px;color:rgba(255,255,255,0.55)">' + formatSpec(specObj) + '</div>' +
|
||||
'<div style="text-align:center;font-size:11px;color:rgba(255,255,255,0.55)">' + formatCutSpec(cutObj) + '</div>' +
|
||||
'<div class="parent-weight ' + weightClass + '">' + totalDue + ' g</div>' +
|
||||
'<div class="parent-weight ' + weightClass + '">' + totalWeighed + ' g</div>' +
|
||||
statusCell +
|
||||
|
||||
@@ -348,6 +348,63 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
function formatSpec(spec) {
|
||||
if (!spec) return '—';
|
||||
if (typeof spec === 'string') return spec;
|
||||
if (!spec.baseGram) return spec.unit || '—';
|
||||
var w = formatGram(spec.baseGram);
|
||||
if (spec.unit === '散装') return '散装/' + w;
|
||||
return w + '/' + (spec.unit || '');
|
||||
}
|
||||
function formatCutSpec(cut) {
|
||||
if (!cut) return '—';
|
||||
if (typeof cut === 'string') return cut;
|
||||
var dims = [cut.length, cut.width, cut.height].filter(function(v){
|
||||
return v !== undefined && v !== null && v !== '' && !isNaN(v);
|
||||
});
|
||||
var dimStr = dims.length ? dims.join('×') + 'mm · ' : '';
|
||||
var shapeStr = cut.shape || '';
|
||||
var techStr = cut.technique ? ' · ' + cut.technique : '';
|
||||
return dimStr + shapeStr + techStr;
|
||||
}
|
||||
/* 历史切配规格字符串解析(兼容 4 种格式) */
|
||||
function parseLegacyCutSpec(s) {
|
||||
if (!s) return null;
|
||||
if (typeof s === 'object') return s;
|
||||
var normalized = String(s).replace(/×/g, '*').replace(/\s/g, '');
|
||||
var m = normalized.match(/^(?:(\d+(?:\.\d+)?)mm\*(\d+(?:\.\d+)?)mm\*(\d+(?:\.\d+)?)mm)?·?([^·]+)·?([^·]+)?$/);
|
||||
if (!m) return { shape: s };
|
||||
return {
|
||||
length: m[1] ? parseFloat(m[1]) : undefined,
|
||||
width: m[2] ? parseFloat(m[2]) : undefined,
|
||||
height: m[3] ? parseFloat(m[3]) : undefined,
|
||||
shape: m[4] || '',
|
||||
technique: m[5] || ''
|
||||
};
|
||||
}
|
||||
function parseLegacyPackSpec(s) {
|
||||
if (!s) return { baseGram: 0, unit: '', packMethod: '' };
|
||||
if (typeof s === 'object') return s;
|
||||
var str = String(s).trim();
|
||||
if (!/\d/.test(str)) return { baseGram: 0, unit: '', packMethod: str };
|
||||
var m = str.match(/^(?:(标准份|小份|大份|迷你份|加大份))?(?:(\d+(?:\.\d+)?)\s*(g|kg))?\/?(袋|盒|箱|盘|份|筐|瓶|罐|桶|散装)?$/);
|
||||
if (!m) return { baseGram: 0, unit: '', packMethod: str };
|
||||
var baseGram = 0;
|
||||
if (m[2]) {
|
||||
baseGram = parseFloat(m[2]);
|
||||
if (m[3] === 'kg') baseGram *= 1000;
|
||||
}
|
||||
return { baseGram: baseGram, unit: m[4] || '', portion: m[1] || '', packMethod: '' };
|
||||
}
|
||||
|
||||
/* ---- 工具函数 ---- */
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -363,30 +420,35 @@
|
||||
document.getElementById('topUser').textContent = userName;
|
||||
document.getElementById('userAvatar').textContent = userName.charAt(0);
|
||||
|
||||
/* ---- 模拟订单数据(来自配比秤已完成配比的订单) ---- */
|
||||
/* ---- 模拟订单数据(来自配比秤已完成配比的订单) ----
|
||||
* 字段说明:
|
||||
* cutSpec: 结构化对象 {length,width,height,shape,technique}(任务 4.4)
|
||||
* packSpec: 结构化对象 {baseGram, unit, portion?, packMethod?}(任务 4.4)
|
||||
* packMethod: 包装方式(独立字段,"真空袋/保鲜盒/散装托盘")
|
||||
*/
|
||||
var taskData = {
|
||||
'L-001': {
|
||||
orderNo: 'PO-20260625-001',
|
||||
orderType: 'internal',
|
||||
items: [
|
||||
{ type: '净菜包', packName: '土豆', packSize: 3, packSpec: '500g', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 500, weighed: 1500, icon: '🥔' },
|
||||
{ type: '净菜包', packName: '胡萝卜', packSize: 3, packSpec: '500g', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 500, weighed: 1500, icon: '🥕' },
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: '大份800g', weightPerPack: 800, weighed: 1600, icon: '🍱' }
|
||||
{ type: '净菜包', packName: '土豆', packSize: 3, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, weightPerPack: 500, weighed: 1500, icon: '🥔' },
|
||||
{ type: '净菜包', packName: '胡萝卜', packSize: 3, packSpec: {baseGram:500, unit:'袋'}, cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, weightPerPack: 500, weighed: 1500, icon: '🥕' },
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 2, packSpec: {baseGram:800, unit:'份', portion:'大份'}, weightPerPack: 800, weighed: 1600, icon: '🍱' }
|
||||
]
|
||||
},
|
||||
'L-002': {
|
||||
orderNo: 'PO-20260625-002',
|
||||
orderType: 'external',
|
||||
items: [
|
||||
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: '1000g', cutSpec: '50mm*30mm*30mm · 宽段 · 平刀', weightPerPack: 1000, weighed: 2000, icon: '🥬' },
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', weightPerPack: 800, weighed: 1600, icon: '🍱' }
|
||||
{ type: '净菜包', packName: '白菜', packSize: 2, packSpec: {baseGram:1000, unit:'袋'}, cutSpec: {length:50, width:30, height:30, shape:'宽段', technique:'平刀'}, weightPerPack: 1000, weighed: 2000, icon: '🥬' },
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: {baseGram:800, unit:'份', portion:'大份'}, weightPerPack: 800, weighed: 1600, icon: '🍱' }
|
||||
]
|
||||
},
|
||||
'L-003': {
|
||||
orderNo: 'PO-20260625-003',
|
||||
orderType: 'internal',
|
||||
items: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '真空包装', weightPerPack: 500, weighed: 2000, icon: '🍱' }
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: {baseGram:500, unit:'份'}, packMethod: '真空袋', weightPerPack: 500, weighed: 2000, icon: '🍱' }
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -502,8 +564,8 @@
|
||||
'<div class="item-row__action">' + actionHtml + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="item-card__meta">' +
|
||||
'<div class="item-card__meta-item"><span class="item-card__meta-label">包装规格:</span><span class="item-card__meta-value">' + (item.packSpec || '—') + '</span></div>' +
|
||||
'<div class="item-card__meta-item"><span class="item-card__meta-label">净菜类型:</span><span class="item-card__meta-value">' + (isMeal ? '—' : (item.cutSpec || '—')) + '</span></div>' +
|
||||
'<div class="item-card__meta-item"><span class="item-card__meta-label">包装规格:</span><span class="item-card__meta-value">' + formatSpec(typeof item.packSpec === 'object' ? item.packSpec : parseLegacyPackSpec(item.packSpec)) + '</span></div>' +
|
||||
'<div class="item-card__meta-item"><span class="item-card__meta-label">净菜类型:</span><span class="item-card__meta-value">' + (isMeal ? '—' : formatCutSpec(typeof item.cutSpec === 'object' ? item.cutSpec : parseLegacyCutSpec(item.cutSpec))) + '</span></div>' +
|
||||
'</div>' +
|
||||
'<div class="item-card__data">' +
|
||||
'<div class="item-card__data-item"><span class="item-card__data-label">应称重量:</span><span class="item-card__data-value">' + totalDue + 'g</span></div>' +
|
||||
@@ -568,7 +630,7 @@
|
||||
list.innerHTML = '';
|
||||
empty.classList.add('hide');
|
||||
|
||||
/* 构建净菜类型映射 */
|
||||
/* 构建净菜类型映射(结构化 cutSpec → formatCutSpec 渲染) */
|
||||
var cutSpecMap = {};
|
||||
allItems.forEach(function(item) {
|
||||
if (item.cutSpec) cutSpecMap[item.packName] = item.cutSpec;
|
||||
@@ -576,7 +638,8 @@
|
||||
|
||||
selected.forEach(function(key) {
|
||||
var r = aiResults[key];
|
||||
var cutSpec = cutSpecMap[r.name] || '';
|
||||
var rawCutSpec = cutSpecMap[r.name];
|
||||
var cutSpec = rawCutSpec ? formatCutSpec(typeof rawCutSpec === 'object' ? rawCutSpec : parseLegacyCutSpec(rawCutSpec)) : '';
|
||||
var card = document.createElement('div');
|
||||
card.className = 'candidate-card';
|
||||
card.onclick = function() { confirmCandidateDirect(r); };
|
||||
@@ -702,14 +765,15 @@
|
||||
title.textContent = '搜索结果 (' + results.length + ')';
|
||||
list.innerHTML = '';
|
||||
empty.classList.add('hide');
|
||||
/* 构建净菜类型映射 */
|
||||
/* 构建净菜类型映射(结构化 cutSpec → formatCutSpec 渲染) */
|
||||
var cutSpecMap2 = {};
|
||||
allItems.forEach(function(item) {
|
||||
if (item.cutSpec) cutSpecMap2[item.packName] = item.cutSpec;
|
||||
});
|
||||
|
||||
results.forEach(function(r) {
|
||||
var cutSpec2 = cutSpecMap2[r.name] || '';
|
||||
var rawCutSpec2 = cutSpecMap2[r.name];
|
||||
var cutSpec2 = rawCutSpec2 ? formatCutSpec(typeof rawCutSpec2 === 'object' ? rawCutSpec2 : parseLegacyCutSpec(rawCutSpec2)) : '';
|
||||
var card = document.createElement('div');
|
||||
card.className = 'candidate-card';
|
||||
card.onclick = function() { confirmCandidateDirect(r); };
|
||||
|
||||
@@ -172,6 +172,62 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
function formatSpec(spec) {
|
||||
if (!spec) return '—';
|
||||
if (typeof spec === 'string') return spec;
|
||||
if (!spec.baseGram) return spec.unit || '—';
|
||||
var w = formatGram(spec.baseGram);
|
||||
if (spec.unit === '散装') return '散装/' + w;
|
||||
return w + '/' + (spec.unit || '');
|
||||
}
|
||||
function formatCutSpec(cut) {
|
||||
if (!cut) return '—';
|
||||
if (typeof cut === 'string') return cut;
|
||||
var dims = [cut.length, cut.width, cut.height].filter(function(v){
|
||||
return v !== undefined && v !== null && v !== '' && !isNaN(v);
|
||||
});
|
||||
var dimStr = dims.length ? dims.join('×') + 'mm · ' : '';
|
||||
var shapeStr = cut.shape || '';
|
||||
var techStr = cut.technique ? ' · ' + cut.technique : '';
|
||||
return dimStr + shapeStr + techStr;
|
||||
}
|
||||
function parseLegacyCutSpec(s) {
|
||||
if (!s) return null;
|
||||
if (typeof s === 'object') return s;
|
||||
var normalized = String(s).replace(/×/g, '*').replace(/\s/g, '');
|
||||
var m = normalized.match(/^(?:(\d+(?:\.\d+)?)mm\*(\d+(?:\.\d+)?)mm\*(\d+(?:\.\d+)?)mm)?·?([^·]+)·?([^·]+)?$/);
|
||||
if (!m) return { shape: s };
|
||||
return {
|
||||
length: m[1] ? parseFloat(m[1]) : undefined,
|
||||
width: m[2] ? parseFloat(m[2]) : undefined,
|
||||
height: m[3] ? parseFloat(m[3]) : undefined,
|
||||
shape: m[4] || '',
|
||||
technique: m[5] || ''
|
||||
};
|
||||
}
|
||||
function parseLegacyPackSpec(s) {
|
||||
if (!s) return { baseGram: 0, unit: '', packMethod: '' };
|
||||
if (typeof s === 'object') return s;
|
||||
var str = String(s).trim();
|
||||
if (!/\d/.test(str)) return { baseGram: 0, unit: '', packMethod: str };
|
||||
var m = str.match(/^(?:(标准份|小份|大份|迷你份|加大份))?(?:(\d+(?:\.\d+)?)\s*(g|kg))?\/?(袋|盒|箱|盘|份|筐|瓶|罐|桶|散装)?$/);
|
||||
if (!m) return { baseGram: 0, unit: '', packMethod: str };
|
||||
var baseGram = 0;
|
||||
if (m[2]) {
|
||||
baseGram = parseFloat(m[2]);
|
||||
if (m[3] === 'kg') baseGram *= 1000;
|
||||
}
|
||||
return { baseGram: baseGram, unit: m[4] || '', portion: m[1] || '', packMethod: '' };
|
||||
}
|
||||
|
||||
/* ---- 工具函数 ---- */
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -185,33 +241,39 @@
|
||||
document.getElementById('topUser').textContent = userName;
|
||||
document.getElementById('userAvatar').textContent = userName.charAt(0);
|
||||
|
||||
/* ---- Mock 数据 ---- */
|
||||
/* ---- Mock 数据 ----
|
||||
* 字段说明:
|
||||
* cutSpec: 结构化对象 {length,width,height,shape,technique}(任务 4.4)
|
||||
* packSpec: 结构化对象 {baseGram, unit, portion?}(任务 4.4)
|
||||
* packSize: 包装份数(原字段 portions 重命名,任务 4.4)
|
||||
*/
|
||||
var mockData = {
|
||||
orderNo: 'PO-20260625-001',
|
||||
orderType: 'internal',
|
||||
packDate: '2026-06-25',
|
||||
/* 净菜包:食材名 + 净菜类型 */
|
||||
/* 净菜包:食材名 + 净菜类型 */
|
||||
vegPacks: [
|
||||
{ type: '净菜包', name: '土豆', cutSpec: '20mm×1mm×1mm · 细丝 · 平刀', packSpec: '500g', portions: 3, shelfLife: '3天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-28' },
|
||||
{ type: '净菜包', name: '胡萝卜', cutSpec: '20mm×1mm×1mm · 细丝 · 平刀', packSpec: '500g', portions: 3, shelfLife: '3天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-28' }
|
||||
{ type: '净菜包', name: '土豆', cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, packSpec: {baseGram:500, unit:'袋'}, packSize: 3, shelfLife: '3天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-28' },
|
||||
{ type: '净菜包', name: '胡萝卜', cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, packSpec: {baseGram:500, unit:'袋'}, packSize: 3, shelfLife: '3天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-28' }
|
||||
],
|
||||
/* 餐品净菜包 */
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', name: '宫保鸡丁套餐', packSpec: '大份800g', portions: 2, shelfLife: '2天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-27' }
|
||||
{ type: '餐品净菜包', name: '宫保鸡丁套餐', packSpec: {baseGram:800, unit:'份', portion:'大份'}, packSize: 2, shelfLife: '2天', storageTemp: '0~4℃ 冷藏', expiryDate: '2026-06-27' }
|
||||
]
|
||||
};
|
||||
|
||||
/* 计算总重量 */
|
||||
/* 计算总重量:packSpec 结构化为 {baseGram, unit[, portion]} 后直接读取 baseGram */
|
||||
function calcTotalWeight() {
|
||||
var total = 0;
|
||||
mockData.vegPacks.forEach(function(v) {
|
||||
var w = parseInt(v.packSpec) || 0;
|
||||
total += w * v.portions;
|
||||
var specObj = typeof v.packSpec === 'object' ? v.packSpec : parseLegacyPackSpec(v.packSpec);
|
||||
var w = (specObj && specObj.baseGram) || 0;
|
||||
total += w * v.packSize;
|
||||
});
|
||||
mockData.mealPacks.forEach(function(m) {
|
||||
var specMatch = m.packSpec.match(/(\d+)g/);
|
||||
var w = specMatch ? parseInt(specMatch[1]) : 0;
|
||||
total += w * m.portions;
|
||||
var specObj = typeof m.packSpec === 'object' ? m.packSpec : parseLegacyPackSpec(m.packSpec);
|
||||
var w = (specObj && specObj.baseGram) || 0;
|
||||
total += w * m.packSize;
|
||||
});
|
||||
return total;
|
||||
}
|
||||
@@ -222,26 +284,29 @@
|
||||
var orderTypeLabel = mockData.orderType === 'internal' ? '内供订单' : '外售订单';
|
||||
var totalWeight = calcTotalWeight();
|
||||
|
||||
/* 包装项明细行 */
|
||||
/* 包装项明细行:cutSpec/packSpec 改为 formatCutSpec/formatSpec 渲染 */
|
||||
var detailRows = '';
|
||||
mockData.vegPacks.forEach(function(v, vi) {
|
||||
var cutObj = typeof v.cutSpec === 'object' ? v.cutSpec : parseLegacyCutSpec(v.cutSpec);
|
||||
var specObj = typeof v.packSpec === 'object' ? v.packSpec : parseLegacyPackSpec(v.packSpec);
|
||||
detailRows += '<tr>'
|
||||
+ '<td>' + (vi + 1) + '</td>'
|
||||
+ '<td><span class="type-tag type-tag--veg">净菜包</span></td>'
|
||||
+ '<td>' + v.name + '</td>'
|
||||
+ '<td>' + v.cutSpec + '</td>'
|
||||
+ '<td>' + v.packSpec + '</td>'
|
||||
+ '<td>' + v.portions + '份</td>'
|
||||
+ '<td>' + formatCutSpec(cutObj) + '</td>'
|
||||
+ '<td>' + formatSpec(specObj) + '</td>'
|
||||
+ '<td>' + v.packSize + '份</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
mockData.mealPacks.forEach(function(m, mi) {
|
||||
var specObj = typeof m.packSpec === 'object' ? m.packSpec : parseLegacyPackSpec(m.packSpec);
|
||||
detailRows += '<tr>'
|
||||
+ '<td>' + (mockData.vegPacks.length + mi + 1) + '</td>'
|
||||
+ '<td><span class="type-tag type-tag--meal">餐品净菜包</span></td>'
|
||||
+ '<td>' + m.name + '</td>'
|
||||
+ '<td>—</td>'
|
||||
+ '<td>' + m.packSpec + '</td>'
|
||||
+ '<td>' + m.portions + '份</td>'
|
||||
+ '<td>' + formatSpec(specObj) + '</td>'
|
||||
+ '<td>' + m.packSize + '份</td>'
|
||||
+ '</tr>';
|
||||
});
|
||||
|
||||
@@ -271,7 +336,9 @@
|
||||
var html = '';
|
||||
|
||||
mockData.vegPacks.forEach(function(v) {
|
||||
for (var p = 0; p < v.portions; p++) {
|
||||
var cutObj = typeof v.cutSpec === 'object' ? v.cutSpec : parseLegacyCutSpec(v.cutSpec);
|
||||
var specObj = typeof v.packSpec === 'object' ? v.packSpec : parseLegacyPackSpec(v.packSpec);
|
||||
for (var p = 0; p < v.packSize; p++) {
|
||||
var code = mockData.orderNo + '-V' + v.name.charAt(0) + (p + 1);
|
||||
html += ''
|
||||
+ '<div class="label-sticker">'
|
||||
@@ -279,10 +346,10 @@
|
||||
+ '<span class="label-sticker__title-bar label-sticker__title-bar--green">净菜标签</span>'
|
||||
+ '<div class="label-info" style="margin-top:4px">'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">净菜名称:</span><span class="label-info__value">' + v.name + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">净菜类型:</span><span class="label-info__value">' + v.cutSpec + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">净菜类型:</span><span class="label-info__value">' + formatCutSpec(cutObj) + '</span></div>'
|
||||
+ '</div>'
|
||||
+ '<div class="label-info">'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">包装规格:</span><span class="label-info__value">' + v.packSpec + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">包装规格:</span><span class="label-info__value">' + formatSpec(specObj) + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">包装日期:</span><span class="label-info__value">' + mockData.packDate + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">订单号:</span><span class="label-info__value">' + mockData.orderNo + '</span></div>'
|
||||
+ '</div>'
|
||||
@@ -305,7 +372,8 @@
|
||||
var html = '';
|
||||
|
||||
mockData.mealPacks.forEach(function(m) {
|
||||
for (var p = 0; p < m.portions; p++) {
|
||||
var specObj = typeof m.packSpec === 'object' ? m.packSpec : parseLegacyPackSpec(m.packSpec);
|
||||
for (var p = 0; p < m.packSize; p++) {
|
||||
var code = mockData.orderNo + '-M' + (p + 1);
|
||||
html += ''
|
||||
+ '<div class="label-sticker">'
|
||||
@@ -315,7 +383,7 @@
|
||||
+ '<div class="label-info__item"><span class="label-info__label">餐品名称:</span><span class="label-info__value">' + m.name + '</span></div>'
|
||||
+ '</div>'
|
||||
+ '<div class="label-info">'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">包装规格:</span><span class="label-info__value">' + m.packSpec + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">包装规格:</span><span class="label-info__value">' + formatSpec(specObj) + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">包装日期:</span><span class="label-info__value">' + mockData.packDate + '</span></div>'
|
||||
+ '<div class="label-info__item"><span class="label-info__label">订单号:</span><span class="label-info__value">' + mockData.orderNo + '</span></div>'
|
||||
+ '</div>'
|
||||
@@ -354,27 +422,27 @@
|
||||
else if (tab === 'meal') btn.classList.add('print-btn--orange');
|
||||
}
|
||||
|
||||
/* ---- 打印当前标签 ---- */
|
||||
/* ---- 打印当前标签:按 packSize 计数 ---- */
|
||||
function handlePrintCurrent() {
|
||||
var labels = [];
|
||||
if (currentTab === 'order') labels.push('订单标签');
|
||||
else if (currentTab === 'veg') {
|
||||
mockData.vegPacks.forEach(function(v) {
|
||||
for (var p = 0; p < v.portions; p++) { labels.push('净菜标签 — ' + v.name); }
|
||||
for (var p = 0; p < v.packSize; p++) { labels.push('净菜标签 — ' + v.name); }
|
||||
});
|
||||
} else if (currentTab === 'meal') {
|
||||
mockData.mealPacks.forEach(function(m) {
|
||||
for (var p = 0; p < m.portions; p++) { labels.push('餐品净菜包标签 — ' + m.name); }
|
||||
for (var p = 0; p < m.packSize; p++) { labels.push('餐品净菜包标签 — ' + m.name); }
|
||||
});
|
||||
}
|
||||
showToast('已发送打印指令:' + labels.length + ' 张标签', 2000, 'success');
|
||||
}
|
||||
|
||||
/* ---- 全部打印 ---- */
|
||||
/* ---- 全部打印:按 packSize 计数 ---- */
|
||||
function handlePrintAll() {
|
||||
var total = 1; /* 订单标签 1 张 */
|
||||
mockData.vegPacks.forEach(function(v) { total += v.portions; });
|
||||
mockData.mealPacks.forEach(function(m) { total += m.portions; });
|
||||
mockData.vegPacks.forEach(function(v) { total += v.packSize; });
|
||||
mockData.mealPacks.forEach(function(m) { total += m.packSize; });
|
||||
showToast('已发送全部打印指令:共 ' + total + ' 张标签', 2500, 'success');
|
||||
}
|
||||
|
||||
|
||||
@@ -68,21 +68,24 @@
|
||||
<div class="settings-content">
|
||||
<div class="settings-section" id="section-cutting">
|
||||
<div class="section-title">加工参数配置</div>
|
||||
<div class="setting-item__hint" style="background:rgba(74,144,217,0.08);border:1px solid rgba(74,144,217,0.2);border-radius:8px;padding:10px 12px;margin-bottom:12px;font-size:11px;color:rgba(255,255,255,0.6);line-height:1.5;">
|
||||
ℹ️ 以下默认值从食材档案 <code style="color:#4a90d9">vegTypes.cutSpec</code> 自动读取,此处仅作设备级覆盖(切配类型字典 <code style="color:#4a90d9">spec_cut_shape</code>)。
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认丝条粗细</div><div class="setting-item__desc">丝条规格的标准粗细</div></div>
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认丝条粗细</div><div class="setting-item__desc">丝条规格的标准粗细(单位统一为 mm)</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="3" min="1" max="10"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">mm</span></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认滚刀块大小</div><div class="setting-item__desc">滚刀块规格的标准大小</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>小(2cm)</option><option selected>中(3cm)</option><option>大(5cm)</option></select></div>
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认滚刀块大小</div><div class="setting-item__desc">滚刀块规格的标准大小(单位统一为 mm)</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>小(20mm)</option><option selected>中(30mm)</option><option>大(50mm)</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认方丁大小</div><div class="setting-item__desc">方丁规格的标准大小</div></div>
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认方丁大小</div><div class="setting-item__desc">方丁规格的标准大小(mm)</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>小(5mm)</option><option selected>中(10mm)</option><option>大(15mm)</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认刀工</div><div class="setting-item__desc">默认使用的刀工方式</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option selected>直刀</option><option>斜刀</option><option>花刀</option></select></div>
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认刀工</div><div class="setting-item__desc">引用切配类型字典(spec_cut_shape)</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option selected>直刀</option><option>斜刀</option><option>花刀</option><option>平刀</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-ai" style="display:none">
|
||||
@@ -102,9 +105,8 @@
|
||||
</div>
|
||||
<div class="settings-section" id="section-scale" style="display:none">
|
||||
<div class="section-title">秤参数配置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">重量单位</div><div class="setting-item__desc">称重显示单位</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option selected>斤</option><option>克</option><option>千克</option></select></div>
|
||||
<div class="setting-item__hint" style="background:rgba(250,173,22,0.08);border:1px solid rgba(250,173,22,0.2);border-radius:8px;padding:10px 12px;margin-bottom:12px;font-size:11px;color:rgba(255,255,255,0.6);line-height:1.5;">
|
||||
⚠️ 存储单位已统一为克(g)(任务 4.2),展示层由 <code style="color:#faad14">formatGramForCanteen</code> 自动还原"斤",本设备不再提供单位切换。
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">小数位数</div><div class="setting-item__desc">重量显示精度</div></div>
|
||||
|
||||
@@ -162,6 +162,96 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ============== 规格体系工具函数(任务 4.5 内联,权威源 spec-utils-reference.md) ============== */
|
||||
/* 克重 → 斤展示(≥500g 显示"X斤",不足 1 斤显示克重) */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || isNaN(g)) return '--';
|
||||
if (g < 500) return g + 'g';
|
||||
var jin = (g / 500).toFixed(1).replace(/\.0$/, '');
|
||||
return jin + '斤';
|
||||
}
|
||||
/* 包装规格结构化对象 → 展示字符串(支持对象和旧字符串形态) */
|
||||
function formatSpec(spec) {
|
||||
if (!spec) return '—';
|
||||
if (typeof spec === 'string') return spec;
|
||||
if (typeof spec === 'object') {
|
||||
var parts = [];
|
||||
if (spec.portion) parts.push(spec.portion);
|
||||
if (spec.baseGram) parts.push(spec.baseGram + 'g');
|
||||
if (spec.unit) parts.push('/ ' + spec.unit);
|
||||
return parts.length ? parts.join(' ') : '—';
|
||||
}
|
||||
return '—';
|
||||
}
|
||||
/* 切配规格结构化对象 → 展示字符串(4 种旧字符串格式兼容) */
|
||||
function formatCutSpec(cutSpec) {
|
||||
if (!cutSpec) return '';
|
||||
if (typeof cutSpec === 'string') return cutSpec;
|
||||
if (typeof cutSpec === 'object') {
|
||||
var parts = [];
|
||||
if (cutSpec.length && cutSpec.width && cutSpec.height) {
|
||||
parts.push(cutSpec.length + 'mm×' + cutSpec.width + 'mm×' + cutSpec.height + 'mm');
|
||||
}
|
||||
if (cutSpec.shape) parts.push(cutSpec.shape);
|
||||
if (cutSpec.technique) parts.push(cutSpec.technique);
|
||||
return parts.join(' · ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/* 兼容解析旧版切配规格字符串为结构化对象 */
|
||||
function parseLegacyCutSpec(str) {
|
||||
if (!str) return null;
|
||||
if (typeof str !== 'string') return str;
|
||||
var s = str.trim();
|
||||
var obj = { length: 0, width: 0, height: 0, shape: '', technique: '' };
|
||||
var dimsMatch = s.match(/(\d+(?:\.\d+)?)\s*mm?\s*[\*×xX]\s*(\d+(?:\.\d+)?)\s*mm?\s*[\*×xX]\s*(\d+(?:\.\d+)?)\s*mm?/);
|
||||
if (dimsMatch) {
|
||||
obj.length = parseFloat(dimsMatch[1]);
|
||||
obj.width = parseFloat(dimsMatch[2]);
|
||||
obj.height = parseFloat(dimsMatch[3]);
|
||||
s = s.substring(s.indexOf(dimsMatch[0]) + dimsMatch[0].length);
|
||||
}
|
||||
var tokens = s.split(/[·\-\s,,]+/).filter(function(t) { return t.trim(); });
|
||||
tokens.forEach(function(tok) {
|
||||
if (/丝|片|丁|块|段|条|末|蓉/.test(tok) && !obj.shape) obj.shape = tok.trim();
|
||||
else if (/刀|平刀|斜刀|直刀|花刀/.test(tok) && !obj.technique) obj.technique = tok.trim();
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
/* 兼容解析旧版包装规格字符串为结构化对象('500g' / '大份800g' / '标准份500g' / '真空袋') */
|
||||
function parseLegacyPackSpec(str) {
|
||||
if (!str) return null;
|
||||
if (typeof str !== 'object') {
|
||||
var s = String(str).trim();
|
||||
var m = s.match(/^(大份|小份|标准份|迷你份)?\s*(\d+(?:\.\d+)?)?\s*(g|克|斤|份|袋|盒)?$/);
|
||||
var portion = (m && m[1]) || '';
|
||||
var baseGram = (m && m[2]) ? parseFloat(m[2]) : 0;
|
||||
var unit = (m && m[3]) || '';
|
||||
if (unit === '克') unit = 'g';
|
||||
if (!unit) unit = baseGram ? '份' : '袋';
|
||||
return { portion: portion, baseGram: baseGram, unit: unit };
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/* 跨页共享:从 localStorage 加载餐品净菜包规格数据(key: dish_specs_v1) */
|
||||
var DISH_SPECS_KEY = 'dish_specs_v1';
|
||||
function loadDishSpecs() {
|
||||
try {
|
||||
var raw = localStorage.getItem(DISH_SPECS_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch(e) {}
|
||||
return {};
|
||||
}
|
||||
/* packSpec 兜底:从跨页共享表查(用于 mockData 与运营配置不一致时的兜底) */
|
||||
function resolvePackSpec(pack) {
|
||||
if (pack.packSpec && typeof pack.packSpec === 'object') return pack.packSpec;
|
||||
if (pack.packSpec && typeof pack.packSpec === 'string') return parseLegacyPackSpec(pack.packSpec);
|
||||
var dishSpecs = loadDishSpecs();
|
||||
var key = pack.packName || pack.name;
|
||||
if (key && dishSpecs[key]) return dishSpecs[key];
|
||||
return null;
|
||||
}
|
||||
|
||||
/* 获取 URL 参数 */
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -175,7 +265,7 @@
|
||||
/* 当前 Tab 筛选 */
|
||||
var currentFilter = 'pending';
|
||||
|
||||
/* 模拟数据:仅展示餐品净菜包订单 */
|
||||
/* 模拟数据:packSpec 改为结构化对象(baseGram+unit+portion),切配规格在 02 页体现 */
|
||||
var mockData = [
|
||||
{
|
||||
orderNo: 'PO-20260616-001',
|
||||
@@ -183,7 +273,7 @@
|
||||
expectedArrival: '2026-06-17 11:30',
|
||||
taskNo: 'T-001',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: {portion:'大份', baseGram:800, unit:'份'}, ingredients: [
|
||||
{ name: '土豆', icon: '🥔', weightPerPack: 300, weighed: 0 },
|
||||
{ name: '胡萝卜', icon: '🥕', weightPerPack: 250, weighed: 0 },
|
||||
{ name: '黄瓜', icon: '🥒', weightPerPack: 250, weighed: 0 }
|
||||
@@ -200,14 +290,14 @@
|
||||
expectedArrival: '2026-06-17 12:00',
|
||||
taskNo: 'T-002',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: {portion:'大份', baseGram:800, unit:'份'}, ingredients: [
|
||||
{ name: '白菜', icon: '🥬', weightPerPack: 400, weighed: 0 },
|
||||
{ name: '胡萝卜', icon: '🥕', weightPerPack: 400, weighed: 0 }
|
||||
], seasonings: [
|
||||
{ name: '醋', cellIdx: 2, weightPerPack: 8, weighed: 0 },
|
||||
{ name: '料酒', cellIdx: 4, weightPerPack: 12, weighed: 0 }
|
||||
]},
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: {portion:'标准份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '番茄', icon: '🍅', weightPerPack: 300, weighed: 0 },
|
||||
{ name: '鸡蛋', icon: '🥚', weightPerPack: 200, weighed: 0 }
|
||||
], seasonings: [
|
||||
@@ -222,7 +312,7 @@
|
||||
expectedArrival: '2026-06-17 14:30',
|
||||
taskNo: 'T-003',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '大份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: {portion:'大份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '番茄炒蛋', icon: '🥘', weightPerPack: 500, weighed: 2000 }
|
||||
], seasonings: [
|
||||
{ name: '盐', cellIdx: 2, weightPerPack: 5, weighed: 20 },
|
||||
@@ -236,7 +326,7 @@
|
||||
expectedArrival: '2026-06-17 16:00',
|
||||
taskNo: 'T-004',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '标准份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: {portion:'标准份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '凉拌黄瓜', icon: '🥘', weightPerPack: 500, weighed: 2500 }
|
||||
], seasonings: [
|
||||
{ name: '醋', cellIdx: 2, weightPerPack: 6, weighed: 30 },
|
||||
@@ -329,11 +419,15 @@
|
||||
var packDone = !hasPendingPack(pack);
|
||||
var weightClass = packDone ? 'parent-weight--done' : '';
|
||||
|
||||
/* packSpec 结构化为对象后用 formatSpec 渲染(兼容旧字符串和跨页共享表) */
|
||||
var specObj = resolvePackSpec(pack);
|
||||
var specText = specObj ? formatSpec(specObj) : '—';
|
||||
|
||||
var parentRow = document.createElement('div');
|
||||
parentRow.className = 'item-parent-row';
|
||||
parentRow.innerHTML =
|
||||
'<div class="parent-name"><div class="parent-icon">📦</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
|
||||
'<div style="text-align:center"><span class="pack-spec-tag">' + (pack.packSpec || '—') + '</span></div>' +
|
||||
'<div style="text-align:center"><span class="pack-spec-tag">' + specText + '</span></div>' +
|
||||
'<div class="parent-weight ' + weightClass + '">' + totalNeed + ' 克</div>' +
|
||||
'<div class="parent-weight ' + weightClass + '">' + totalWeighed + ' 克</div>' +
|
||||
'<div style="text-align:center"><span class="status-tag ' + (packDone ? 'status-tag--done' : 'status-tag--wait') + '">' + (packDone ? '已完成' : '待组配') + '</span></div>' +
|
||||
|
||||
@@ -520,6 +520,95 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* ============== 规格体系工具函数(任务 4.5 内联,权威源 spec-utils-reference.md) ============== */
|
||||
/* 克重 → 斤展示(≥500g 显示"X斤",不足 1 斤显示克重) */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || isNaN(g)) return '--';
|
||||
if (g < 500) return g + 'g';
|
||||
var jin = (g / 500).toFixed(1).replace(/\.0$/, '');
|
||||
return jin + '斤';
|
||||
}
|
||||
/* 包装规格结构化对象 → 展示字符串(支持对象和旧字符串形态) */
|
||||
function formatSpec(spec) {
|
||||
if (!spec) return '—';
|
||||
if (typeof spec === 'string') return spec;
|
||||
if (typeof spec === 'object') {
|
||||
var parts = [];
|
||||
if (spec.portion) parts.push(spec.portion);
|
||||
if (spec.baseGram) parts.push(spec.baseGram + 'g');
|
||||
if (spec.unit) parts.push('/ ' + spec.unit);
|
||||
return parts.length ? parts.join(' ') : '—';
|
||||
}
|
||||
return '—';
|
||||
}
|
||||
/* 切配规格结构化对象 → 展示字符串(4 种旧字符串格式兼容) */
|
||||
function formatCutSpec(cutSpec) {
|
||||
if (!cutSpec) return '';
|
||||
if (typeof cutSpec === 'string') return cutSpec;
|
||||
if (typeof cutSpec === 'object') {
|
||||
var parts = [];
|
||||
if (cutSpec.length && cutSpec.width && cutSpec.height) {
|
||||
parts.push(cutSpec.length + 'mm×' + cutSpec.width + 'mm×' + cutSpec.height + 'mm');
|
||||
}
|
||||
if (cutSpec.shape) parts.push(cutSpec.shape);
|
||||
if (cutSpec.technique) parts.push(cutSpec.technique);
|
||||
return parts.join(' · ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/* 兼容解析旧版切配规格字符串为结构化对象 */
|
||||
function parseLegacyCutSpec(str) {
|
||||
if (!str) return null;
|
||||
if (typeof str !== 'string') return str;
|
||||
var s = str.trim();
|
||||
var obj = { length: 0, width: 0, height: 0, shape: '', technique: '' };
|
||||
var dimsMatch = s.match(/(\d+(?:\.\d+)?)\s*mm?\s*[\*×xX]\s*(\d+(?:\.\d+)?)\s*mm?\s*[\*×xX]\s*(\d+(?:\.\d+)?)\s*mm?/);
|
||||
if (dimsMatch) {
|
||||
obj.length = parseFloat(dimsMatch[1]);
|
||||
obj.width = parseFloat(dimsMatch[2]);
|
||||
obj.height = parseFloat(dimsMatch[3]);
|
||||
s = s.substring(s.indexOf(dimsMatch[0]) + dimsMatch[0].length);
|
||||
}
|
||||
var tokens = s.split(/[·\-\s,,]+/).filter(function(t) { return t.trim(); });
|
||||
tokens.forEach(function(tok) {
|
||||
if (/丝|片|丁|块|段|条|末|蓉/.test(tok) && !obj.shape) obj.shape = tok.trim();
|
||||
else if (/刀|平刀|斜刀|直刀|花刀/.test(tok) && !obj.technique) obj.technique = tok.trim();
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
/* 兼容解析旧版包装规格字符串为结构化对象 */
|
||||
function parseLegacyPackSpec(str) {
|
||||
if (!str) return null;
|
||||
if (typeof str !== 'object') {
|
||||
var s = String(str).trim();
|
||||
var m = s.match(/^(大份|小份|标准份|迷你份)?\s*(\d+(?:\.\d+)?)?\s*(g|克|斤|份|袋|盒)?$/);
|
||||
var portion = (m && m[1]) || '';
|
||||
var baseGram = (m && m[2]) ? parseFloat(m[2]) : 0;
|
||||
var unit = (m && m[3]) || '';
|
||||
if (unit === '克') unit = 'g';
|
||||
if (!unit) unit = baseGram ? '份' : '袋';
|
||||
return { portion: portion, baseGram: baseGram, unit: unit };
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/* 跨页共享:从 localStorage 加载餐品净菜包规格数据(key: dish_specs_v1) */
|
||||
var DISH_SPECS_KEY = 'dish_specs_v1';
|
||||
function loadDishSpecs() {
|
||||
try {
|
||||
var raw = localStorage.getItem(DISH_SPECS_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch(e) {}
|
||||
return {};
|
||||
}
|
||||
function resolvePackSpec(pack) {
|
||||
if (pack.packSpec && typeof pack.packSpec === 'object') return pack.packSpec;
|
||||
if (pack.packSpec && typeof pack.packSpec === 'string') return parseLegacyPackSpec(pack.packSpec);
|
||||
var dishSpecs = loadDishSpecs();
|
||||
var key = pack.packName || pack.name;
|
||||
if (key && dishSpecs[key]) return dishSpecs[key];
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ---- 工具函数 ---- */
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -537,16 +626,16 @@
|
||||
document.getElementById('topUser').textContent = userName;
|
||||
document.getElementById('userAvatar').textContent = userName.charAt(0);
|
||||
|
||||
/* ---- 模拟数据 ---- */
|
||||
/* ---- 模拟数据:packSpec/cutSpec 结构化为对象,切配规格 4 字段(length/width/height/shape/technique) ---- */
|
||||
var taskData = {
|
||||
'T-001': {
|
||||
orderNo: 'PO-20260616-001',
|
||||
orderType: 'internal',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
|
||||
{ name: '土豆', icon: '🥔', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 300, weighed: 0 },
|
||||
{ name: '胡萝卜', icon: '🥕', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 250, weighed: 0 },
|
||||
{ name: '黄瓜', icon: '🥒', cutSpec: '30mm*30mm*3mm · 圆片 · 平刀', weightPerPack: 250, weighed: 0 }
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: {portion:'大份', baseGram:800, unit:'份'}, ingredients: [
|
||||
{ name: '土豆', icon: '🥔', cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, weightPerPack: 300, weighed: 0 },
|
||||
{ name: '胡萝卜', icon: '🥕', cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, weightPerPack: 250, weighed: 0 },
|
||||
{ name: '黄瓜', icon: '🥒', cutSpec: {length:30, width:30, height:3, shape:'圆片', technique:'平刀'}, weightPerPack: 250, weighed: 0 }
|
||||
], seasonings: [
|
||||
{ name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 50, weighed: 0 },
|
||||
{ name: '盐', icon: '🧂', cellIdx: 1, weightPerPack: 5, weighed: 0 },
|
||||
@@ -562,9 +651,9 @@
|
||||
orderNo: 'PO-20260616-002',
|
||||
orderType: 'external',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
|
||||
{ name: '白菜', icon: '🥬', cutSpec: '50mm*30mm*30mm · 宽段 · 平刀', weightPerPack: 400, weighed: 0 },
|
||||
{ name: '胡萝卜', icon: '🥕', cutSpec: '20mm*1mm*1mm · 细丝 · 平刀', weightPerPack: 400, weighed: 0 }
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: {portion:'大份', baseGram:800, unit:'份'}, ingredients: [
|
||||
{ name: '白菜', icon: '🥬', cutSpec: {length:50, width:30, height:30, shape:'宽段', technique:'平刀'}, weightPerPack: 400, weighed: 0 },
|
||||
{ name: '胡萝卜', icon: '🥕', cutSpec: {length:20, width:1, height:1, shape:'细丝', technique:'平刀'}, weightPerPack: 400, weighed: 0 }
|
||||
], seasonings: [
|
||||
{ name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 40, weighed: 0 },
|
||||
{ name: '盐', icon: '🧂', cellIdx: 1, weightPerPack: 4, weighed: 0 },
|
||||
@@ -580,7 +669,7 @@
|
||||
orderNo: 'PO-20260616-003',
|
||||
orderType: 'internal',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '大份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: {portion:'大份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '番茄炒蛋', icon: '🥘', weightPerPack: 500, weighed: 2000 }
|
||||
], seasonings: [
|
||||
{ name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 30, weighed: 120 },
|
||||
@@ -593,7 +682,7 @@
|
||||
orderNo: 'PO-20260616-004',
|
||||
orderType: 'external',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '标准份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: {portion:'标准份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '凉拌黄瓜', icon: '🥘', weightPerPack: 500, weighed: 2500 }
|
||||
], seasonings: [
|
||||
{ name: '油', icon: '🛢', cellIdx: 0, weightPerPack: 20, weighed: 100 },
|
||||
@@ -712,7 +801,10 @@
|
||||
var orderTypeLabel = task.orderType === 'internal' ? '内供订单' : '外售订单';
|
||||
var orderTypeCls = task.orderType === 'internal' ? 'order-type-tag--internal' : 'order-type-tag--external';
|
||||
orderTypeEl.innerHTML = '<span class="order-type-tag ' + orderTypeCls + '">' + orderTypeLabel + '</span>';
|
||||
document.getElementById('taskPackName').textContent = currentPack.packName + ' · ' + (currentPack.packSpec || '—');
|
||||
/* 餐品名称展示:packSpec 结构化为对象,使用 formatSpec 渲染 */
|
||||
var specObj = resolvePackSpec(currentPack);
|
||||
var specText = specObj ? formatSpec(specObj) : '—';
|
||||
document.getElementById('taskPackName').textContent = currentPack.packName + ' · ' + specText;
|
||||
|
||||
/* 订单状态:主辅材+调料全部完成才算完成 */
|
||||
var ingAllDone = allIngredients.every(function(ing) {
|
||||
@@ -748,6 +840,9 @@
|
||||
allIngredients.forEach(function(ing, idx) {
|
||||
var need = Math.round(ing.weightPerPack * 100) / 100;
|
||||
var isDone = ing.weighed >= need;
|
||||
/* cutSpec 结构化为对象,用 formatCutSpec 渲染(兼容旧字符串) */
|
||||
var cutObj = ing.cutSpec ? (typeof ing.cutSpec === 'object' ? ing.cutSpec : parseLegacyCutSpec(ing.cutSpec)) : null;
|
||||
var cutText = cutObj ? formatCutSpec(cutObj) : '—';
|
||||
|
||||
var row = document.createElement('div');
|
||||
row.className = 'item-row';
|
||||
@@ -768,7 +863,7 @@
|
||||
'<div class="item-card__data">' +
|
||||
'<div class="item-card__data-item"><span class="item-card__data-label">应称</span><span class="item-card__data-value">' + need + 'g</span></div>' +
|
||||
'<div class="item-card__data-item"><span class="item-card__data-label">已称</span><span class="item-card__data-value ' + actualClass + '">' + ing.weighed + 'g</span></div>' +
|
||||
'<div class="item-card__data-item"><span class="item-card__data-label">净菜类型</span><span class="item-card__data-value">' + (ing.cutSpec || '—') + '</span></div>' +
|
||||
'<div class="item-card__data-item"><span class="item-card__data-label">净菜类型</span><span class="item-card__data-value">' + cutText + '</span></div>' +
|
||||
'</div>';
|
||||
|
||||
body.appendChild(row);
|
||||
@@ -953,10 +1048,13 @@
|
||||
|
||||
title.textContent = '智能识别候选 (' + selected.length + ')';
|
||||
|
||||
/* 构建净菜类型映射 */
|
||||
/* 构建净菜类型映射:cutSpec 结构化后用 formatCutSpec 渲染为字符串 */
|
||||
var cutSpecMap = {};
|
||||
allIngredients.forEach(function(ing) {
|
||||
if (ing.cutSpec) cutSpecMap[ing.name] = ing.cutSpec;
|
||||
if (ing.cutSpec) {
|
||||
var cutObj = typeof ing.cutSpec === 'object' ? ing.cutSpec : parseLegacyCutSpec(ing.cutSpec);
|
||||
cutSpecMap[ing.name] = formatCutSpec(cutObj);
|
||||
}
|
||||
});
|
||||
|
||||
list.innerHTML = '';
|
||||
|
||||
@@ -358,6 +358,75 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ============== 规格体系工具函数(任务 4.5 内联,权威源 spec-utils-reference.md) ============== */
|
||||
/* 克重 → 斤展示(≥500g 显示"X斤",不足 1 斤显示克重) */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || isNaN(g)) return '--';
|
||||
if (g < 500) return g + 'g';
|
||||
var jin = (g / 500).toFixed(1).replace(/\.0$/, '');
|
||||
return jin + '斤';
|
||||
}
|
||||
/* 包装规格结构化对象 → 展示字符串(支持对象和旧字符串形态) */
|
||||
function formatSpec(spec) {
|
||||
if (!spec) return '—';
|
||||
if (typeof spec === 'string') return spec;
|
||||
if (typeof spec === 'object') {
|
||||
var parts = [];
|
||||
if (spec.portion) parts.push(spec.portion);
|
||||
if (spec.baseGram) parts.push(spec.baseGram + 'g');
|
||||
if (spec.unit) parts.push('/ ' + spec.unit);
|
||||
return parts.length ? parts.join(' ') : '—';
|
||||
}
|
||||
return '—';
|
||||
}
|
||||
/* 切配规格结构化对象 → 展示字符串(4 种旧字符串格式兼容) */
|
||||
function formatCutSpec(cutSpec) {
|
||||
if (!cutSpec) return '';
|
||||
if (typeof cutSpec === 'string') return cutSpec;
|
||||
if (typeof cutSpec === 'object') {
|
||||
var parts = [];
|
||||
if (cutSpec.length && cutSpec.width && cutSpec.height) {
|
||||
parts.push(cutSpec.length + 'mm×' + cutSpec.width + 'mm×' + cutSpec.height + 'mm');
|
||||
}
|
||||
if (cutSpec.shape) parts.push(cutSpec.shape);
|
||||
if (cutSpec.technique) parts.push(cutSpec.technique);
|
||||
return parts.join(' · ');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/* 兼容解析旧版包装规格字符串为结构化对象 */
|
||||
function parseLegacyPackSpec(str) {
|
||||
if (!str) return null;
|
||||
if (typeof str !== 'object') {
|
||||
var s = String(str).trim();
|
||||
var m = s.match(/^(大份|小份|标准份|迷你份)?\s*(\d+(?:\.\d+)?)?\s*(g|克|斤|份|袋|盒)?$/);
|
||||
var portion = (m && m[1]) || '';
|
||||
var baseGram = (m && m[2]) ? parseFloat(m[2]) : 0;
|
||||
var unit = (m && m[3]) || '';
|
||||
if (unit === '克') unit = 'g';
|
||||
if (!unit) unit = baseGram ? '份' : '袋';
|
||||
return { portion: portion, baseGram: baseGram, unit: unit };
|
||||
}
|
||||
return str;
|
||||
}
|
||||
/* 跨页共享:从 localStorage 加载餐品净菜包规格数据(key: dish_specs_v1) */
|
||||
var DISH_SPECS_KEY = 'dish_specs_v1';
|
||||
function loadDishSpecs() {
|
||||
try {
|
||||
var raw = localStorage.getItem(DISH_SPECS_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch(e) {}
|
||||
return {};
|
||||
}
|
||||
function resolvePackSpec(pack) {
|
||||
if (pack.packSpec && typeof pack.packSpec === 'object') return pack.packSpec;
|
||||
if (pack.packSpec && typeof pack.packSpec === 'string') return parseLegacyPackSpec(pack.packSpec);
|
||||
var dishSpecs = loadDishSpecs();
|
||||
var key = pack.packName || pack.name;
|
||||
if (key && dishSpecs[key]) return dishSpecs[key];
|
||||
return null;
|
||||
}
|
||||
|
||||
/* 退出登录 */
|
||||
function handleLogout() {
|
||||
window.location.href = '00-welcome.html';
|
||||
@@ -379,12 +448,12 @@
|
||||
var TODAY = '2026-06-16';
|
||||
var WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
|
||||
// 组配记录数据(与组配任务页01数据结构一致,全部为已组配状态)
|
||||
// 组配记录数据(packSpec 结构化为对象,与 01 任务页保持一致)
|
||||
var RECORDS = [
|
||||
{
|
||||
orderNo: 'PO-20260616-001', orderType: 'internal', completeTime: '2026-06-16 10:20', taskNo: 'T-001',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: '大份800g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '宫保鸡丁套餐', packSize: 3, packSpec: {portion:'大份', baseGram:800, unit:'份'}, ingredients: [
|
||||
{ name: '土豆', icon: '🥔', weightPerPack: 300, weighed: 900 },
|
||||
{ name: '胡萝卜', icon: '🥕', weightPerPack: 250, weighed: 750 },
|
||||
{ name: '黄瓜', icon: '🥒', weightPerPack: 250, weighed: 750 }
|
||||
@@ -402,7 +471,7 @@
|
||||
{
|
||||
orderNo: 'PO-20260616-002', orderType: 'external', completeTime: '2026-06-16 11:00', taskNo: 'T-002',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: '大份800g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '鱼香肉丝套餐', packSize: 2, packSpec: {portion:'大份', baseGram:800, unit:'份'}, ingredients: [
|
||||
{ name: '白菜', icon: '🥬', weightPerPack: 400, weighed: 800 },
|
||||
{ name: '胡萝卜', icon: '🥕', weightPerPack: 400, weighed: 800 }
|
||||
], seasonings: [
|
||||
@@ -414,7 +483,7 @@
|
||||
{ name: '料酒', cellIdx: 5, weightPerPack: 10, weighed: 20 },
|
||||
{ name: '豆瓣酱', cellIdx: 6, weightPerPack: 15, weighed: 30 }
|
||||
]},
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: '标准份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 2, packSpec: {portion:'标准份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '番茄', icon: '🍅', weightPerPack: 300, weighed: 600 },
|
||||
{ name: '鸡蛋', icon: '🥚', weightPerPack: 200, weighed: 400 }
|
||||
], seasonings: [
|
||||
@@ -427,7 +496,7 @@
|
||||
{
|
||||
orderNo: 'PO-20260616-003', orderType: 'internal', completeTime: '2026-06-16 14:50', taskNo: 'T-003',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: '大份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '番茄炒蛋套餐', packSize: 4, packSpec: {portion:'大份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '番茄炒蛋', icon: '🥘', weightPerPack: 500, weighed: 2000 }
|
||||
], seasonings: [
|
||||
{ name: '油', cellIdx: 0, weightPerPack: 30, weighed: 120 },
|
||||
@@ -439,7 +508,7 @@
|
||||
{
|
||||
orderNo: 'PO-20260616-004', orderType: 'external', completeTime: '2026-06-16 15:20', taskNo: 'T-004',
|
||||
mealPacks: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: '标准份500g', ingredients: [
|
||||
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 5, packSpec: {portion:'标准份', baseGram:500, unit:'份'}, ingredients: [
|
||||
{ name: '凉拌黄瓜', icon: '🥘', weightPerPack: 500, weighed: 2500 }
|
||||
], seasonings: [
|
||||
{ name: '油', cellIdx: 0, weightPerPack: 20, weighed: 100 },
|
||||
@@ -725,11 +794,15 @@
|
||||
var packDone = !hasPendingPack(pack);
|
||||
var weightClass = packDone ? 'parent-weight--done' : '';
|
||||
|
||||
/* packSpec 结构化为对象,用 formatSpec 渲染(兼容旧字符串和跨页共享表) */
|
||||
var specObj = resolvePackSpec(pack);
|
||||
var specText = specObj ? formatSpec(specObj) : '—';
|
||||
|
||||
var parentRow = document.createElement('div');
|
||||
parentRow.className = 'item-parent-row';
|
||||
parentRow.innerHTML =
|
||||
'<div class="parent-name"><div class="parent-icon">📦</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
|
||||
'<div style="text-align:center"><span class="pack-spec-tag">' + (pack.packSpec || '—') + '</span></div>' +
|
||||
'<div style="text-align:center"><span class="pack-spec-tag">' + specText + '</span></div>' +
|
||||
'<div class="parent-weight ' + weightClass + '">' + totalNeed + ' 克</div>' +
|
||||
'<div class="parent-weight ' + weightClass + '">' + totalWeighed + ' 克</div>' +
|
||||
'<div style="text-align:center"><span class="status-tag ' + (packDone ? 'status-tag--done' : 'status-tag--wait') + '">' + (packDone ? '已完成' : '待组配') + '</span></div>';
|
||||
|
||||
@@ -1009,40 +1009,42 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* ===== 食材目录(可称重食材,按重量验收) ===== */
|
||||
/* ===== 食材目录(称重验收,存储单位克,展示层用 formatGramForCanteen 还原"斤") ===== */
|
||||
/* verifyType:'weight' 表示称重验收,可用 ['斤','公斤','克','千克'] */
|
||||
var FOOD_CATALOG = {
|
||||
'大白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC001' },
|
||||
'圆白菜': { price: 4.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC002' },
|
||||
'菠菜': { price: 6.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC003' },
|
||||
'油菜': { price: 4.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC004' },
|
||||
'生菜': { price: 5.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC005' },
|
||||
'芹菜': { price: 5.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC006' },
|
||||
'西兰花': { price: 8.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC001' },
|
||||
'花菜': { price: 7.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC002' },
|
||||
'黄瓜': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG001' },
|
||||
'西红柿': { price: 6.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG002' },
|
||||
'茄子': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG003' },
|
||||
'青椒': { price: 6.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG004' },
|
||||
'豆角': { price: 7.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG005' },
|
||||
'土豆': { price: 3.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ001' },
|
||||
'胡萝卜': { price: 4.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ002' },
|
||||
'白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC007' },
|
||||
'猪里脊': { price: 18.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ001' },
|
||||
'五花肉': { price: 16.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ002' },
|
||||
'草鱼': { price: 14.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC001' },
|
||||
'虾仁': { price: 35.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC002' }
|
||||
'大白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC001', verifyType: 'weight' },
|
||||
'圆白菜': { price: 4.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC002', verifyType: 'weight' },
|
||||
'菠菜': { price: 6.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC003', verifyType: 'weight' },
|
||||
'油菜': { price: 4.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC004', verifyType: 'weight' },
|
||||
'生菜': { price: 5.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC005', verifyType: 'weight' },
|
||||
'芹菜': { price: 5.0, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC006', verifyType: 'weight' },
|
||||
'西兰花': { price: 8.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC001', verifyType: 'weight' },
|
||||
'花菜': { price: 7.0, unit: '斤', catLabel: '花菜类', traceCode: 'TRC260601-HC002', verifyType: 'weight' },
|
||||
'黄瓜': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG001', verifyType: 'weight' },
|
||||
'西红柿': { price: 6.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG002', verifyType: 'weight' },
|
||||
'茄子': { price: 5.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG003', verifyType: 'weight' },
|
||||
'青椒': { price: 6.0, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG004', verifyType: 'weight' },
|
||||
'豆角': { price: 7.5, unit: '斤', catLabel: '瓜果类', traceCode: 'TRC260601-GG005', verifyType: 'weight' },
|
||||
'土豆': { price: 3.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ001', verifyType: 'weight' },
|
||||
'胡萝卜': { price: 4.0, unit: '斤', catLabel: '根茎类', traceCode: 'TRC260601-GJ002', verifyType: 'weight' },
|
||||
'白菜': { price: 3.5, unit: '斤', catLabel: '叶菜类', traceCode: 'TRC260601-YC007', verifyType: 'weight' },
|
||||
'猪里脊': { price: 18.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ001', verifyType: 'weight' },
|
||||
'五花肉': { price: 16.0, unit: '斤', catLabel: '肉禽类', traceCode: 'TRC260601-RQ002', verifyType: 'weight' },
|
||||
'草鱼': { price: 14.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC001', verifyType: 'weight' },
|
||||
'虾仁': { price: 35.0, unit: '斤', catLabel: '水产类', traceCode: 'TRC260601-SC002', verifyType: 'weight' }
|
||||
};
|
||||
|
||||
/* ===== 调料目录(按规格/数量验收,无法称重) ===== */
|
||||
/* ===== 调料目录(点收验收,按规格/数量验收) ===== */
|
||||
/* verifyType:'count' 表示点收验收,可用 ['袋','瓶','箱','桶','罐','份','个','只','条','把'] */
|
||||
var SPICE_CATALOG = {
|
||||
'酱油': { price: 12.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL001' },
|
||||
'醋': { price: 8.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL002' },
|
||||
'料酒': { price: 10.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL003' },
|
||||
'盐': { price: 3.0, unit: '袋', spec: '500g/袋', catLabel: '调料', traceCode: 'TRC260601-TL004' },
|
||||
'白糖': { price: 6.0, unit: '袋', spec: '1kg/袋', catLabel: '调料', traceCode: 'TRC260601-TL005' },
|
||||
'鸡精': { price: 8.0, unit: '罐', spec: '200g/罐', catLabel: '调料', traceCode: 'TRC260601-TL006' },
|
||||
'食用油': { price: 45.0, unit: '桶', spec: '5L/桶', catLabel: '调料', traceCode: 'TRC260601-TL007' },
|
||||
'花椒': { price: 25.0, unit: '袋', spec: '100g/袋', catLabel: '调料', traceCode: 'TRC260601-TL008' }
|
||||
'酱油': { price: 12.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL001', verifyType: 'count' },
|
||||
'醋': { price: 8.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL002', verifyType: 'count' },
|
||||
'料酒': { price: 10.0, unit: '瓶', spec: '500ml/瓶', catLabel: '调料', traceCode: 'TRC260601-TL003', verifyType: 'count' },
|
||||
'盐': { price: 3.0, unit: '袋', spec: '500g/袋', catLabel: '调料', traceCode: 'TRC260601-TL004', verifyType: 'count' },
|
||||
'白糖': { price: 6.0, unit: '袋', spec: '1kg/袋', catLabel: '调料', traceCode: 'TRC260601-TL005', verifyType: 'count' },
|
||||
'鸡精': { price: 8.0, unit: '罐', spec: '200g/罐', catLabel: '调料', traceCode: 'TRC260601-TL006', verifyType: 'count' },
|
||||
'食用油': { price: 45.0, unit: '桶', spec: '5L/桶', catLabel: '调料', traceCode: 'TRC260601-TL007', verifyType: 'count' },
|
||||
'花椒': { price: 25.0, unit: '袋', spec: '100g/袋', catLabel: '调料', traceCode: 'TRC260601-TL008', verifyType: 'count' }
|
||||
};
|
||||
|
||||
/* 判断食材是否为调料(按规格验收) */
|
||||
@@ -1473,8 +1475,12 @@
|
||||
? '<span class="food-row__weight" style="color:rgba(255,255,255,0.25);">--</span>'
|
||||
: '<span class="food-row__weight">' + (spice ? orderQty : orderQty.toFixed(1)) + '</span>';
|
||||
|
||||
/* 规格下拉选项(采购规格 + 实收规格共用) */
|
||||
var UNIT_OPTIONS = ['斤','公斤','克','千克','袋','瓶','箱','桶','罐','份','个','只','条','把'];
|
||||
/* 规格下拉选项(按 verifyType 分类过滤) */
|
||||
/* 称重类(weight):'斤','公斤','克','千克' | 计数类(count):'袋','瓶','箱','桶','罐','份','个','只','条','把' */
|
||||
var UNIT_OPTIONS_WEIGHT = ['斤','公斤','克','千克'];
|
||||
var UNIT_OPTIONS_COUNT = ['袋','瓶','箱','桶','罐','份','个','只','条','把'];
|
||||
var verifyType = info.verifyType || 'weight';
|
||||
var UNIT_OPTIONS = (verifyType === 'count') ? UNIT_OPTIONS_COUNT : UNIT_OPTIONS_WEIGHT;
|
||||
var currentUnit = (f._customUnit !== undefined) ? f._customUnit : info.unit;
|
||||
var actualUnit = (f._actualUnit !== undefined) ? f._actualUnit : currentUnit;
|
||||
function buildUnitSelect(selected, onChangeFn) {
|
||||
|
||||
@@ -260,22 +260,22 @@
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--cucumber">🥒</div>
|
||||
<span class="ingredient-row__name">黄瓜</span>
|
||||
<span class="ingredient-row__weight">需 4.0 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 4.0 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="2000">需 4 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="2000">已录 4 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 10:00</span>
|
||||
</div>
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--potato"></div>
|
||||
<span class="ingredient-row__name">土豆</span>
|
||||
<span class="ingredient-row__weight">需 5.0 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 5.0 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="2500">需 5 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="2500">已录 5 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 10:30</span>
|
||||
</div>
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--tomato">🍅</div>
|
||||
<span class="ingredient-row__name">西红柿</span>
|
||||
<span class="ingredient-row__weight">需 3.5 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 3.5 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="1750">需 3.5 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="1750">已录 3.5 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 11:00</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -296,15 +296,15 @@
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--carrot"></div>
|
||||
<span class="ingredient-row__name">胡萝卜</span>
|
||||
<span class="ingredient-row__weight">需 2.0 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 2.0 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="1000">需 2 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="1000">已录 2 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 11:30</span>
|
||||
</div>
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--tomato">🍅</div>
|
||||
<span class="ingredient-row__name">小番茄</span>
|
||||
<span class="ingredient-row__weight">需 1.5 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 1.5 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="750">需 1.5 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="750">已录 1.5 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 14:00</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -324,22 +324,22 @@
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--cabbage">🥬</div>
|
||||
<span class="ingredient-row__name">大白菜</span>
|
||||
<span class="ingredient-row__weight">需 8.0 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 8.0 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="4000">需 8 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="4000">已录 8 斤</span>
|
||||
<span class="ingredient-row__deadline ingredient-row__deadline--overdue">截止 09:30</span>
|
||||
</div>
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--cabbage">🥬</div>
|
||||
<span class="ingredient-row__name">娃娃菜</span>
|
||||
<span class="ingredient-row__weight">需 2.5 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 2.5 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="1250">需 2.5 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="1250">已录 2.5 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 10:00</span>
|
||||
</div>
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--potato"></div>
|
||||
<span class="ingredient-row__name">土豆(晚餐)</span>
|
||||
<span class="ingredient-row__weight">需 3.0 斤</span>
|
||||
<span class="ingredient-row__recorded">已录 3.0 斤</span>
|
||||
<span class="ingredient-row__weight" data-weight-g="1500">需 3 斤</span>
|
||||
<span class="ingredient-row__recorded" data-weight-g="1500">已录 3 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 15:00</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -353,6 +353,23 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
/* 克重展示格式化:500→"500g",1500→"1.5kg",25000→"25kg" */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
/* 验收秤场景兼容"斤"显示:整500倍数显示"斤",否则回退到 formatGram */
|
||||
function formatGramForCanteen(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g % 500 === 0) return (g / 500) + '斤';
|
||||
return formatGram(g);
|
||||
}
|
||||
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -423,14 +440,14 @@
|
||||
'<div class="ingredient-row">' +
|
||||
'<div class="ingredient-row__icon icon--cabbage">🥦</div>' +
|
||||
'<span class="ingredient-row__name">西兰花</span>' +
|
||||
'<span class="ingredient-row__weight">需 3.0 斤</span>' +
|
||||
'<span class="ingredient-row__weight" data-weight-g="1500">需 3 斤</span>' +
|
||||
'<span class="ingredient-row__recorded ingredient-row__recorded--none">未录入</span>' +
|
||||
'<span class="ingredient-row__deadline">截止 13:00</span>' +
|
||||
'</div>' +
|
||||
'<div class="ingredient-row">' +
|
||||
'<div class="ingredient-row__icon icon--carrot">🥕</div>' +
|
||||
'<span class="ingredient-row__name">芹菜</span>' +
|
||||
'<span class="ingredient-row__weight">需 2.0 斤</span>' +
|
||||
'<span class="ingredient-row__weight" data-weight-g="1000">需 2 斤</span>' +
|
||||
'<span class="ingredient-row__recorded ingredient-row__recorded--none">未录入</span>' +
|
||||
'<span class="ingredient-row__deadline">截止 13:30</span>' +
|
||||
'</div>' +
|
||||
|
||||
@@ -346,7 +346,7 @@
|
||||
<span class="task-info__value" id="taskWeightDisplay" style="color:#faad14">--</span>
|
||||
<span style="color:rgba(255,255,255,0.15);font-size:11px">|</span>
|
||||
<span class="task-info__label">已录入</span>
|
||||
<span class="task-info__value" id="taskActualDisplay" style="color:#52c41a">0.0 斤</span>
|
||||
<span class="task-info__value" id="taskActualDisplay" style="color:#52c41a">0g</span>
|
||||
</div>
|
||||
|
||||
<!-- 批次信息条 -->
|
||||
@@ -401,10 +401,9 @@
|
||||
<span class="trace-row__status" id="traceStatus"></span>
|
||||
</div>
|
||||
|
||||
<!-- 重量显示 -->
|
||||
<!-- 重量显示(存储单位:克,展示层用 formatGramForCanteen 还原"斤") -->
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<span class="weight-display__value" id="weightValue">0g</span>
|
||||
<div class="weight-display__status" id="weightStatus">请放置食材至秤台</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -418,8 +417,8 @@
|
||||
<div class="batch-stat__label">品类数</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value" id="totalWeight">0.0</div>
|
||||
<div class="batch-stat__label">总重量(斤)</div>
|
||||
<div class="batch-stat__value" id="totalWeight">0g</div>
|
||||
<div class="batch-stat__label">总重量</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value batch-stat__value--green" id="totalTraced">0</div>
|
||||
@@ -459,6 +458,26 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
/* 克重展示格式化:500→"500g",1500→"1.5kg",25000→"25kg" */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
/* 验收秤场景兼容"斤"显示:整500倍数显示"斤",否则回退到 formatGram */
|
||||
/* 6250g → "12.5斤"(2位小数),整500倍数 → "N斤",其余 → formatGram */
|
||||
function formatGramForCanteen(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g % 500 === 0) return (g / 500) + '斤';
|
||||
if (g % 250 === 0) return ((g / 500).toFixed(1)) + '斤'; /* 半斤或0.5斤倍数 */
|
||||
if (g % 50 === 0) return ((g / 500).toFixed(2)) + '斤'; /* 50g倍数(0.1斤精度) */
|
||||
return formatGram(g);
|
||||
}
|
||||
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -469,23 +488,23 @@
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// 任务单数据(含食材级应录入重量)
|
||||
// 任务单数据(含食材级应录入重量,单位:克)
|
||||
var taskDataMap = {
|
||||
'RW-20260613-001': {
|
||||
weight: 12.5,
|
||||
foods: { '黄瓜': 4.0, '土豆': 5.0, '西红柿': 3.5 }
|
||||
weight: 6250,
|
||||
foods: { '黄瓜': 2000, '土豆': 2500, '西红柿': 1750 }
|
||||
},
|
||||
'RW-20260613-002': {
|
||||
weight: 3.5,
|
||||
foods: { '胡萝卜': 2.0, '小番茄': 1.5 }
|
||||
weight: 1750,
|
||||
foods: { '胡萝卜': 1000, '小番茄': 750 }
|
||||
},
|
||||
'RW-20260613-003': {
|
||||
weight: 13.5,
|
||||
foods: { '大白菜': 8.0, '娃娃菜': 2.5, '土豆(晚餐)': 3.0 }
|
||||
weight: 6750,
|
||||
foods: { '大白菜': 4000, '娃娃菜': 1250, '土豆(晚餐)': 1500 }
|
||||
},
|
||||
'RW-20260613-004': {
|
||||
weight: 5.0,
|
||||
foods: { '西兰花': 3.0, '芹菜': 2.0 }
|
||||
weight: 2500,
|
||||
foods: { '西兰花': 1500, '芹菜': 1000 }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -585,7 +604,7 @@
|
||||
document.getElementById('taskNoDisplay').textContent = taskNo;
|
||||
var taskData = taskDataMap[taskNo];
|
||||
if (taskData) {
|
||||
document.getElementById('taskWeightDisplay').textContent = taskData.weight.toFixed(1) + ' 斤';
|
||||
document.getElementById('taskWeightDisplay').textContent = formatGramForCanteen(taskData.weight);
|
||||
initTaskList();
|
||||
}
|
||||
}
|
||||
@@ -708,19 +727,19 @@
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
|
||||
// 随机食材 + 重量动画
|
||||
// 随机食材 + 重量动画(克为单位,500-2500g 范围)
|
||||
var item = aiResults[Math.floor(Math.random() * aiResults.length)];
|
||||
var targetWeight = (Math.random() * 4 + 1).toFixed(2) * 1;
|
||||
var targetWeight = Math.round((Math.random() * 2000 + 500) / 50) * 50;
|
||||
|
||||
var anim = 0;
|
||||
var timer = setInterval(function() {
|
||||
anim += 0.15;
|
||||
anim += 75;
|
||||
if (anim >= targetWeight) {
|
||||
anim = targetWeight;
|
||||
clearInterval(timer);
|
||||
onStable(item, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = anim.toFixed(2);
|
||||
document.getElementById('weightValue').textContent = formatGramForCanteen(anim);
|
||||
}, 100);
|
||||
|
||||
// 延迟显示候选
|
||||
@@ -782,7 +801,7 @@
|
||||
matched.trace = currentTrace;
|
||||
matched.time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
||||
matched.done = true;
|
||||
showToast(foodName + ' ' + currentWeight.toFixed(2) + '斤 已录入(应 ' + matched.requiredWeight.toFixed(1) + '斤)', 1500, 'success');
|
||||
showToast(foodName + ' ' + formatGramForCanteen(currentWeight) + ' 已录入(应 ' + formatGramForCanteen(matched.requiredWeight) + ')', 1500, 'success');
|
||||
} else {
|
||||
// 任务单外的食材,追加新行
|
||||
var entry = {
|
||||
@@ -796,7 +815,7 @@
|
||||
done: true
|
||||
};
|
||||
batchItems.push(entry);
|
||||
showToast(foodName + ' ' + currentWeight.toFixed(2) + '斤 已录入', 1500, 'success');
|
||||
showToast(foodName + ' ' + formatGramForCanteen(currentWeight) + ' 已录入', 1500, 'success');
|
||||
}
|
||||
|
||||
renderBatchList();
|
||||
@@ -856,8 +875,8 @@
|
||||
var el = document.createElement('div');
|
||||
el.className = 'batch-item' + (item.done ? '' : ' batch-item--pending');
|
||||
|
||||
var requiredHtml = item.requiredWeight !== null ? item.requiredWeight.toFixed(1) + '斤' : '--';
|
||||
var actualHtml = item.done ? item.weight.toFixed(2) + '斤' : '--';
|
||||
var requiredHtml = item.requiredWeight !== null ? formatGramForCanteen(item.requiredWeight) : '--';
|
||||
var actualHtml = item.done ? formatGramForCanteen(item.weight) : '--';
|
||||
var statusHtml = item.done
|
||||
? '<span class="batch-item__status batch-item__status--done">已完成</span>'
|
||||
: '<span class="batch-item__status batch-item__status--pending">待称重</span>';
|
||||
@@ -890,11 +909,11 @@
|
||||
if (it.done) { totalW += it.weight; doneCount++; }
|
||||
});
|
||||
document.getElementById('totalItems').textContent = Object.keys(names).length;
|
||||
document.getElementById('totalWeight').textContent = totalW.toFixed(1);
|
||||
document.getElementById('totalWeight').textContent = formatGramForCanteen(totalW);
|
||||
document.getElementById('totalTraced').textContent = doneCount;
|
||||
// 实时更新 task-info 条中的「已录入」
|
||||
var actualEl = document.getElementById('taskActualDisplay');
|
||||
if (actualEl) actualEl.textContent = totalW.toFixed(1) + ' 斤';
|
||||
if (actualEl) actualEl.textContent = formatGramForCanteen(totalW);
|
||||
}
|
||||
|
||||
// 重新识别:清除当前状态,重新触发 AI 识别
|
||||
@@ -936,7 +955,7 @@
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.remove('detecting', 'stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--idle';
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightValue').textContent = '0g';
|
||||
document.getElementById('weightStatus').textContent = '请放置食材至秤台';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
@@ -967,7 +986,7 @@
|
||||
}
|
||||
var totalW = 0;
|
||||
batchItems.forEach(function(it) { totalW += it.weight; });
|
||||
showToast('任务单提交成功:' + batchItems.length + '类食材,共 ' + totalW.toFixed(1) + '斤', 1500, 'success');
|
||||
showToast('任务单提交成功:' + batchItems.length + '类食材,共 ' + formatGramForCanteen(totalW), 1500, 'success');
|
||||
setTimeout(function() {
|
||||
var user = document.getElementById('topUser').textContent.replace('操作员: ', '');
|
||||
window.location.href = '01-washing-tasks.html' + (user ? '?user=' + encodeURIComponent(user) : '');
|
||||
|
||||
@@ -330,10 +330,9 @@
|
||||
<span class="trace-row__status" id="traceStatus"></span>
|
||||
</div>
|
||||
|
||||
<!-- 重量显示 -->
|
||||
<!-- 重量显示(存储单位:克,展示层用 formatGramForCanteen 还原"斤") -->
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<span class="weight-display__value" id="weightValue">0g</span>
|
||||
<div class="weight-display__status" id="weightStatus">请放置食材至秤台</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -347,8 +346,8 @@
|
||||
<div class="batch-stat__label">品类数</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value" id="totalWeight">0.0</div>
|
||||
<div class="batch-stat__label">总重量(斤)</div>
|
||||
<div class="batch-stat__value" id="totalWeight">0g</div>
|
||||
<div class="batch-stat__label">总重量</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value batch-stat__value--green" id="totalTraced">0</div>
|
||||
@@ -381,6 +380,23 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
function formatGramForCanteen(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g % 500 === 0) return (g / 500) + '斤';
|
||||
if (g % 250 === 0) return ((g / 500).toFixed(1)) + '斤';
|
||||
if (g % 50 === 0) return ((g / 500).toFixed(2)) + '斤';
|
||||
return formatGram(g);
|
||||
}
|
||||
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
@@ -391,30 +407,30 @@
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// AI 识别结果库(含相似度)
|
||||
// AI 识别结果库(含相似度 + 出净率 yieldRate)
|
||||
var aiResults = [
|
||||
{ name: '土豆(净)', icon: '🥔', sim: 97 },
|
||||
{ name: '西红柿(净)', icon: '🍅', sim: 96 },
|
||||
{ name: '大白菜(净)', icon: '🥬', sim: 95 },
|
||||
{ name: '胡萝卜(净)', icon: '🥕', sim: 98 },
|
||||
{ name: '黄瓜(净)', icon: '🥒', sim: 94 },
|
||||
{ name: '娃娃菜(净)', icon: '🥬', sim: 93 }
|
||||
{ name: '土豆(净)', icon: '🥔', sim: 97, yieldRate: 90 },
|
||||
{ name: '西红柿(净)', icon: '🍅', sim: 96, yieldRate: 95 },
|
||||
{ name: '大白菜(净)', icon: '🥬', sim: 95, yieldRate: 88 },
|
||||
{ name: '胡萝卜(净)', icon: '🥕', sim: 98, yieldRate: 90 },
|
||||
{ name: '黄瓜(净)', icon: '🥒', sim: 94, yieldRate: 92 },
|
||||
{ name: '娃娃菜(净)', icon: '🥬', sim: 93, yieldRate: 88 }
|
||||
];
|
||||
|
||||
// 食材库(搜索用)
|
||||
// 食材库(搜索用,含出净率 yieldRate)
|
||||
var foodDB = [
|
||||
{ name: '土豆(净)', icon: '🥔' },
|
||||
{ name: '西红柿(净)', icon: '🍅' },
|
||||
{ name: '大白菜(净)', icon: '🥬' },
|
||||
{ name: '胡萝卜(净)', icon: '🥕' },
|
||||
{ name: '黄瓜(净)', icon: '🥒' },
|
||||
{ name: '娃娃菜(净)', icon: '🥬' },
|
||||
{ name: '茄子(净)', icon: '🍆' },
|
||||
{ name: '青椒(净)', icon: '🫑' },
|
||||
{ name: '芹菜(净)', icon: '🥬' },
|
||||
{ name: '菠菜(净)', icon: '🥬' },
|
||||
{ name: '豆角(净)', icon: '🫘' },
|
||||
{ name: '西兰花(净)', icon: '🥦' }
|
||||
{ name: '土豆(净)', icon: '🥔', yieldRate: 90 },
|
||||
{ name: '西红柿(净)', icon: '🍅', yieldRate: 95 },
|
||||
{ name: '大白菜(净)', icon: '🥬', yieldRate: 88 },
|
||||
{ name: '胡萝卜(净)', icon: '🥕', yieldRate: 90 },
|
||||
{ name: '黄瓜(净)', icon: '🥒', yieldRate: 92 },
|
||||
{ name: '娃娃菜(净)', icon: '🥬', yieldRate: 88 },
|
||||
{ name: '茄子(净)', icon: '🍆', yieldRate: 85 },
|
||||
{ name: '青椒(净)', icon: '🫑', yieldRate: 90 },
|
||||
{ name: '芹菜(净)', icon: '🥬', yieldRate: 80 },
|
||||
{ name: '菠菜(净)', icon: '🥬', yieldRate: 85 },
|
||||
{ name: '豆角(净)', icon: '🫘', yieldRate: 92 },
|
||||
{ name: '西兰花(净)', icon: '🥦', yieldRate: 88 }
|
||||
];
|
||||
|
||||
var scaleState = 'idle'; // idle | detecting | stable
|
||||
@@ -523,19 +539,19 @@
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
|
||||
// 随机食材 + 重量动画
|
||||
// 随机食材 + 重量动画(克为单位,500-2500g 范围)
|
||||
var item = aiResults[Math.floor(Math.random() * aiResults.length)];
|
||||
var targetWeight = (Math.random() * 4 + 1).toFixed(2) * 1;
|
||||
var targetWeight = Math.round((Math.random() * 2000 + 500) / 50) * 50;
|
||||
|
||||
var anim = 0;
|
||||
var timer = setInterval(function() {
|
||||
anim += 0.15;
|
||||
anim += 75;
|
||||
if (anim >= targetWeight) {
|
||||
anim = targetWeight;
|
||||
clearInterval(timer);
|
||||
onStable(item, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = anim.toFixed(2);
|
||||
document.getElementById('weightValue').textContent = formatGramForCanteen(anim);
|
||||
}, 100);
|
||||
|
||||
// 延迟显示候选
|
||||
@@ -590,7 +606,7 @@
|
||||
batchItems.push(entry);
|
||||
renderBatchList();
|
||||
updateSummary();
|
||||
showToast(entry.name + ' ' + entry.weight.toFixed(2) + '斤 已录入', 1500, 'success');
|
||||
showToast(entry.name + ' ' + formatGramForCanteen(entry.weight) + ' 已录入', 1500, 'success');
|
||||
resetScale();
|
||||
}
|
||||
|
||||
@@ -636,7 +652,7 @@
|
||||
'<div class="batch-item__name">' + item.name + '</div>' +
|
||||
'<div class="batch-item__trace">' + item.trace + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="batch-item__weight">+' + item.weight.toFixed(2) + '斤</div>' +
|
||||
'<div class="batch-item__weight">+' + formatGramForCanteen(item.weight) + '</div>' +
|
||||
'<button class="batch-item__del" onclick="deleteBatchItem(' + item.id + ')" title="删除">✖</button>';
|
||||
list.appendChild(el);
|
||||
});
|
||||
@@ -651,7 +667,7 @@
|
||||
totalW += it.weight;
|
||||
});
|
||||
document.getElementById('totalItems').textContent = Object.keys(names).length;
|
||||
document.getElementById('totalWeight').textContent = totalW.toFixed(1);
|
||||
document.getElementById('totalWeight').textContent = formatGramForCanteen(totalW);
|
||||
document.getElementById('totalTraced').textContent = batchItems.length;
|
||||
}
|
||||
|
||||
@@ -668,7 +684,7 @@
|
||||
cam.classList.remove('detecting', 'stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--idle';
|
||||
document.getElementById('statusText').textContent = '就绪,等待食材放置';
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightValue').textContent = '0g';
|
||||
document.getElementById('weightStatus').textContent = '请放置食材至秤台';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
@@ -695,7 +711,7 @@
|
||||
batchItems = [];
|
||||
renderBatchList();
|
||||
document.getElementById('totalItems').textContent = '0';
|
||||
document.getElementById('totalWeight').textContent = '0.0';
|
||||
document.getElementById('totalWeight').textContent = '0g';
|
||||
document.getElementById('totalTraced').textContent = '0';
|
||||
document.getElementById('batchCount').textContent = '0';
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
|
||||
@@ -202,8 +202,8 @@
|
||||
<div class="stat-card__value stat-card__value--blue" id="statTotal">8</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">总重量(斤)</div>
|
||||
<div class="stat-card__value" id="statWeight">33.3</div>
|
||||
<div class="stat-card__label">总重量</div>
|
||||
<div class="stat-card__value" id="statWeight">33.3 斤</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="2600">
|
||||
<span class="record-card__weight-value">5.2</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -246,7 +246,7 @@
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="1900">
|
||||
<span class="record-card__weight-value">3.8</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -268,7 +268,7 @@
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="4100">
|
||||
<span class="record-card__weight-value">8.2</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -290,7 +290,7 @@
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="1250">
|
||||
<span class="record-card__weight-value">2.5</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -312,7 +312,7 @@
|
||||
<div class="record-card__batch">LC-20260613-002</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="2150">
|
||||
<span class="record-card__weight-value">4.3</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -334,7 +334,7 @@
|
||||
<div class="record-card__batch">LC-20260613-002</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="1300">
|
||||
<span class="record-card__weight-value">2.6</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -356,7 +356,7 @@
|
||||
<div class="record-card__batch">LC-20260613-003</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="1600">
|
||||
<span class="record-card__weight-value">3.2</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -378,7 +378,7 @@
|
||||
<div class="record-card__batch">LC-20260613-003</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<div class="record-card__weight" data-weight-g="1750">
|
||||
<span class="record-card__weight-value">3.5</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
@@ -403,6 +403,34 @@
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* === 规格工具函数(从 spec-utils-reference.md 内联) === */
|
||||
function formatGram(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g >= 1000 && g % 1000 === 0) return (g / 1000) + 'kg';
|
||||
if (g >= 1000) return (g / 1000).toFixed(1) + 'kg';
|
||||
return g + 'g';
|
||||
}
|
||||
function formatGramForCanteen(g) {
|
||||
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
|
||||
g = Number(g);
|
||||
if (g % 500 === 0) return (g / 500) + '斤';
|
||||
if (g % 250 === 0) return ((g / 500).toFixed(1)) + '斤';
|
||||
if (g % 50 === 0) return ((g / 500).toFixed(2)) + '斤';
|
||||
return formatGram(g);
|
||||
}
|
||||
/* 历史卡片重量渲染(从 data-weight-g 读克数,展示"X 斤") */
|
||||
function renderHistoryWeights() {
|
||||
var nodes = document.querySelectorAll('.record-card__weight[data-weight-g]');
|
||||
nodes.forEach(function(el) {
|
||||
var g = parseInt(el.getAttribute('data-weight-g'), 10);
|
||||
var formatted = formatGramForCanteen(g);
|
||||
el.querySelector('.record-card__weight-value').textContent = formatted.replace(/斤$/, '');
|
||||
el.querySelector('.record-card__weight-unit').textContent = formatted.indexOf('斤') !== -1 ? '斤' : '';
|
||||
});
|
||||
}
|
||||
renderHistoryWeights();
|
||||
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
|
||||
Reference in New Issue
Block a user