diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 0b1c274..0a81896 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -1,4 +1,4 @@ -_Last synced: 2026-08-05 | Base commit: `4fd44f1`_ +_Last synced: 2026-08-05 | Base commit: `d00208a`_ # 记忆索引 | 文件 | 描述 | 类型 | 最后更新 | diff --git a/.claude/memory/lint_report.md b/.claude/memory/lint_report.md index f031663..9b01543 100644 --- a/.claude/memory/lint_report.md +++ b/.claude/memory/lint_report.md @@ -2,12 +2,12 @@ name: 记忆健康检查报告 description: memory-lint 最新一次执行的检查结果与待处理项 type: lint -last_updated: 2026-08-06 +last_updated: 2026-08-05 --- # 记忆健康检查报告 -> _执行时间: 2026-08-06 | Base commit: `6a505f2` | Last synced: 2026-08-06_ +> _执行时间: 2026-08-05 | Base commit: `d00208a` | Last synced: 2026-08-05_ ## 健康概览 @@ -18,20 +18,11 @@ last_updated: 2026-08-06 | 引用存在性检测(3A) | ✅ | 0 | 0 | | 双链对称性检测(3B) | ✅ | 0 | 0 | | 内容矛盾检测 | ✅ | — | 0 | -| 过期检测 | ⚠ | — | 4 待处理 | -| 可推断内容污染 | ✅ | — | 0 | -| 索引日期一致性 | ✅ | 1 | — | +| 过期检测 | ⚠ | — | 2 待处理 | +| 可推断内容污染 | ⚠ | — | 1 待处理 | +| frontmatter 完整性 | ⚠ | — | 4 待处理 | -**AUTO-FIX 已执行 1 项 | NEED-HUMAN 待处理 4 项** - ---- - -## AUTO-FIX 已执行清单 - -### [AUTO-FIX] MEMORY.md 索引日期与实际文件日期不一致 - -- **问题**:`MEMORY.md` 表格中 `project_progress.md` 显示「最后更新 2026-07-24」,实际文件已更新至 2026-08-06;头部 `_Last synced_` 锚点为 `aa3d439`(2026-08-03),落后于当前 HEAD `6a505f2`。 -- **修复**:刷新 `_Last synced_` 为 `2026-08-06 | 6a505f2`;表格中 `project_progress.md` 日期更新为 `2026-08-06`。 +**AUTO-FIX 已执行 0 项 | NEED-HUMAN 待处理 7 项** --- @@ -39,27 +30,34 @@ last_updated: 2026-08-06 ### [ERROR] 严重过期 — user_profile.md -- **文件**:`user_profile.md`(last_updated: 2026-04-21,距今 107 天,超过 90 天 ERROR 阈值) +- **文件**:`user_profile.md`(last_updated: 2026-04-21,距今 106 天,超过 90 天 ERROR 阈值) - **建议**:确认用户角色/偏好是否有变化,执行 `/memory-update` 更新。 -### [WARN] 过期 — project_overview.md - -- **文件**:`project_overview.md`(last_updated: 2026-05-14,距今 84 天) -- **建议**:确认技术栈、三端架构描述是否需要更新。 - -### [WARN] 过期 — decisions.md - -- **文件**:`decisions.md`(last_updated: 2026-06-15,距今 52 天) -- **建议**:确认是否有新增关键决策需要记录。 - ### [WARN] 过期 — feedback.md -- **文件**:`feedback.md`(last_updated: 2026-06-15,距今 52 天) +- **文件**:`feedback.md`(last_updated: 2026-06-15,距今 51 天,超 30 天 WARN 阈值) - **建议**:确认是否有新的协作规范需要记录。 +### [WARN] 可推断内容污染 — project_progress.md + +- **文件**:`project_progress.md` 第 110~145 行「近期里程碑」区段 +- **问题**:含具体文件路径列表(如 `canteen-nutrition-scale/02-dish-display.html`、`prod-clean-order`/`sup-clean-external`/`sup-clean-internal`)和 commit message 风格描述,按 memory-update 规则「Git 历史(可用 git log 查)」不应写入记忆文件 +- **建议**:保留里程碑结论,删除具体文件路径与 commit message 流水,让用户能通过 `git log` 自查细节。 + +### [WARN] frontmatter 缺失 — 4 个文件 + +- **文件**: + - `decisions.md` — 无 frontmatter + - `feedback.md` — 无 frontmatter + - `project_overview.md` — 无 frontmatter + - `user_profile.md` — 无 frontmatter +- **问题**:按 memory-update 规则,记忆文件应有 `name/description/type/last_updated/commit` 五字段 frontmatter;目前仅 `project_progress.md` 与 `lint_report.md` 有 frontmatter,其余四文件以一级标题起首 +- **建议**:判断是否需要补全 frontmatter 以便跨会话检索。MEMORY.md 索引已含类型与日期,可视为冗余备份;如无需统一可忽略。 + --- ## 交叉引用验证清单 -- `decisions.md` → `[[feedback.md#PAD 终端页面内禁止可见滚动条]]` ✅ 目标存在,双向闭环 -- `feedback.md` → `[[decisions.md#餐厅终端设备 PAD 居中外壳设计模式]]` ✅ 目标存在,双向闭环 +- `decisions.md#餐厅终端设备 PAD 居中外壳设计模式` → `[[feedback.md#PAD 终端页面内禁止可见滚动条]]` ✅ 目标存在,双向闭环 +- `feedback.md#PAD 终端页面内禁止可见滚动条` → `[[decisions.md#餐厅终端设备 PAD 居中外壳设计模式]]` ✅ 目标存在,双向闭环 +- `decisions.md#规格体系统一化改造` → `[[feedback.md]]` ✅ 目标存在(占位引用「暂无直接关联条目」) diff --git a/.claude/memory/project_progress.md b/.claude/memory/project_progress.md index fbe4618..2c8ddfc 100644 --- a/.claude/memory/project_progress.md +++ b/.claude/memory/project_progress.md @@ -3,7 +3,7 @@ name: 项目进度 description: 各模块原型开发进度与里程碑 type: project last_updated: 2026-08-05 -commit: 4fd44f1 +commit: d00208a --- # 项目进度 @@ -105,6 +105,7 @@ Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、 - 覆盖模块:18 个(含三端+其他终端) ## 近期里程碑 +- 2026-08-05:餐线营养秤菜品展示页新增视频演示模式 + 净菜订单/申领三页统一订单金额字段(commit d00208a)——①`canteen-nutrition-scale/02-dish-display.html` 新增演示模式切换 pill(常规/视频)与视频模式布局(上视频+下信息卡,680px 16:9 视频播放器含播放按钮/控制条,下方菜品名+营养指标信息卡),通过 `main-area.mode-video` 状态类控制 CSS 布局切换;②`web-admin/nutrition` 净菜三页(`prod-clean-order`/`sup-clean-external`/`sup-clean-internal`)统一增加订单金额字段:订单明细 `lines[]` 每项新增 `price` 与 `subtotal` 字段、列表表头新增「订单金额」列、弹窗移除「汇总预览」表格改为底部灰底「订单总金额」栏(`#orderTotalAmount`/`#extTotalAmount`/`#claimTotalAmount`)、新增 `calcOrderTotal(o)` 与 `formatMoney(n)` 工具函数(`¥1,234.5` 千分位 + 最多 2 位小数)。 - 2026-08-05:规格体系统一化改造阶段四(设备端)+ 阶段五(辅助页)全部完成(commit 4fd44f1)——阶段四覆盖设备端 9 任务 14 文件:①毛菜验收秤 `verifyType` 字段(weight/count)+ UNIT_OPTIONS 分类;②毛菜加工秤 4 页存储层斤→克、`formatGram` 展示层兼容、`yieldRate` 字段、04-history 语法错误修复;③净菜加工秤设置页删除"重量单位"select、滚刀块 cm→mm;④净菜包装贴标秤 3 页(最复杂)5 个工具函数内联、`cutSpec`/`packSpec` 全部结构化、`portions`→`packSize` 重命名;⑤净菜组配秤 3 页工具函数内联 + `localStorage['dish_specs_v1']` 跨页共享 + `resolvePackSpec` 兜底。阶段五覆盖辅助页 3 任务 6 文件:①营养质控 3 页(confirmSync 真落地写入餐品总库、菜品下拉跨页共享、同步日志驱动);②分析页字典语义修正(`sup-process-analysis` 切配线"切丝"→"丝"形状名词、新增独立 `pack_method` 字典;`sup-biz-analysis` 投料/产出吨→kg 对齐);③净菜进柜 `prod-proc-cabinet` 的 `spec` 多形态字符串结构化(含决策点 #10:`'6份/箱'` 理解为 6 个份型单位装一箱)。新增权威源 `doc/spec-utils-reference.md`、改造任务清单与研发同步说明。阶段四+五完成标志着规格体系统一化五个阶段全部完成。 - 2026-08-05:体重管理新增 `eat-exercise-detail-v2.html`(饮食运动详情 v2);西安中心应急大屏迭代至 `index-v4.html`。 - 2026-08-06:营养管理后台「批量充值」从 `din-user-mgmt.html` 弹窗抽离为独立原型页 `din-user-recharge.html`(用户筛选/候选用户表/充值配置区/汇总栏),原页面清理相关 CSS/JS/HTML 共 ≈800 行,操作栏「批量充值」按钮改为 `` 跳转;`index.html` SITE_MAP 在「用户管理」后追加入口。同日 `din-user-account.html` 充值弹窗支付方式精简为微信/现金/支付宝三种;小程序 `me-account.html` 姓名行增加可编辑态(与手机号行一致)。 diff --git a/.claude/memory/screenshots/din-dish-add-pricing-after.png b/.claude/memory/screenshots/din-dish-add-pricing-after.png new file mode 100644 index 0000000..6f830a0 Binary files /dev/null and b/.claude/memory/screenshots/din-dish-add-pricing-after.png differ diff --git a/.claude/memory/screenshots/din-dish-add-pricing-v2.png b/.claude/memory/screenshots/din-dish-add-pricing-v2.png new file mode 100644 index 0000000..c757488 Binary files /dev/null and b/.claude/memory/screenshots/din-dish-add-pricing-v2.png differ diff --git a/web-admin/nutrition/din-dish-add.html b/web-admin/nutrition/din-dish-add.html index b8e6992..8994347 100644 --- a/web-admin/nutrition/din-dish-add.html +++ b/web-admin/nutrition/din-dish-add.html @@ -208,21 +208,41 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei .input-with-btn{display:flex;gap:8px;align-items:center;} .input-with-btn .form-input{flex:1;min-width:0;} .input-with-btn__btn{flex-shrink:0;height:36px;font-size:var(--font-sm);white-space:nowrap;} +/* 食材成本后的小问号图标 */ +.cost-help-icon{flex-shrink:0;width:22px;height:22px;border-radius:50%;border:1px solid var(--border);color:var(--text-secondary);font-size:var(--font-xs);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;background:#fff;transition:all .15s;user-select:none;} +.cost-help-icon:hover{border-color:var(--primary);color:var(--primary);} +/* 食材成本缺失提示 */ +.cost-missing-hint{margin-top:6px;font-size:var(--font-xs);color:#FA541C;line-height:1.4;display:none;} +.cost-missing-hint.is-visible{display:block;} -/* 成本核算:成本价行 + 小问号弹窗 */ -.cost-result{display:flex;align-items:center;gap:8px;margin-top:12px;font-size:var(--font-sm);color:var(--text-secondary);} -.cost-result__value{font-size:var(--font-base);font-weight:600;color:var(--primary);} -.cost-result__hint{font-size:var(--font-xs);color:var(--text-placeholder);margin-left:6px;} -.cost-help{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:var(--primary);color:#fff;font-size:10px;cursor:pointer;user-select:none;margin-left:4px;} -.cost-detail-pop{position:absolute;display:none;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:0 4px 16px rgba(0,0,0,.12);padding:14px 16px;z-index:50;min-width:340px;font-size:var(--font-sm);top:100%;left:0;margin-top:6px;} -.cost-detail-pop.open{display:block;} -.cost-detail-pop__title{font-weight:600;color:var(--text-primary);margin-bottom:10px;font-size:var(--font-sm);} -.cost-detail-pop__table{width:100%;border-collapse:collapse;} -.cost-detail-pop__table th,.cost-detail-pop__table td{padding:6px 8px;font-size:var(--font-xs);text-align:left;border-bottom:1px solid #F0F0F0;} -.cost-detail-pop__table th{color:var(--text-secondary);font-weight:500;background:#FAFBFC;} -.cost-detail-pop__table tr:last-child td{border-bottom:none;} -.cost-detail-pop__total{margin-top:10px;padding-top:8px;border-top:1px solid var(--border);display:flex;justify-content:space-between;font-size:var(--font-sm);} -.cost-detail-pop__total strong{color:var(--primary);} +/* 成本核算:左右布局(左食材明细+总成本,右成本价+状态+应用按钮) */ +.cost-result{display:flex;gap:16px;margin-top:12px;padding:14px 16px;background:#FAFBFC;border:1px solid var(--border);border-radius:var(--radius-md);font-size:var(--font-sm);color:var(--text-secondary);align-items:stretch;} +.cost-result__left{flex:1;min-width:0;display:flex;flex-direction:column;gap:10px;} +.cost-result__right{flex-shrink:0;width:240px;display:flex;flex-direction:column;gap:14px;padding-left:16px;border-left:1px dashed var(--border);} +.cost-result__head{display:flex;flex-direction:column;gap:8px;} +.cost-result__price-line{display:flex;align-items:baseline;gap:4px;flex-wrap:wrap;} +.cost-result__value{font-size:22px;font-weight:600;color:var(--primary);line-height:1.2;} +.cost-result__hint{font-size:var(--font-xs);color:var(--text-placeholder);} +.cost-result__apply{width:100%;height:32px;font-size:var(--font-sm);margin-top:auto;} +/* 核算状态徽标 */ +.cost-status{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:11px;font-size:var(--font-xs);font-weight:500;line-height:1.4;width:fit-content;} +.cost-status::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;} +.cost-status--missing{background:#FFF1F0;color:#FA541C;} +.cost-status--success{background:#F6FFED;color:#52C41A;} +/* 食材成本明细块(直接展示,非弹窗) */ +.cost-detail-block{display:flex;flex-direction:column;gap:8px;} +.cost-detail-block__title{font-weight:600;color:var(--text-primary);font-size:var(--font-sm);} +.cost-detail-block__table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;} +.cost-detail-block__table th,.cost-detail-block__table td{padding:8px 12px;font-size:var(--font-xs);text-align:left;border-bottom:1px solid #F0F0F0;} +.cost-detail-block__table th{color:var(--text-secondary);font-weight:500;background:#FAFBFC;} +.cost-detail-block__table tr:last-child td{border-bottom:none;} +.cost-detail-block__table th:nth-child(2),.cost-detail-block__table th:nth-child(3),.cost-detail-block__table th:nth-child(4), +.cost-detail-block__table td:nth-child(2),.cost-detail-block__table td:nth-child(4){text-align:right;} +.cost-detail-block__table td:nth-child(3){text-align:right;padding:6px 12px;} +.cost-detail__unit-input{width:90px;height:28px;padding:2px 8px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-xs);text-align:right;color:var(--text-primary);} +.cost-detail__unit-input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.1);} +.cost-detail-block__total{display:flex;justify-content:flex-end;align-items:center;gap:6px;padding-top:6px;font-size:var(--font-sm);} +.cost-detail-block__total strong{color:var(--primary);font-size:var(--font-base);font-weight:600;} /* 宣传视频列表(多视频) */ .promo-video-list{display:flex;flex-direction:column;gap:10px;margin-top:10px;} @@ -337,10 +357,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
1基本档案
2餐品构成
-
3烹饪工艺
-
4味道档案
-
5口感档案
-
6色泽档案
+
3餐品定价
+
4烹饪工艺
+
5味道档案
+
6口感档案
+
7色泽档案
@@ -431,7 +452,7 @@ var INNOV = innovId ? INNOV_MAP[innovId] : null; var initTab = parseInt(urlParams.get('tab')) || 0; var currentStep = 0; -var totalSteps = 6; +var totalSteps = 7; if(initTab >= 0 && initTab < totalSteps) currentStep = initTab; /* 适配人群数据 */ var crowdList = []; @@ -452,42 +473,6 @@ var STEP_HTML = [
👥 适配人群
-
💰 餐品定价
-
-
-
-
- -
- - -
-
-
- -
📐 餐品规格
- - - - - - - -
规格(克重/份型)餐品售价(元/份)净菜包售价(元/份)外售净菜包默认操作
350g/标准份
15.0010.00
下架
默认
200g/小份
10.007.00
下架
默认
+ 添加规格
📷 菜品展示
@@ -556,7 +541,53 @@ var STEP_HTML = [
`, - // Step 2: 烹饪工艺 + // Step 2: 餐品定价(含规格表 + 成本核算,从原 Step 0 拆出,2026-08-05) + `
💰 餐品定价
+
+
+ +
+ + + ? +
+
⚠ 食材价格缺失,请补全食材基础定价
+
+
+
+
+ +
📐 餐品规格
+ + + + + +
规格(克重/份型)餐品售价(元/份)净菜包售价(元/份)外售净菜包默认操作
+ 添加规格
`, + + // Step 3: 烹饪工艺 `
🍳 烹饪工艺
@@ -568,7 +599,7 @@ var STEP_HTML = [
`, - // Step 3: 味道档案 + // Step 4: 味道档案 `
👅 味道档案
@@ -581,7 +612,7 @@ var STEP_HTML = [
`, - // Step 4: 口感档案 + // Step 5: 口感档案 `
🫦 口感档案
@@ -593,7 +624,7 @@ var STEP_HTML = [
`, - // Step 5: 色泽档案 + // Step 6: 色泽档案 `
🎨 色泽档案
@@ -623,7 +654,7 @@ function renderStep(){ function lockConvertStep(){ var c=document.getElementById('stepContent'); c.classList.remove('step-readonly'); - var LOCKED=[1,3,4,5]; + var LOCKED=[1,4,5,6]; if(LOCKED.indexOf(currentStep)===-1) return; c.classList.add('step-readonly'); var tip=document.createElement('div'); @@ -760,97 +791,142 @@ function triggerPromoVideoUpload(idx){ } } -/* 餐品定价:成本核算 + 应用到称重基础价 */ +/* 餐品定价:成本核算 + 应用到食材成本 */ function calcCostPrice(){ - var ingredientCost = parseFloat(document.getElementById('ingredientCostInput') && document.getElementById('ingredientCostInput').value) || 0; - var cookedWeight = parseFloat(document.getElementById('cookedWeightInput') && document.getElementById('cookedWeightInput').value) || 0; - if(cookedWeight <= 0){ - cookedWeight = 350; - } - if(ingredientCost <= 0){ - ingredientCost = 12.00; - } - /* 每百克成本 */ - var per100gCost = ingredientCost / cookedWeight * 100; var row = document.getElementById('costResultRow'); + /* toggle:已显示则隐藏,再次点击才重新核算 */ + if(row && row.style.display !== 'none'){ + row.style.display = 'none'; + return; + } + /* 食材来源:Step 1 餐品构成中的食材表 #ingredientTbody,若无则用演示数据 */ + var rows = document.querySelectorAll('#ingredientTbody tr'); + var hasRealIngredient = rows.length > 0 && !(rows.length === 1 && rows[0].querySelector('td[colspan]')); + var items = []; + if(!hasRealIngredient){ + items = [ + {code:'ING-001', name:'鸡胸肉', amount:120, unitPrice:25.00, priceDate:'2026-08-04'}, + {code:'ING-014', name:'西兰花', amount:80, unitPrice:10.00, priceDate:'2026-08-03'}, + {code:'ING-027', name:'橄榄油', amount:10, unitPrice:35.00, priceDate:'2026-08-01'}, + {code:'ING-031', name:'食盐', amount:3, unitPrice:6.00, priceDate:'2026-07-28'} + ]; + } else { + rows.forEach(function(r, i){ + var tds = r.querySelectorAll('td'); + if(tds.length < 3) return; + var name = tds[0] ? tds[0].textContent : ''; + var amount = parseFloat(tds[2] ? tds[2].textContent : 0) || 0; + /* 简化原型:单价按食材类型粗略给(主材25元/kg / 辅材12元/kg / 调料8元/kg) */ + var typeText = tds[3] ? tds[3].textContent : ''; + var unitPrice = 25; + if(typeText.indexOf('辅材')>=0) unitPrice = 12; + else if(typeText.indexOf('调料')>=0) unitPrice = 8; + items.push({code:'ING-' + String(i+1).padStart(3,'0'), name:name, amount:amount, unitPrice:unitPrice, priceDate:'2026-08-05'}); + }); + } + /* 按 100g 餐品等比缩放:scale = 100 / Σ原用量,缩放后所有用量合计 = 100g */ + var sumAmount = 0; + items.forEach(function(d){ sumAmount += d.amount || 0; }); + var scale = sumAmount > 0 ? 100 / sumAmount : 0; + /* 渲染明细表 */ + var total = 0; + var html = ''; + if(items.length === 0 || scale === 0){ + html = '暂无食材'; + } else { + items.forEach(function(d, idx){ + var scaledAmount = d.amount * scale; + var subtotal = scaledAmount * d.unitPrice / 1000; + total += subtotal; + html += '' + + ''+d.code+'' + + ''+d.name+'' + + ''+scaledAmount.toFixed(2)+'' + + '' + + ''+d.priceDate+'' + + ''+subtotal.toFixed(2)+'' + + ''; + }); + } + /* 显示成本核算结果区 */ if(row) row.style.display = 'flex'; - var display = document.getElementById('costPriceDisplay'); - if(display) display.textContent = per100gCost.toFixed(2); - /* 同步弹窗明细 */ + /* 食材明细表 */ var detailTbody = document.getElementById('costDetailTbody'); - if(detailTbody){ - var rows = document.querySelectorAll('#ingredientTbody tr'); - var hasRealIngredient = rows.length > 0 && !(rows.length === 1 && rows[0].querySelector('td[colspan]')); - var html = ''; - var total = 0; - if(!hasRealIngredient){ - /* 演示食材明细 */ - var demo = [ - {name:'鸡胸肉', amount:120, unitPrice:25.00}, - {name:'西兰花', amount:80, unitPrice:10.00}, - {name:'橄榄油', amount:10, unitPrice:35.00}, - {name:'食盐', amount:3, unitPrice:6.00} - ]; - demo.forEach(function(d){ - var subtotal = d.amount * d.unitPrice / 1000; - total += subtotal; - html += ''+d.name+''+d.amount+''+d.unitPrice.toFixed(2)+''+subtotal.toFixed(2)+''; - }); - } else { - rows.forEach(function(r){ - var tds = r.querySelectorAll('td'); - if(tds.length < 3) return; - var name = tds[0] ? tds[0].textContent : ''; - var amount = parseFloat(tds[2] ? tds[2].textContent : 0) || 0; - /* 简化原型:单价按食材类型粗略给(主材18元/kg / 辅材12元/kg / 调料8元/kg) */ - var typeText = tds[3] ? tds[3].textContent : ''; - var unitPrice = 18; - if(typeText.indexOf('辅材')>=0) unitPrice = 12; - else if(typeText.indexOf('调料')>=0) unitPrice = 8; - var subtotal = amount * unitPrice / 1000; - total += subtotal; - html += ''+name+''+amount+''+unitPrice.toFixed(2)+''+subtotal.toFixed(2)+''; - }); + if(detailTbody) detailTbody.innerHTML = html; + /* 食材总成本 */ + var totalEl = document.getElementById('costDetailTotal'); + if(totalEl) totalEl.textContent = total.toFixed(2); + /* 隐藏食材成本缺失提示 */ + var hintEl = document.getElementById('ingredientCostHint'); + if(hintEl) hintEl.classList.remove('is-visible'); + /* 重新判断核算状态(任一单价<=0 视为数据缺失) */ + updateCostStatus(); +} + +/* 根据食材明细表中的单价判断核算状态,并更新成本价和徽标 */ +function updateCostStatus(){ + var inputs = document.querySelectorAll('#costDetailTbody .cost-detail__unit-input'); + var hasMissing = false; + var total = 0; + inputs.forEach(function(inp){ + var price = parseFloat(inp.value) || 0; + if(price <= 0) hasMissing = true; + var tr = inp.closest('tr'); + if(tr){ + var amountCell = tr.querySelector('td.cost-detail__amount'); + var amount = parseFloat(amountCell && amountCell.textContent) || 0; + total += amount * price / 1000; } - detailTbody.innerHTML = html || '暂无食材'; - var totalEl = document.getElementById('costDetailTotal'); - if(totalEl) totalEl.textContent = total.toFixed(2); - var weightEl = document.getElementById('costDetailWeight'); - if(weightEl) weightEl.textContent = cookedWeight; - var per100gEl = document.getElementById('costDetailPer100g'); - if(per100gEl) per100gEl.textContent = per100gCost.toFixed(2); + }); + /* 无食材时也视为数据缺失 */ + if(inputs.length === 0) hasMissing = true; + /* 更新食材总成本 */ + var totalEl = document.getElementById('costDetailTotal'); + if(totalEl) totalEl.textContent = total.toFixed(2); + /* 更新成本价(数据缺失时显示 —) */ + var display = document.getElementById('costPriceDisplay'); + if(display) display.textContent = hasMissing ? '—' : total.toFixed(2); + /* 更新状态徽标 */ + var badge = document.getElementById('costStatusBadge'); + if(badge){ + badge.textContent = hasMissing ? '数据缺失' : '核算成功'; + badge.className = 'cost-status ' + (hasMissing ? 'cost-status--missing' : 'cost-status--success'); } } -/* 小问号弹窗显示/切换 */ -function toggleCostDetailPop(e){ - if(e) e.stopPropagation(); - var pop = document.getElementById('costDetailPop'); - if(pop) pop.classList.toggle('open'); +/* 食材成本明细:单价修改后联动重算小计、总成本、成本价、状态徽标 */ +function recalcCostRow(inputEl){ + var newPrice = parseFloat(inputEl.value) || 0; + /* 重算该行小计 */ + var tr = inputEl.closest('tr'); + if(!tr) return; + var amountCell = tr.querySelector('td.cost-detail__amount'); + var subtotalCell = tr.querySelector('td.cost-detail__subtotal'); + if(!amountCell || !subtotalCell) return; + var amount = parseFloat(amountCell.textContent) || 0; + var subtotal = amount * newPrice / 1000; + subtotalCell.textContent = subtotal.toFixed(2); + /* 统一交给 updateCostStatus 重算总成本、成本价、状态徽标 */ + updateCostStatus(); } -/* 应用成本价到称重基础价(叠加利润率) */ +/* 小问号弹窗显示/切换 — 已移除(成本明细改为直接展示) */ + +/* 应用食材总成本到食材成本 input,并根据毛利润自动算出称重基础价 */ function applyCostToBasePrice(){ - var cost = parseFloat(document.getElementById('costPriceDisplay') && document.getElementById('costPriceDisplay').textContent) || 0; - if(cost <= 0){ return; } + var total = parseFloat(document.getElementById('costDetailTotal') && document.getElementById('costDetailTotal').textContent) || 0; + if(total <= 0){ return; } + /* 1. 回填食材成本 input */ + var costInput = document.getElementById('ingredientCostInput'); + if(costInput) costInput.value = total.toFixed(2); + /* 2. 根据毛利润自动算出称重基础价 */ var profitInput = document.getElementById('profitRateInput'); var rate = profitInput ? (parseFloat(profitInput.value)||0) : 0; - var base = cost * (1 + rate/100); + var base = total * (1 + rate/100); var baseInput = document.getElementById('basePriceInput'); - if(baseInput){ - baseInput.value = base.toFixed(2); - } + if(baseInput) baseInput.value = base.toFixed(2); } -/* 文档点击关闭弹窗 */ -document.addEventListener('click', function(e){ - var pop = document.getElementById('costDetailPop'); - if(!pop || !pop.classList.contains('open')) return; - var help = e.target.closest('.cost-help'); - var inPop = e.target.closest('.cost-detail-pop'); - if(!help && !inPop) pop.classList.remove('open'); -}); - /* 营养指标自动计算(根据食材汇总) */ function recalcAddNutri(){ /* 简化原型:根据食材数量生成示例值 */ @@ -1121,14 +1197,9 @@ function fillEditData(){ var d = EDIT_DATA; if(currentStep===0){ - // 基本档案 - 输入顺序:餐品名称(0), 过敏原(1), [定价: basePrice(2), profitRate(3), ingredientCost(4)] + // 基本档案 - 输入顺序:餐品名称(0), 过敏原(1)(定价与规格已拆到 Step 2) if(inputs[0]) inputs[0].value = d.name; if(inputs[1]) inputs[1].value = d.allergen; - if(d.pricing){ - if(inputs[2]) inputs[2].value = d.pricing.basePrice||''; - if(inputs[3]) inputs[3].value = d.pricing.profitRate||150; - if(inputs[4]) inputs[4].value = (d.pricing.ingredientCost||0).toFixed(2); - } // selects 顺序:分类、餐次、宝塔、标签、上架状态 setSelectByText(selects[0], d.category); setSelectByText(selects[1], d.meal); @@ -1140,20 +1211,6 @@ function fillEditData(){ crowdList = d.crowd.map(function(c){return {name:c.name,level:c.level};}); renderCrowdRows(); } - // 预填规格表 - var specBody = container.querySelector('#specTable tbody'); - if(specBody){ - specBody.innerHTML = ''; - d.specs.forEach(function(s){ - var tr = document.createElement('tr'); - var saleActive = s.cleanSale === true; - var saleHtml = '
'+(saleActive?'上架':'下架')+'
'; - var portionVal = s.portion || s.spec || ''; - var gramVal = s.baseGram!==undefined?s.baseGram:(s.weight!==undefined?s.weight:''); - tr.innerHTML = '
'+saleHtml+'删除'; - specBody.appendChild(tr); - }); - } // 预填宣传视频 if(d.promoVideos && d.promoVideos.length>0){ promoVideoList = d.promoVideos.map(function(v, i){ @@ -1186,25 +1243,46 @@ function fillEditData(){ }); } } else if(currentStep===2){ + // 餐品定价 - 输入顺序:称重基础价(0), 毛利润(1), 食材成本(2) + if(d.pricing){ + if(inputs[0]) inputs[0].value = d.pricing.basePrice||''; + if(inputs[1]) inputs[1].value = d.pricing.profitRate||''; + if(inputs[2]) inputs[2].value = (d.pricing.ingredientCost||0).toFixed(2); + } + // 预填规格表 + var specBody = container.querySelector('#specTable tbody'); + if(specBody){ + specBody.innerHTML = ''; + d.specs.forEach(function(s){ + var tr = document.createElement('tr'); + var saleActive = s.cleanSale === true; + var saleHtml = '
'+(saleActive?'上架':'下架')+'
'; + var portionVal = s.portion || s.spec || ''; + var gramVal = s.baseGram!==undefined?s.baseGram:(s.weight!==undefined?s.weight:''); + tr.innerHTML = '
'+saleHtml+'删除'; + specBody.appendChild(tr); + }); + } + } else if(currentStep===3){ // 烹饪工艺 - 字段顺序:主烹饪法、辅烹饪法、火候、时长、温度、步骤数 var c = d.cook; var vals = [c.mainMethod, c.subMethod, c.heat, c.duration, c.temp, c.steps]; inputs.forEach(function(inp, i){ if(vals[i]!==undefined) inp.value = vals[i]; }); var ta = container.querySelector('textarea'); if(ta) ta.value = c.desc||''; - } else if(currentStep===3){ + } else if(currentStep===4){ // 味道档案 var t = d.taste; var tvals = [t.sour, t.sweet, t.bitter, t.spicy, t.salty, t.smell, t.flavorScore]; inputs.forEach(function(inp, i){ if(tvals[i]!==undefined) inp.value = tvals[i]; }); if(selects[0]) setSelectByValue(selects[0], t.flavorGrade); - } else if(currentStep===4){ + } else if(currentStep===5){ // 口感档案 var tx = d.texture; var txvals = [tx.hardness, tx.sticky, tx.crispy, tx.smooth, tx.elastic, tx.textureScore]; inputs.forEach(function(inp, i){ if(txvals[i]!==undefined) inp.value = txvals[i]; }); if(selects[0]) setSelectByValue(selects[0], tx.textureGrade); - } else if(currentStep===5){ + } else if(currentStep===6){ // 色泽档案 var cl = d.color; var cvals = [cl.mainColor, cl.uniformity, cl.gloss, cl.freshness, cl.appearance, cl.colorScore]; diff --git a/web-admin/nutrition/din-dish-all.html b/web-admin/nutrition/din-dish-all.html index 36dffd8..3f4fe35 100644 --- a/web-admin/nutrition/din-dish-all.html +++ b/web-admin/nutrition/din-dish-all.html @@ -210,21 +210,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} /* 营养指标 - 自动计算提示 */ .nutri-tip{font-size:var(--font-xs);color:#D46B08;background:#FFF7E6;border:1px solid #FFD591;border-radius:var(--radius-sm);padding:6px 12px;margin-bottom:12px;display:inline-flex;align-items:center;gap:4px;} -/* 成本核算:成本价行 + 小问号弹窗 */ -.cost-result{display:flex;align-items:center;gap:8px;margin-top:10px;font-size:var(--font-sm);color:var(--text-secondary);position:relative;} -.cost-result__value{font-size:var(--font-base);font-weight:600;color:var(--primary);} -.cost-result__hint{font-size:var(--font-xs);color:var(--text-placeholder);margin-left:6px;} -.cost-result__apply{margin-left:auto;} -.cost-help{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:var(--primary);color:#fff;font-size:10px;cursor:pointer;user-select:none;margin-left:4px;} -.cost-detail-pop{position:absolute;left:0;top:24px;display:none;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:0 4px 16px rgba(0,0,0,.12);padding:14px 16px;z-index:50;min-width:340px;font-size:var(--font-sm);} -.cost-detail-pop.open{display:block;} -.cost-detail-pop__title{font-weight:600;color:var(--text-primary);margin-bottom:10px;font-size:var(--font-sm);} -.cost-detail-pop__table{width:100%;border-collapse:collapse;} -.cost-detail-pop__table th,.cost-detail-pop__table td{padding:6px 8px;font-size:var(--font-xs);text-align:left;border-bottom:1px solid #F0F0F0;} -.cost-detail-pop__table th{color:var(--text-secondary);font-weight:500;background:#FAFBFC;} -.cost-detail-pop__table tr:last-child td{border-bottom:none;} -.cost-detail-pop__total{margin-top:10px;padding-top:8px;border-top:1px solid var(--border);display:flex;justify-content:space-between;font-size:var(--font-sm);} -.cost-detail-pop__total strong{color:var(--primary);} +/* 成本核算样式已移除(详情页不再展示成本核算,编辑/新增页 din-dish-add.html 自带样式) */ /* 宣传视频多卡片 */ .promo-video-card{background:#FAFBFC;border:1px solid #F0F0F0;border-radius:var(--radius-md);overflow:hidden;display:flex;flex-direction:column;} @@ -369,10 +355,11 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
🍽️ 基本档案
🧩 餐品构成
-
🍳 烹饪工艺
-
👅 味道档案
-
🫦 口感档案
-
🎨 色泽档案
+
💰 餐品定价
+
🍳 烹饪工艺
+
👅 味道档案
+
🫦 口感档案
+
🎨 色泽档案
@@ -470,21 +457,27 @@ function renderDetail(){ const s = document.getElementById('detailStatus'); s.textContent = d.status; s.className='tag tag-'+(d.status==='启用'?'green':'gray'); const body = document.getElementById('detailBody'); - const renderers = [renderBasic,renderComposition,renderCook,renderTaste,renderTexture,renderColor]; + const renderers = [renderBasic,renderComposition,renderPricing,renderCook,renderTaste,renderTexture,renderColor]; body.innerHTML = renderers[currentTab](d); } +/* 删除餐品:二次确认 */ +function deleteDish(idx){ + var d = DISHES[idx]; + if(!d) return; + var msg = '确定删除餐品「'+d.name+'」('+d.id+')吗?\n删除后不可恢复,请谨慎操作。'; + if(!confirm(msg)) return; + /* 原型演示:仅从内存数据中移除并刷新列表 */ + DISHES.splice(idx,1); + if(currentDish >= DISHES.length) currentDish = DISHES.length - 1; + if(currentDish < 0) currentDish = 0; + currentTab = 0; + renderDishList(); + renderDetail(); +} + function renderBasic(d){ - var specRows = (d.specs||[]).map(function(s){ - var statusHtml = s.isDefault ? '默认' : ''; - var cleanPrice = (s.cleanPrice!==undefined && s.cleanPrice!==null) ? parseFloat(s.cleanPrice).toFixed(2) : '—'; - var cleanSaleActive = s.cleanSale === true; - var cleanSaleHtml = '
'+(cleanSaleActive?'上架':'下架')+'
'; - var gramText=s.baseGram?(parseFloat(s.baseGram)+'g'):''; - var specViewHtml='
'+gramText+'/'+(s.portion||'—')+'
'; - return ''+specViewHtml+''+s.price.toFixed(2)+''+cleanPrice+''+cleanSaleHtml+''+statusHtml+''; - }).join(''); - return `
基本信息 编辑
+ return `
餐品编号
${d.id}
餐品分类
${d.category}
@@ -497,9 +490,6 @@ function renderBasic(d){
👥 适配人群
${(d.crowd||[]).map(function(c){return '
'+c.name+''+levelTag(c.level)+'
';}).join('')}
${d.crowdNote?'
⚠️ '+d.crowdNote+'
':''}
-
📐 餐品规格(共 ${(d.specs||[]).length} 项)
- ${specRows}
规格餐品售价(元/份)净菜包售价(元/份)外售净菜包状态
- ${renderPricingSection(d)}
📷 菜品展示
图片
@@ -513,32 +503,32 @@ function renderBasic(d){ ${renderPromoVideosSection(d)}`; } -/* 餐品定价分区(普通 info-section 样式,含成本核算) */ +/* 餐品定价 Tab:餐品规格 + 餐品定价(从基本档案拆出,2026-08-05) */ +function renderPricing(d){ + var specRows = (d.specs||[]).map(function(s){ + var statusHtml = s.isDefault ? '默认' : ''; + var cleanPrice = (s.cleanPrice!==undefined && s.cleanPrice!==null) ? parseFloat(s.cleanPrice).toFixed(2) : '—'; + var cleanSaleActive = s.cleanSale === true; + var cleanSaleHtml = '
'+(cleanSaleActive?'上架':'下架')+'
'; + var gramText=s.baseGram?(parseFloat(s.baseGram)+'g'):''; + var specViewHtml='
'+gramText+'/'+(s.portion||'—')+'
'; + return ''+specViewHtml+''+s.price.toFixed(2)+''+cleanPrice+''+cleanSaleHtml+''+statusHtml+''; + }).join(''); + return renderPricingSection(d) + + '
📐 餐品规格(共 '+(d.specs||[]).length+' 项)
' + + ''+specRows+'
规格餐品售价(元/份)净菜包售价(元/份)外售净菜包状态
'; +} + +/* 餐品定价分区(普通 info-section 样式,详情页只展示字段,成本核算仅在编辑/新增页) */ function renderPricingSection(d){ var p = d.pricing || {}; var ingredientCost = (p.ingredientCost||0).toFixed(2); - var cookedWeight = d.cookedWeight||0; - var per100g = cookedWeight>0 ? (p.ingredientCost/cookedWeight*100) : (p.per100gCost||0); - var per100gStr = per100g.toFixed(2); var profitRate = p.profitRate||0; - return '
💰 餐品定价 编辑
' + return '
💰 餐品定价 编辑
' + '
' + '
称重基础价(元/100g)
¥ '+(p.basePrice||'—')+'
' - + '
利润率
'+profitRate+'%
' - + '
食材成本(元/份)
¥ '+ingredientCost+'
' - + '
成本核算
' - + '
' - + '' + '
'; } @@ -617,7 +607,7 @@ function renderNutriSection(d){ function renderCook(d){ var c = d.cook; - return '
烹饪工艺 编辑
' + return '
烹饪工艺 编辑
' + '
' + '
主烹饪法
'+c.mainMethod+'
' + '
辅烹饪法
'+c.subMethod+'
' @@ -631,7 +621,7 @@ function renderCook(d){ function renderTaste(d){ const t = d.taste; const gradeTag = t.flavorGrade==='优秀'?'green':t.flavorGrade==='良好'?'blue':t.flavorGrade==='一般'?'orange':'red'; - return `
味道档案 编辑
+ return `
味道档案 编辑
酸度 (1-5)
${t.sour}
甜度 (1-5)
${t.sweet}
@@ -647,7 +637,7 @@ function renderTaste(d){ function renderColor(d){ const c = d.color; const gradeTag = c.colorGrade==='优秀'?'green':c.colorGrade==='良好'?'blue':c.colorGrade==='一般'?'orange':'red'; - return `
色泽档案 编辑
+ return `
色泽档案 编辑
主色调
${c.mainColor}
色泽均匀度 (1-5)
${c.uniformity}
@@ -662,7 +652,7 @@ function renderColor(d){ function renderTexture(d){ const t = d.texture; const gradeTag = t.textureGrade==='优秀'?'green':t.textureGrade==='良好'?'blue':t.textureGrade==='一般'?'orange':'red'; - return `
口感档案 编辑
+ return `
口感档案 编辑
软硬度 (1-5)
${t.hardness}
黏糯度 (1-5)
${t.sticky}
@@ -723,79 +713,8 @@ function togglePromoVideo(el, dishIdx, videoIdx){ } } -/* 成本核算:计算每百克成本并展示明细 */ -function calcDetailCost(dishIdx){ - var dish = DISHES[dishIdx]; - if(!dish) return; - var ingredientCost = (dish.pricing && dish.pricing.ingredientCost) || 0; - var cookedWeight = dish.cookedWeight || 0; - if(cookedWeight <= 0){ alert('请先在「餐品构成」中填写熟重'); return; } - if(ingredientCost <= 0){ alert('当前食材成本为 0,请先录入食材'); return; } - var per100gCost = ingredientCost / cookedWeight * 100; - var row = document.getElementById('costResultRow'); - if(row) row.style.display = 'flex'; - var display = document.getElementById('costPriceDisplay'); - if(display) display.textContent = per100gCost.toFixed(2); - /* 同步弹窗明细 */ - var detailTbody = document.getElementById('costDetailTbody'); - if(detailTbody){ - var rows = dish.composition || []; - if(rows.length === 0){ - detailTbody.innerHTML = '暂无食材'; - } else { - var html = ''; - var total = 0; - rows.forEach(function(c){ - var amount = parseFloat(c.amount) || 0; - var unitPrice = 18; - if((c.type||'').indexOf('辅材')>=0) unitPrice = 12; - else if((c.type||'').indexOf('调料')>=0) unitPrice = 8; - var subtotal = amount * unitPrice / 1000; - total += subtotal; - html += ''+c.name+''+amount+''+unitPrice.toFixed(2)+''+subtotal.toFixed(2)+''; - }); - detailTbody.innerHTML = html; - var totalEl = document.getElementById('costDetailTotal'); - if(totalEl) totalEl.textContent = total.toFixed(2); - var weightEl = document.getElementById('costDetailWeight'); - if(weightEl) weightEl.textContent = cookedWeight; - var per100gEl = document.getElementById('costDetailPer100g'); - if(per100gEl) per100gEl.textContent = per100gCost.toFixed(2); - } - } -} +/* 成本核算相关函数已移除(详情页不再展示成本核算,编辑/新增页 din-dish-add.html 自带实现) */ -/* 小问号弹窗显示/切换 */ -function toggleCostDetailPop(e){ - if(e) e.stopPropagation(); - var pop = document.getElementById('costDetailPop'); - if(pop) pop.classList.toggle('open'); -} - -/* 应用成本价到称重基础价(叠加利润率) */ -function applyDetailCost(dishIdx){ - var dish = DISHES[dishIdx]; - if(!dish) return; - var display = document.getElementById('costPriceDisplay'); - var cost = parseFloat(display && display.textContent) || 0; - if(cost <= 0){ alert('请先点击成本核算'); return; } - var rate = (dish.pricing && dish.pricing.profitRate) || 0; - var base = cost * (1 + rate/100); - if(!dish.pricing) dish.pricing = {}; - dish.pricing.basePrice = base.toFixed(2); - dish.pricing.per100gCost = cost.toFixed(2); - alert('已应用到称重基础价:¥'+base.toFixed(2)+'\n(成本 ¥'+cost.toFixed(2)+' × (1 + '+rate+'%))'); - renderDetail(); -} - -/* 文档点击关闭弹窗 */ -document.addEventListener('click', function(e){ - var pop = document.getElementById('costDetailPop'); - if(!pop || !pop.classList.contains('open')) return; - var help = e.target.closest('.cost-help'); - var inPop = e.target.closest('.cost-detail-pop'); - if(!help && !inPop) pop.classList.remove('open'); -}); (function(){var a=document.querySelector('.sidebar-item.active');if(!a)return;var s=a.closest('.sidebar-subgroup');if(s)s.classList.add('open');})(); // 初始化 diff --git a/web-admin/nutrition/din-dish-own.html b/web-admin/nutrition/din-dish-own.html index 8f1bdfe..0213aa1 100644 --- a/web-admin/nutrition/din-dish-own.html +++ b/web-admin/nutrition/din-dish-own.html @@ -210,21 +210,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} /* 营养指标 - 自动计算提示 */ .nutri-tip{font-size:var(--font-xs);color:#D46B08;background:#FFF7E6;border:1px solid #FFD591;border-radius:var(--radius-sm);padding:6px 12px;margin-bottom:12px;display:inline-flex;align-items:center;gap:4px;} -/* 成本核算:成本价行 + 小问号弹窗 */ -.cost-result{display:flex;align-items:center;gap:8px;margin-top:10px;font-size:var(--font-sm);color:var(--text-secondary);position:relative;} -.cost-result__value{font-size:var(--font-base);font-weight:600;color:var(--primary);} -.cost-result__hint{font-size:var(--font-xs);color:var(--text-placeholder);margin-left:6px;} -.cost-result__apply{margin-left:auto;} -.cost-help{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:var(--primary);color:#fff;font-size:10px;cursor:pointer;user-select:none;margin-left:4px;} -.cost-detail-pop{position:absolute;left:0;top:24px;display:none;background:#fff;border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:0 4px 16px rgba(0,0,0,.12);padding:14px 16px;z-index:50;min-width:340px;font-size:var(--font-sm);} -.cost-detail-pop.open{display:block;} -.cost-detail-pop__title{font-weight:600;color:var(--text-primary);margin-bottom:10px;font-size:var(--font-sm);} -.cost-detail-pop__table{width:100%;border-collapse:collapse;} -.cost-detail-pop__table th,.cost-detail-pop__table td{padding:6px 8px;font-size:var(--font-xs);text-align:left;border-bottom:1px solid #F0F0F0;} -.cost-detail-pop__table th{color:var(--text-secondary);font-weight:500;background:#FAFBFC;} -.cost-detail-pop__table tr:last-child td{border-bottom:none;} -.cost-detail-pop__total{margin-top:10px;padding-top:8px;border-top:1px solid var(--border);display:flex;justify-content:space-between;font-size:var(--font-sm);} -.cost-detail-pop__total strong{color:var(--primary);} +/* 成本核算样式已移除(详情页不再展示成本核算,编辑/新增页 din-dish-add.html 自带样式) */ /* 宣传视频多卡片 */ .promo-video-card{background:#FAFBFC;border:1px solid #F0F0F0;border-radius:var(--radius-md);overflow:hidden;display:flex;flex-direction:column;} @@ -393,10 +379,11 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
🍽️ 基本档案
🧩 餐品构成
-
🍳 烹饪工艺
-
👅 味道档案
-
🫦 口感档案
-
🎨 色泽档案
+
💰 餐品定价
+
🍳 烹饪工艺
+
👅 味道档案
+
🫦 口感档案
+
🎨 色泽档案
@@ -494,21 +481,27 @@ function renderDetail(){ const s = document.getElementById('detailStatus'); s.textContent = d.status; s.className='tag tag-'+(d.status==='启用'?'green':'gray'); const body = document.getElementById('detailBody'); - const renderers = [renderBasic,renderComposition,renderCook,renderTaste,renderTexture,renderColor]; + const renderers = [renderBasic,renderComposition,renderPricing,renderCook,renderTaste,renderTexture,renderColor]; body.innerHTML = renderers[currentTab](d); } +/* 删除餐品:二次确认 */ +function deleteDish(idx){ + var d = DISHES[idx]; + if(!d) return; + var msg = '确定删除餐品「'+d.name+'」('+d.id+')吗?\n删除后不可恢复,请谨慎操作。'; + if(!confirm(msg)) return; + /* 原型演示:仅从内存数据中移除并刷新列表 */ + DISHES.splice(idx,1); + if(currentDish >= DISHES.length) currentDish = DISHES.length - 1; + if(currentDish < 0) currentDish = 0; + currentTab = 0; + renderDishList(); + renderDetail(); +} + function renderBasic(d){ - var specRows = (d.specs||[]).map(function(s){ - var statusHtml = s.isDefault ? '默认' : ''; - var cleanPrice = (s.cleanPrice!==undefined && s.cleanPrice!==null) ? parseFloat(s.cleanPrice).toFixed(2) : '—'; - var cleanSaleActive = s.cleanSale === true; - var cleanSaleHtml = '
'+(cleanSaleActive?'上架':'下架')+'
'; - var gramText=s.baseGram?(parseFloat(s.baseGram)+'g'):''; - var specViewHtml='
'+gramText+'/'+(s.portion||'—')+'
'; - return ''+specViewHtml+''+s.price.toFixed(2)+''+cleanPrice+''+cleanSaleHtml+''+statusHtml+''; - }).join(''); - return '
基本信息 编辑
' + return '
' + '
' + '
餐品编号
'+d.id+'
' + '
餐品分类
'+d.category+'
' @@ -522,9 +515,6 @@ function renderBasic(d){ + '
'+(d.crowd||[]).map(function(c){return '
'+c.name+''+levelTag(c.level)+'
';}).join('')+'
' + (d.crowdNote?'
⚠️ '+d.crowdNote+'
':'') + '
' - + '
📐 餐品规格(共 '+(d.specs||[]).length+' 项)
' - + ''+specRows+'
规格餐品售价(元/份)净菜包售价(元/份)外售净菜包状态
' - + renderPricingSection(d) + '
📷 菜品展示
' + '
' + '
图片
' @@ -536,32 +526,32 @@ function renderBasic(d){ + renderPromoVideosSection(d); } -/* 餐品定价分区(普通 info-section 样式,含成本核算) */ +/* 餐品定价 Tab:餐品规格 + 餐品定价(从基本档案拆出,2026-08-05) */ +function renderPricing(d){ + var specRows = (d.specs||[]).map(function(s){ + var statusHtml = s.isDefault ? '默认' : ''; + var cleanPrice = (s.cleanPrice!==undefined && s.cleanPrice!==null) ? parseFloat(s.cleanPrice).toFixed(2) : '—'; + var cleanSaleActive = s.cleanSale === true; + var cleanSaleHtml = '
'+(cleanSaleActive?'上架':'下架')+'
'; + var gramText=s.baseGram?(parseFloat(s.baseGram)+'g'):''; + var specViewHtml='
'+gramText+'/'+(s.portion||'—')+'
'; + return ''+specViewHtml+''+s.price.toFixed(2)+''+cleanPrice+''+cleanSaleHtml+''+statusHtml+''; + }).join(''); + return renderPricingSection(d) + + '
📐 餐品规格(共 '+(d.specs||[]).length+' 项)
' + + ''+specRows+'
规格餐品售价(元/份)净菜包售价(元/份)外售净菜包状态
'; +} + +/* 餐品定价分区(普通 info-section 样式,详情页只展示字段,成本核算仅在编辑/新增页) */ function renderPricingSection(d){ var p = d.pricing || {}; var ingredientCost = (p.ingredientCost||0).toFixed(2); - var cookedWeight = d.cookedWeight||0; - var per100g = cookedWeight>0 ? (p.ingredientCost/cookedWeight*100) : (p.per100gCost||0); - var per100gStr = per100g.toFixed(2); var profitRate = p.profitRate||0; - return '
💰 餐品定价 编辑
' + return '
💰 餐品定价 编辑
' + '
' + '
称重基础价(元/100g)
¥ '+(p.basePrice||'—')+'
' - + '
利润率
'+profitRate+'%
' - + '
食材成本(元/份)
¥ '+ingredientCost+'
' - + '
成本核算
' - + '
' - + '' + '
'; } @@ -640,7 +630,7 @@ function renderNutriSection(d){ function renderCook(d){ var c = d.cook; - return '
烹饪工艺 编辑
' + return '
烹饪工艺 编辑
' + '
' + '
主烹饪法
'+c.mainMethod+'
' + '
辅烹饪法
'+c.subMethod+'
' @@ -654,7 +644,7 @@ function renderCook(d){ function renderTaste(d){ const t = d.taste; const gradeTag = t.flavorGrade==='优秀'?'green':t.flavorGrade==='良好'?'blue':t.flavorGrade==='一般'?'orange':'red'; - return `
味道档案 编辑
+ return `
味道档案 编辑
酸度 (1-5)
${t.sour}
甜度 (1-5)
${t.sweet}
@@ -670,7 +660,7 @@ function renderTaste(d){ function renderColor(d){ const c = d.color; const gradeTag = c.colorGrade==='优秀'?'green':c.colorGrade==='良好'?'blue':c.colorGrade==='一般'?'orange':'red'; - return `
色泽档案 编辑
+ return `
色泽档案 编辑
主色调
${c.mainColor}
色泽均匀度 (1-5)
${c.uniformity}
@@ -685,7 +675,7 @@ function renderColor(d){ function renderTexture(d){ const t = d.texture; const gradeTag = t.textureGrade==='优秀'?'green':t.textureGrade==='良好'?'blue':t.textureGrade==='一般'?'orange':'red'; - return `
口感档案 编辑
+ return `
口感档案 编辑
软硬度 (1-5)
${t.hardness}
黏糯度 (1-5)
${t.sticky}
@@ -745,79 +735,8 @@ function togglePromoVideo(el, dishIdx, videoIdx){ } } -/* 成本核算:计算每百克成本并展示明细 */ -function calcDetailCost(dishIdx){ - var dish = DISHES[dishIdx]; - if(!dish) return; - var ingredientCost = (dish.pricing && dish.pricing.ingredientCost) || 0; - var cookedWeight = dish.cookedWeight || 0; - if(cookedWeight <= 0){ alert('请先在「餐品构成」中填写熟重'); return; } - if(ingredientCost <= 0){ alert('当前食材成本为 0,请先录入食材'); return; } - var per100gCost = ingredientCost / cookedWeight * 100; - var row = document.getElementById('costResultRow'); - if(row) row.style.display = 'flex'; - var display = document.getElementById('costPriceDisplay'); - if(display) display.textContent = per100gCost.toFixed(2); - /* 同步弹窗明细 */ - var detailTbody = document.getElementById('costDetailTbody'); - if(detailTbody){ - var rows = dish.composition || []; - if(rows.length === 0){ - detailTbody.innerHTML = '暂无食材'; - } else { - var html = ''; - var total = 0; - rows.forEach(function(c){ - var amount = parseFloat(c.amount) || 0; - var unitPrice = 18; - if((c.type||'').indexOf('辅材')>=0) unitPrice = 12; - else if((c.type||'').indexOf('调料')>=0) unitPrice = 8; - var subtotal = amount * unitPrice / 1000; - total += subtotal; - html += ''+c.name+''+amount+''+unitPrice.toFixed(2)+''+subtotal.toFixed(2)+''; - }); - detailTbody.innerHTML = html; - var totalEl = document.getElementById('costDetailTotal'); - if(totalEl) totalEl.textContent = total.toFixed(2); - var weightEl = document.getElementById('costDetailWeight'); - if(weightEl) weightEl.textContent = cookedWeight; - var per100gEl = document.getElementById('costDetailPer100g'); - if(per100gEl) per100gEl.textContent = per100gCost.toFixed(2); - } - } -} +/* 成本核算相关函数已移除(详情页不再展示成本核算,编辑/新增页 din-dish-add.html 自带实现) */ -/* 小问号弹窗显示/切换 */ -function toggleCostDetailPop(e){ - if(e) e.stopPropagation(); - var pop = document.getElementById('costDetailPop'); - if(pop) pop.classList.toggle('open'); -} - -/* 应用成本价到称重基础价(叠加利润率) */ -function applyDetailCost(dishIdx){ - var dish = DISHES[dishIdx]; - if(!dish) return; - var display = document.getElementById('costPriceDisplay'); - var cost = parseFloat(display && display.textContent) || 0; - if(cost <= 0){ alert('请先点击成本核算'); return; } - var rate = (dish.pricing && dish.pricing.profitRate) || 0; - var base = cost * (1 + rate/100); - if(!dish.pricing) dish.pricing = {}; - dish.pricing.basePrice = base.toFixed(2); - dish.pricing.per100gCost = cost.toFixed(2); - alert('已应用到称重基础价:¥'+base.toFixed(2)+'\n(成本 ¥'+cost.toFixed(2)+' × (1 + '+rate+'%))'); - renderDetail(); -} - -/* 文档点击关闭弹窗 */ -document.addEventListener('click', function(e){ - var pop = document.getElementById('costDetailPop'); - if(!pop || !pop.classList.contains('open')) return; - var help = e.target.closest('.cost-help'); - var inPop = e.target.closest('.cost-detail-pop'); - if(!help && !inPop) pop.classList.remove('open'); -}); (function(){var a=document.querySelector('.sidebar-item.active');if(!a)return;var s=a.closest('.sidebar-subgroup');if(s)s.classList.add('open');})(); // 初始化 diff --git a/web-admin/nutrition/din-ingredient-add.html b/web-admin/nutrition/din-ingredient-add.html index 2215060..4ee4e13 100644 --- a/web-admin/nutrition/din-ingredient-add.html +++ b/web-admin/nutrition/din-ingredient-add.html @@ -296,6 +296,12 @@ var STEP_HTML = [
+
+
+ + 元/100g +
+
@@ -727,7 +733,7 @@ if(initTab >= 0 && initTab < totalSteps) currentStep = initTab; /* 编辑模式 mock 数据(与 din-ingredient.html 的 FOODS 结构一致) */ var EDIT_FOODS = { - '0101001':{name:'粳米(生)',code:'0101001',alias:'大米',firstClass:'谷薯类',secondClass:'稻米',edible:'97',waterRate:'13.3',dataSource:'0',vegTypes:[{name:'洗净米',yieldRate:'97'},{name:'免淘米',yieldRate:'95'}], + '0101001':{name:'粳米(生)',code:'0101001',alias:'大米',firstClass:'谷薯类',secondClass:'稻米',edible:'97',waterRate:'13.3',dataSource:'0',basePrice:'3.5',vegTypes:[{name:'洗净米',yieldRate:'97'},{name:'免淘米',yieldRate:'95'}], crowd:[{name:'全人群',level:'推荐'},{name:'糖尿病人群',level:'适量'}], nutri:{calorie:346,protein:7.4,fat:0.6,carbohydrate:77.2,dietary_fiber:0.7, vitamin_a:0,vitamin_b1:0.11,vitamin_b2:0.02,vitamin_b6:0.12,vitamin_b12:0,vitamin_c:0,vitamin_d:0,vitamin_e:1.01,vitamin_k:0,niacin:1.6,folic_acid:3.8,biotin:0,pantothenic_acid:0.6,total_choline:0, @@ -738,7 +744,7 @@ var EDIT_FOODS = { color:{main:'乳白色',grade:'优',desc:'色泽洁白,半透明,颗粒饱满',cause:'碾磨去皮后淀粉体暴露'}, taste:{main:'淡味',sub:'微涩',intensity:'弱',desc:'生米无明显味道,煮熟后清香'}, texture:{main:'软糯',chew:'易',digest:'中',desc:'米粒饱满,质地坚硬,煮后软糯',cook:'水米比1:1.2,浸泡30分钟口感更佳'}}, - '0301001':{name:'猪里脊(生)',code:'0301001',alias:'里脊肉',firstClass:'畜禽肉类',secondClass:'猪肉',edible:'100',waterRate:'73.3',dataSource:'0',vegTypes:[{name:'去筋膜',yieldRate:'92'},{name:'切丁',yieldRate:'88',cutSpec:{shape:'丁(中)',length:10,width:10,height:10,technique:'直刀'}}], + '0301001':{name:'猪里脊(生)',code:'0301001',alias:'里脊肉',firstClass:'畜禽肉类',secondClass:'猪肉',edible:'100',waterRate:'73.3',dataSource:'0',basePrice:'18.8',vegTypes:[{name:'去筋膜',yieldRate:'92'},{name:'切丁',yieldRate:'88',cutSpec:{shape:'丁(中)',length:10,width:10,height:10,technique:'直刀'}}], crowd:[{name:'成人',level:'推荐'},{name:'儿童',level:'推荐'},{name:'高血脂人群',level:'适量'}], nutri:{calorie:143,protein:20.2,fat:7.1,carbohydrate:0.7,dietary_fiber:0, vitamin_a:6,vitamin_b1:0.54,vitamin_b2:0.1,vitamin_b6:0.27,vitamin_b12:0.6,vitamin_c:0,vitamin_d:0.2,vitamin_e:0.34,vitamin_k:0,niacin:5.3,folic_acid:5,biotin:0,pantothenic_acid:0.5,total_choline:0, @@ -749,7 +755,7 @@ var EDIT_FOODS = { color:{main:'粉红色',grade:'优',desc:'色泽粉嫩,纹理清晰',cause:'肌红蛋白含量适中'}, taste:{main:'鲜味',sub:'微甜',intensity:'中',desc:'肉质鲜嫩,口感清甜'}, texture:{main:'嫩滑',chew:'易',digest:'中',desc:'纤维细腻,口感软嫩',cook:'腌制后煎炒口感最佳'}}, - '0501001':{name:'鸡蛋(生)',code:'0501001',alias:'鲜鸡蛋',firstClass:'蛋类',secondClass:'鸡蛋',edible:'88',waterRate:'74.1',dataSource:'0',vegTypes:[], + '0501001':{name:'鸡蛋(生)',code:'0501001',alias:'鲜鸡蛋',firstClass:'蛋类',secondClass:'鸡蛋',edible:'88',waterRate:'74.1',dataSource:'0',basePrice:'6.5',vegTypes:[], crowd:[{name:'全人群',level:'推荐'},{name:'婴幼儿',level:'推荐'},{name:'老年人',level:'推荐'}], nutri:{calorie:144,protein:13.3,fat:8.8,carbohydrate:2.8,dietary_fiber:0, vitamin_a:234,vitamin_b1:0.11,vitamin_b2:0.27,vitamin_b6:0.13,vitamin_b12:1.0,vitamin_c:0,vitamin_d:2.2,vitamin_e:1.84,vitamin_k:0,niacin:0.2,folic_acid:113,biotin:0,pantothenic_acid:1.3,total_choline:0, @@ -760,7 +766,7 @@ var EDIT_FOODS = { color:{main:'金黄色',grade:'优',desc:'蛋壳浅褐,蛋黄金黄饱满',cause:'蛋黄富含叶黄素和类胡萝卜素'}, taste:{main:'鲜味',sub:'蛋香',intensity:'中',desc:'蛋白柔和,蛋黄浓郁'}, texture:{main:'嫩滑',chew:'易',digest:'易',desc:'水煮蛋弹嫩,炒蛋软嫩,蒸蛋细腻',cook:'蒸蛋口感最软,适合各年龄段'}}, - '0201001':{name:'菠菜(生)',code:'0201001',alias:'波斯菜',firstClass:'蔬菜类',secondClass:'叶菜类',edible:'89',waterRate:'91.2',dataSource:'0',vegTypes:[{name:'去根',yieldRate:'90'},{name:'切段',yieldRate:'88',cutSpec:{shape:'段',length:50,width:'',height:'',technique:'直刀'}}], + '0201001':{name:'菠菜(生)',code:'0201001',alias:'波斯菜',firstClass:'蔬菜类',secondClass:'叶菜类',edible:'89',waterRate:'91.2',dataSource:'0',basePrice:'4.2',vegTypes:[{name:'去根',yieldRate:'90'},{name:'切段',yieldRate:'88',cutSpec:{shape:'段',length:50,width:'',height:'',technique:'直刀'}}], crowd:[{name:'全人群',level:'推荐'},{name:'肾结石人群',level:'慎食'}], nutri:{calorie:28,protein:2.6,fat:0.3,carbohydrate:4.5,dietary_fiber:1.7, vitamin_a:487,vitamin_b1:0.04,vitamin_b2:0.11,vitamin_b6:0.1,vitamin_b12:0,vitamin_c:32,vitamin_d:0,vitamin_e:1.74,vitamin_k:0,niacin:0.6,folic_acid:110,biotin:0,pantothenic_acid:0.2,total_choline:0, @@ -771,7 +777,7 @@ var EDIT_FOODS = { color:{main:'深绿色',grade:'优',desc:'叶片翠绿有光泽,根部紫红',cause:'叶绿素含量高'}, taste:{main:'清甜',sub:'微涩',intensity:'弱',desc:'清爽微甜带涩'}, texture:{main:'嫩脆',chew:'易',digest:'易',desc:'叶片嫩滑,茎部脆嫩',cook:'焯水去草酸后口感最佳'}}, - '0601001':{name:'豆腐(北)',code:'0601001',alias:'北豆腐',firstClass:'豆及豆制品',secondClass:'豆制品',edible:'100',waterRate:'82.8',dataSource:'1',vegTypes:[{name:'切块',yieldRate:'96',cutSpec:{shape:'块(中)',length:30,width:30,height:20,technique:'直刀'}},{name:'切丁',yieldRate:'94',cutSpec:{shape:'丁(中)',length:10,width:10,height:10,technique:'直刀'}}], + '0601001':{name:'豆腐(北)',code:'0601001',alias:'北豆腐',firstClass:'豆及豆制品',secondClass:'豆制品',edible:'100',waterRate:'82.8',dataSource:'1',basePrice:'5.0',vegTypes:[{name:'切块',yieldRate:'96',cutSpec:{shape:'块(中)',length:30,width:30,height:20,technique:'直刀'}},{name:'切丁',yieldRate:'94',cutSpec:{shape:'丁(中)',length:10,width:10,height:10,technique:'直刀'}}], crowd:[{name:'全人群',level:'推荐'},{name:'高血压人群',level:'推荐'},{name:'痛风人群',level:'适量'},{name:'老年人',level:'推荐'},{name:'术后恢复',level:'推荐'}], nutri:{calorie:98,protein:12.2,fat:4.8,carbohydrate:1.5,dietary_fiber:0.4, vitamin_a:0,vitamin_b1:0.07,vitamin_b2:0.04,vitamin_b6:0.05,vitamin_b12:0,vitamin_c:0,vitamin_d:0,vitamin_e:2.71,vitamin_k:0,niacin:0.1,folic_acid:37,biotin:0,pantothenic_acid:0.1,total_choline:0, @@ -800,6 +806,7 @@ function initEditMode(){ formData['edible'] = food.edible || ''; formData['waterRate'] = food.waterRate || ''; formData['dataSource'] = food.dataSource === 0 ? '0' : String(food.dataSource); + formData['basePrice'] = food.basePrice || ''; // 填充食物分类 selectedCategory.l1 = food.firstClass; @@ -955,6 +962,7 @@ function confirmCopy(){ formData['edible'] = food.edible || ''; formData['waterRate'] = food.waterRate || ''; formData['dataSource'] = food.dataSource === 0 ? '0' : String(food.dataSource || '0'); + formData['basePrice'] = food.basePrice || ''; // 净菜类型 vegTypes = food.vegTypes ? food.vegTypes.slice() : []; diff --git a/web-admin/nutrition/din-ingredient.html b/web-admin/nutrition/din-ingredient.html index 2b86451..8f31990 100644 --- a/web-admin/nutrition/din-ingredient.html +++ b/web-admin/nutrition/din-ingredient.html @@ -373,7 +373,7 @@ function formatCutSpec(cut){ // 食物数据(对齐 yx_mater_base_material 表结构) const FOODS = [ - {name:'粳米(生)',code:'0101001',alias:'大米',firstClass:'谷薯类',secondClass:'稻米',icon:'🌾',edible:'100',waterRate:'13.3',dataSource:0,vegTypes:[{name:'洗净米',yieldRate:'97'},{name:'免淘米',yieldRate:'95'}], + {name:'粳米(生)',code:'0101001',alias:'大米',firstClass:'谷薯类',secondClass:'稻米',icon:'🌾',edible:'100',waterRate:'13.3',dataSource:0,basePrice:'3.5',vegTypes:[{name:'洗净米',yieldRate:'97'},{name:'免淘米',yieldRate:'95'}], nutri:{calorie:346,protein:7.4,fat:0.8,carbohydrate:77.2,dietary_fiber:0.6, vitamin_a:0,vitamin_b1:0.16,vitamin_b2:0.08,vitamin_b6:0.16,vitamin_b12:0,vitamin_c:0,vitamin_d:0,vitamin_e:0.46,vitamin_k:0,niacin:1.9,folic_acid:3.8,biotin:0,pantothenic_acid:0.6,total_choline:0, ca:13,phosphorus:110,kalium:103,mg:34,na:3.8,fe:2.3,zn:1.7,se:2.23,cu:0.3,mn:1.29,iodine:0, @@ -383,7 +383,7 @@ const FOODS = [ color:{main:'白色',desc:'乳白色,半透明,颗粒饱满',cause:'淀粉主体,含少量蛋白质',grade:'优',crowd:[{name:'成人',level:'推荐'},{name:'老年人',level:'推荐'},{name:'儿童',level:'推荐'}],note:'—'}, taste:{main:'淡味',sub:'—',intensity:'弱',desc:'清淡,煮熟后微甜,无异味',crowd:[{name:'全人群',level:'推荐'},{name:'糖尿病人群',level:'适量'}],note:'糖尿病人群注意控制量'}, texture:{main:'软糯',chew:'易',digest:'易',desc:'煮熟后软糯有嚼劲,易于消化',crowd:[{name:'全人群',level:'推荐'},{name:'老年人',level:'推荐'},{name:'术后恢复',level:'推荐'}],cook:'熬粥质地更软,适合老年人'}}, - {name:'猪里脊肉',code:'0201001',alias:'里脊',firstClass:'畜禽肉类',secondClass:'猪肉',icon:'🥩',edible:'100',waterRate:'71.4',dataSource:0,vegTypes:[{name:'肉片',yieldRate:'95',cutSpec:{shape:'片(中)',length:3,width:1,height:5,technique:'平刀'}},{name:'肉丁',yieldRate:'92',cutSpec:{shape:'丁(中)',length:10,width:10,height:10,technique:'直刀'}},{name:'肉块',yieldRate:'97',cutSpec:{shape:'块(中)',length:30,width:30,height:30,technique:'直刀'}}], + {name:'猪里脊肉',code:'0201001',alias:'里脊',firstClass:'畜禽肉类',secondClass:'猪肉',icon:'🥩',edible:'100',waterRate:'71.4',dataSource:0,basePrice:'18.8',vegTypes:[{name:'肉片',yieldRate:'95',cutSpec:{shape:'片(中)',length:3,width:1,height:5,technique:'平刀'}},{name:'肉丁',yieldRate:'92',cutSpec:{shape:'丁(中)',length:10,width:10,height:10,technique:'直刀'}},{name:'肉块',yieldRate:'97',cutSpec:{shape:'块(中)',length:30,width:30,height:30,technique:'直刀'}}], nutri:{calorie:155,protein:20.2,fat:7.9,carbohydrate:0.7,dietary_fiber:0, vitamin_a:6,vitamin_b1:0.54,vitamin_b2:0.1,vitamin_b6:0.33,vitamin_b12:0.6,vitamin_c:0,vitamin_d:0,vitamin_e:0.34,vitamin_k:0,niacin:5.3,folic_acid:3,biotin:0,pantothenic_acid:0.8,total_choline:0, ca:6,phosphorus:196,kalium:305,mg:25,na:43.2,fe:1.5,zn:2.3,se:11.97,cu:0.09,mn:0.01,iodine:0, @@ -393,7 +393,7 @@ const FOODS = [ color:{main:'粉红色',desc:'鲜嫩粉红,有光泽,纹理细腻',cause:'肌红蛋白含量适中',grade:'优',crowd:[{name:'成人',level:'推荐'},{name:'老年人',level:'适量'}],note:'老年人消化负担稍重'}, taste:{main:'鲜味',sub:'淡咸',intensity:'中',desc:'肉香鲜美,脂肪少,口感纯正',crowd:[{name:'成人',level:'推荐'},{name:'儿童',level:'推荐'},{name:'肾病人群',level:'慎食'}],note:'肾病人群需控制蛋白质摄入'}, texture:{main:'嫩滑',chew:'中',digest:'中',desc:'肌纤维细腻,炒制后滑嫩,少筋膜',crowd:[{name:'成人',level:'推荐'},{name:'老年人',level:'适量'},{name:'儿童',level:'推荐'}],cook:'老年人建议炖煮至软烂,避免大块'}}, - {name:'菠菜',code:'0701001',alias:'菠菜',firstClass:'蔬菜类',secondClass:'叶菜类',icon:'🥬',edible:'89',waterRate:'91.2',dataSource:0,vegTypes:[], + {name:'菠菜',code:'0701001',alias:'菠菜',firstClass:'蔬菜类',secondClass:'叶菜类',icon:'🥬',edible:'89',waterRate:'91.2',dataSource:0,basePrice:'4.2',vegTypes:[], nutri:{calorie:24,protein:2.6,fat:0.3,carbohydrate:3.1,dietary_fiber:1.7, vitamin_a:487,vitamin_b1:0.04,vitamin_b2:0.11,vitamin_b6:0.1,vitamin_b12:0,vitamin_c:32,vitamin_d:0,vitamin_e:1.74,vitamin_k:483,niacin:0.6,folic_acid:110,biotin:0,pantothenic_acid:0.2,total_choline:0, ca:66,phosphorus:47,kalium:311,mg:58,na:85.2,fe:2.9,zn:0.85,se:0.97,cu:0.1,mn:0.66,iodine:0, @@ -403,7 +403,7 @@ const FOODS = [ color:{main:'深绿色',desc:'翠绿鲜亮,叶片厚实,颜色均匀',cause:'叶绿素含量丰富,含胡萝卜素',grade:'优',crowd:[{name:'成人',level:'推荐'},{name:'孕妇',level:'推荐'},{name:'贫血人群',level:'推荐'}],note:'富含叶酸,孕妇适宜'}, taste:{main:'淡味',sub:'微涩',intensity:'弱',desc:'轻微涩感(草酸),汆烫后消失',crowd:[{name:'全人群',level:'推荐'},{name:'肾结石人群',level:'慎食'}],note:'肾结石人群慎食,草酸含量高'}, texture:{main:'嫩软',chew:'易',digest:'易',desc:'叶片薄嫩,焯水后柔软,汁水丰富',crowd:[{name:'全人群',level:'推荐'},{name:'老年人',level:'推荐'},{name:'婴幼儿',level:'适量'}],cook:'婴幼儿建议打泥处理,去除纤维'}}, - {name:'鸡蛋',code:'0401001',alias:'鸡蛋',firstClass:'蛋类',secondClass:'鸡蛋',icon:'🥚',edible:'88',waterRate:'74.1',dataSource:0,vegTypes:[], + {name:'鸡蛋',code:'0401001',alias:'鸡蛋',firstClass:'蛋类',secondClass:'鸡蛋',icon:'🥚',edible:'88',waterRate:'74.1',dataSource:0,basePrice:'6.5',vegTypes:[], nutri:{calorie:144,protein:13.3,fat:8.8,carbohydrate:2.8,dietary_fiber:0, vitamin_a:234,vitamin_b1:0.11,vitamin_b2:0.27,vitamin_b6:0.11,vitamin_b12:1.0,vitamin_c:0,vitamin_d:2.2,vitamin_e:1.06,vitamin_k:0,niacin:0.2,folic_acid:70,biotin:20,pantothenic_acid:1.3,total_choline:294, ca:56,phosphorus:195,kalium:154,mg:10,na:131.5,fe:2.0,zn:1.1,se:14.34,cu:0.15,mn:0.04,iodine:27.2, @@ -413,7 +413,7 @@ const FOODS = [ color:{main:'米黄色',desc:'蛋黄橙黄,蛋白透明,整体色泽亮',cause:'蛋黄富含类胡萝卜素',grade:'优',crowd:[{name:'成人',level:'推荐'},{name:'儿童',level:'推荐'},{name:'高血脂人群',level:'慎食'}],note:'高血脂人群每日不超过1个'}, taste:{main:'鲜味',sub:'淡味',intensity:'中',desc:'蛋黄浓郁,蛋白清淡,整体鲜香',crowd:[{name:'成人',level:'推荐'},{name:'儿童',level:'推荐'}],note:'优质蛋白,味道适中'}, texture:{main:'嫩滑',chew:'易',digest:'易',desc:'水煮蛋弹嫩,炒蛋软嫩,蒸蛋细腻',crowd:[{name:'全人群',level:'推荐'},{name:'婴幼儿',level:'推荐'},{name:'老年人',level:'推荐'}],cook:'蒸蛋口感最软,适合各年龄段'}}, - {name:'豆腐(北)',code:'0601001',alias:'北豆腐',firstClass:'豆及豆制品',secondClass:'豆制品',icon:'🧈',edible:'100',waterRate:'82.8',dataSource:1,vegTypes:[{name:'豆腐块',yieldRate:'98',cutSpec:{shape:'块(中)',length:30,width:30,height:20,technique:'直刀'}}], + {name:'豆腐(北)',code:'0601001',alias:'北豆腐',firstClass:'豆及豆制品',secondClass:'豆制品',icon:'🧈',edible:'100',waterRate:'82.8',dataSource:1,basePrice:'5.0',vegTypes:[{name:'豆腐块',yieldRate:'98',cutSpec:{shape:'块(中)',length:30,width:30,height:20,technique:'直刀'}}], nutri:{calorie:98,protein:12.2,fat:4.8,carbohydrate:1.5,dietary_fiber:0.4, vitamin_a:0,vitamin_b1:0.07,vitamin_b2:0.04,vitamin_b6:0.05,vitamin_b12:0,vitamin_c:0,vitamin_d:0,vitamin_e:2.71,vitamin_k:0,niacin:0.1,folic_acid:37,biotin:0,pantothenic_acid:0.1,total_choline:0, ca:138,phosphorus:97,kalium:106,mg:63,na:7.2,fe:2.5,zn:1.11,se:2.3,cu:0.27,mn:0.47,iodine:0, @@ -473,6 +473,7 @@ function renderBasicInfo(f){ html += `
可食部分
${f.edible}%
`; html += `
水分
${f.waterRate}%
`; html += `
数据来源
${sourceLabel}
`; + html += `
基础定价
${f.basePrice ? f.basePrice + ' 元/100g' : '—'}
`; let vegTypesValue; if(f.vegTypes && f.vegTypes.length > 0){ let rows = f.vegTypes.map(t=>`${t.name||'—'}${t.yieldRate!==undefined&&t.yieldRate!==''?t.yieldRate+'%':'—'}${formatCutSpec(t.cutSpec)}`).join(''); diff --git a/web-admin/nutrition/doc/CHANGELOG.md b/web-admin/nutrition/doc/CHANGELOG.md index 88b699e..5fc9fc6 100644 --- a/web-admin/nutrition/doc/CHANGELOG.md +++ b/web-admin/nutrition/doc/CHANGELOG.md @@ -1,5 +1,35 @@ # 营养用餐管理端 CHANGELOG +## [2026-08-06] feat: 由食谱快速创建订单支持餐次级人数 + 新增「生成中」状态 + +### 变更背景 +原「由食谱快速创建订单」弹窗要求用户在顶部统一填写就餐人数,但实际业务中早/午/晚/加餐各餐次人数差异较大,统一人数导致采购量核算不准。同时订单创建后系统需要根据人数智能核算采购量,该过程有耗时,原状态机无对应中间态,用户无法感知订单是否已生成完毕。 + +### 新增(`prod-clean-order.html`) +- 食谱弹窗增加「预定配送时间」字段(datetime-local),位于食谱日期右侧 +- 「当日食谱」表格新增「就餐人数」列,每个餐次独立填写人数(4 个 input) +- 新增 `validateMealDinerCounts()` 校验所选餐次人数是否填写完整 +- 新增「生成中」订单状态:青色脉冲呼吸动效 `.tag-cyan` + `@keyframes statusPulse` +- `aggregateStatus()` 增加「生成中」聚合规则:任意一行生成中即整单生成中 +- 订单状态筛选下拉框增加「生成中」选项 +- 统计卡片区增加「生成中」卡片 +- 订单操作列增加「生成完成」入口(`orderAction` 新增 `finishGenerate` 分支,流转到「备货中」) +- 食谱弹窗底部新增智能核算提示语(`.recipe-tip` 渐变蓝底提示框) + +### 调整 +- 食谱弹窗移除顶部「就餐人数」全局输入框 +- 移除「订单汇总」预览区(`#recipeOrderSummary` 整块) +- 食谱餐品份数计算逻辑改为按各餐次人数累加:同菜品跨餐次时 dinerCount 累加,weight 按总份数核算 +- 由食谱创建的订单 `lines[].status` 初始置为「生成中」(原为「备货中」) +- `submitRecipeOrder` 校验新增预定配送时间必填 + 所选餐次人数必填 +- 默认配送时间函数 `getDefaultDeliveryTime()`:食谱日期次日 10:00 +- 弹窗打开初始化逻辑移除已删除的 `recipeDinerCount`/`recipeOrderSummary` 引用 + +### 润色后提示语 +> ⏳ 系统智能核算提示:订单提交后,系统将依据您填写的各餐次就餐人数自动核算采购量并生成净菜明细,处理过程约需 1~2 分钟。生成结果与最终订单金额将在 订单列表 中实时更新,请耐心等待并关注订单状态变化。 + +--- + ## [2026-08-05] feat: 净菜订单/申领页面统一新增订单金额字段与总金额栏 ### 变更背景 diff --git a/web-admin/nutrition/prod-clean-order.html b/web-admin/nutrition/prod-clean-order.html index a0794f2..8b2c10d 100644 --- a/web-admin/nutrition/prod-clean-order.html +++ b/web-admin/nutrition/prod-clean-order.html @@ -64,6 +64,15 @@ tr.detail-row td:first-child{padding-left:44px;} .tag-red{background:#FFF1F0;color:var(--danger);} .tag-gray{background:#F5F5F5;color:#999;} .tag-purple{background:#F9F0FF;color:#722ED1;} +/* 生成中状态:脉冲呼吸动效 */ +.tag-cyan{background:#E6FFFB;color:#08979C;border:1px solid #87E8DE;} +.tag-cyan::before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;background:#13C2C2;margin-right:5px;vertical-align:middle;animation:statusPulse 1.4s ease-in-out infinite;} +@keyframes statusPulse{0%,100%{opacity:0.3;transform:scale(0.8);}50%{opacity:1;transform:scale(1.2);}} +/* 食谱创建提示语 */ +.recipe-tip{display:flex;align-items:flex-start;gap:8px;margin-top:16px;padding:12px 16px;background:linear-gradient(90deg,#E6F7FF 0%,#F0F9FF 100%);border:1px solid #91D5FF;border-radius:var(--radius-md);color:#096DD9;font-size:var(--font-sm);line-height:1.6;} +.recipe-tip__icon{font-size:18px;flex-shrink:0;line-height:1.4;} +.recipe-tip__text{flex:1;} +.recipe-tip__text b{color:#0050B3;} .link{color:var(--primary);cursor:pointer;font-size:var(--font-sm);}.link:hover{color:#40a9ff;} .pagination{display:flex;align-items:center;justify-content:flex-end;padding:16px 24px;gap:8px;border-top:1px solid var(--border);} .pagination span{font-size:var(--font-sm);color:var(--text-secondary);} @@ -212,15 +221,15 @@ tr.detail-row td.td-actions{background:#FAFBFC;box-shadow:-4px 0 8px rgba(0,0,0,
📝
8
今日订单数
+
0
生成中
4
已完成
🚚
2
配送中
-
📦
2
部分完成
-
+
~
@@ -311,26 +320,21 @@ tr.detail-row td.td-actions{background:#FAFBFC;box-shadow:-4px 0 8px rgba(0,0,0,
-
就餐人数*
- +
预定配送时间*
+
- @@ -475,6 +479,8 @@ function formatMoney(n){var v=parseFloat(n)||0;return '¥'+v.toLocaleString('zh- /* ========== 状态聚合逻辑 ========== */ function aggregateStatus(lines){ var statuses=lines.map(function(l){return l.status;}); + /* 任意一行处于生成中 → 整单生成中 */ + if(statuses.some(function(s){return s==='生成中';}))return '生成中'; if(statuses.every(function(s){return s==='已完成';}))return '已完成'; if(statuses.some(function(s){return s==='配送中';}) && !statuses.some(function(s){return s==='备货中'||s==='待配送';}))return '配送中'; if(statuses.some(function(s){return s==='待配送';}) && !statuses.some(function(s){return s==='备货中';}))return '待配送'; @@ -487,7 +493,7 @@ function aggregateStatus(lines){ } function statusTag(status){ - var map={已完成:'tag-green',配送中:'tag-blue',待配送:'tag-orange',备货中:'tag-gray','部分完成':'tag-purple'}; + var map={'生成中':'tag-cyan',已完成:'tag-green',配送中:'tag-blue',待配送:'tag-orange',备货中:'tag-gray','部分完成':'tag-purple'}; return ''+status+''; } @@ -524,7 +530,9 @@ function renderOrderTable(){ html+=''+order.creator+''; /* 操作列:状态按钮 + 详情 + 删除 */ html+=''; - if(aggStatus==='备货中'){ + if(aggStatus==='生成中'){ + html+='生成完成 '; + }else if(aggStatus==='备货中'){ html+='备货完成 '; }else if(aggStatus==='待配送'){ html+='立即配送 '; @@ -585,7 +593,8 @@ function orderAction(orderNo,action){ var order=orders.find(function(o){return o.orderNo===orderNo;}); if(!order)return; var nextStatus=''; - if(action==='completePrep')nextStatus='待配送'; + if(action==='finishGenerate')nextStatus='备货中'; + else if(action==='completePrep')nextStatus='待配送'; else if(action==='deliver')nextStatus='配送中'; else if(action==='receive')nextStatus='已完成'; if(!nextStatus)return; @@ -1030,6 +1039,15 @@ function getDishSpecForRecipe(name){ var MEAL_NAMES=['早餐','午餐','晚餐','加餐']; function getDefaultDate(){return '2026-05-20';} +/* 默认配送时间:食谱日期次日 10:00 */ +function getDefaultDeliveryTime(){ + var d=new Date(getDefaultDate()); + d.setDate(d.getDate()+1); + var yyyy=d.getFullYear(); + var mm=String(d.getMonth()+1).padStart(2,'0'); + var dd=String(d.getDate()).padStart(2,'0'); + return yyyy+'-'+mm+'-'+dd+'T10:00'; +} function getDailyMenu(dateStr){ var d=new Date(dateStr); @@ -1049,107 +1067,93 @@ function onRecipeDateChange(){ var tbody=document.getElementById('dailyMenuBody'); if(!dateVal){ dailySection.style.display='none'; - document.getElementById('recipeOrderSummary').style.display='none'; return; } var daily=getDailyMenu(dateVal); if(!daily){ - tbody.innerHTML='该日期暂无食谱数据'; + tbody.innerHTML='该日期暂无食谱数据'; dailySection.style.display='block'; - document.getElementById('recipeOrderSummary').style.display='none'; return; } var rows=''; for(var mi=0;mi<4;mi++){ var dishes=daily.meals[mi]&&daily.meals[mi][daily.dayIdx]||[]; var dishHtml=dishes.length?dishes.join('、'):''; - rows+=''+MEAL_NAMES[mi]+''+dishHtml+'' - +''; + rows+='' + +''+MEAL_NAMES[mi]+'' + +''+dishHtml+'' + +'' + +'' + +''; } tbody.innerHTML=rows; dailySection.style.display='block'; - document.getElementById('recipeOrderSummary').style.display='none'; } -function onRecipeParamChange(){ - var dateVal=document.getElementById('recipeDate').value; - var dinerCount=parseInt(document.getElementById('recipeDinerCount').value||'0',10); - var summaryDiv=document.getElementById('recipeOrderSummary'); - var tbody=document.getElementById('recipeOrderSummaryBody'); - if(!dateVal||dinerCount<=0){summaryDiv.style.display='none';return;} - var daily=getDailyMenu(dateVal); - if(!daily){summaryDiv.style.display='none';return;} - var mealIndices=[]; - document.querySelectorAll('#dailyMenuBody .meal-cb:checked').forEach(function(cb){mealIndices.push(parseInt(cb.value));}); - if(mealIndices.length===0){summaryDiv.style.display='none';return;} - var dishMap={}; - mealIndices.forEach(function(mi){ - var dayDishes=daily.meals[mi]&&daily.meals[mi][daily.dayIdx]||[]; - dayDishes.forEach(function(dishName){ - if(!dishMap[dishName])dishMap[dishName]=getDishSpecForRecipe(dishName); - }); +/* 校验所选餐次是否都已填写就餐人数,并返回缺失餐次名 */ +function validateMealDinerCounts(checkedMealIndices){ + var missing=[]; + checkedMealIndices.forEach(function(mi){ + var input=document.querySelector('#dailyMenuBody .meal-diner-input[data-meal-idx="'+mi+'"]'); + var val=input?input.value.trim():''; + if(!val||parseInt(val)<=0)missing.push(MEAL_NAMES[mi]); }); - var dishNames=Object.keys(dishMap); - if(dishNames.length===0){summaryDiv.style.display='none';return;} - var rows=''; - for(var name in dishMap){ - var sp=dishMap[name]; - var servings=dinerCount; - var weightKg=(sp.baseGram*servings/1000).toFixed(2); - var specTxt=sp.baseGram+'g/'+(sp.portion||sp.unit); - rows+=''+name+''+specTxt+''+servings+''+weightKg+''; - } - tbody.innerHTML=rows; - summaryDiv.style.display='block'; + return missing; } function submitRecipeOrder(){ var recipeDate=document.getElementById('recipeDate').value; - var dinerCount=document.getElementById('recipeDinerCount').value; - var meals=[]; - document.querySelectorAll('#dailyMenuBody .meal-cb:checked').forEach(function(cb){meals.push(MEAL_NAMES[parseInt(cb.value)]);}); - if(!recipeDate){alert('请选择食谱日期');return;} - if(meals.length===0){alert('请至少选择一个餐次');return;} - if(!dinerCount||parseInt(dinerCount)<=0){alert('请输入有效的就餐人数');return;} - /* 由食谱创建一个包含多道菜品的订单 */ - var daily=getDailyMenu(recipeDate); + var deliveryTime=document.getElementById('recipeDeliveryTime').value; var mealIndices=[]; document.querySelectorAll('#dailyMenuBody .meal-cb:checked').forEach(function(cb){mealIndices.push(parseInt(cb.value));}); + if(!recipeDate){alert('请选择食谱日期');return;} + if(!deliveryTime){alert('请选择预定配送时间');return;} + if(mealIndices.length===0){alert('请至少选择一个餐次');return;} + var missing=validateMealDinerCounts(mealIndices); + if(missing.length>0){alert('以下餐次尚未填写就餐人数:'+missing.join('、'));return;} + /* 由食谱创建一个包含所选餐次全部菜品的订单,订单初始状态为「生成中」 */ + var daily=getDailyMenu(recipeDate); var dishMap={}; mealIndices.forEach(function(mi){ + var input=document.querySelector('#dailyMenuBody .meal-diner-input[data-meal-idx="'+mi+'"]'); + var dinerCount=parseInt(input.value.trim(),10); var dayDishes=daily.meals[mi]&&daily.meals[mi][daily.dayIdx]||[]; dayDishes.forEach(function(dishName){ - if(!dishMap[dishName])dishMap[dishName]=getDishSpecForRecipe(dishName); + if(!dishMap[dishName])dishMap[dishName]={spec:getDishSpecForRecipe(dishName),mealIdx:mi,dinerCount:dinerCount}; + else{ + /* 同菜品跨餐次:累加人数(按总人数核算) */ + dishMap[dishName].dinerCount+=dinerCount; + } }); }); var orderNo='CO-'+String(orders.length+1).padStart(3,'0'); var newLines=[]; + var di=0; for(var name in dishMap){ - var sp=dishMap[name]; + var item=dishMap[name]; + var sp=item.spec; var baseGram=sp.baseGram; + var servings=item.dinerCount; newLines.push({ - id:'L'+(Date.now()+Object.keys(dishMap).indexOf(name)), + id:'L'+(Date.now()+di), ncType:'cp',foodName:name, spec:baseGram+'g/'+(sp.portion||sp.unit), portion:sp.portion,baseGram:baseGram,unit:sp.unit, - qty:parseInt(dinerCount), - weight:parseFloat((baseGram*parseInt(dinerCount)/1000).toFixed(2)), - status:'备货中' + qty:servings, + weight:parseFloat((baseGram*servings/1000).toFixed(2)), + status:'生成中' }); + di++; } orders.unshift({ orderNo:orderNo,orderType:'内供',ncType:'cp', claimTime:new Date().toISOString().slice(0,16).replace('T',' '), - deliverTime:recipeDate+' 10:00',creator:'系统', + deliverTime:deliveryTime.replace('T',' '),creator:'系统', lines:newLines }); closeModal('modal-recipe-order'); renderOrderTable(); - document.getElementById('recipeDate').value=getDefaultDate(); - document.getElementById('recipeDinerCount').value=''; - document.getElementById('dailyMenuSection').style.display='none'; - document.getElementById('recipeOrderSummary').style.display='none'; - alert('已根据 '+recipeDate+' 食谱创建订单 '+orderNo+',包含 '+newLines.length+' 道菜品'); + alert('订单 '+orderNo+' 已提交,系统正在根据各餐次就餐人数智能核算采购量,请关注订单状态变化(生成中 → 备货中)。'); } function sanitizeIntegerInput(el){el.value=el.value.replace(/[^\d]/g,'');} @@ -1161,9 +1165,8 @@ function sanitizeIntegerInput(el){el.value=el.value.replace(/[^\d]/g,'');} if(id==='modal-order')resetOrderForm(); if(id==='modal-recipe-order'){ document.getElementById('recipeDate').value=getDefaultDate(); - document.getElementById('recipeDinerCount').value=''; + document.getElementById('recipeDeliveryTime').value=getDefaultDeliveryTime(); document.getElementById('dailyMenuSection').style.display='none'; - document.getElementById('recipeOrderSummary').style.display='none'; onRecipeDateChange(); } document.getElementById(id).classList.add('show');