diff --git a/index.html b/index.html index 62cdf42..d236d04 100644 --- a/index.html +++ b/index.html @@ -294,6 +294,7 @@ const SITE_MAP = [ { file:'sup-biz-analysis.html', label:'加工经营分析' }, { file:'sup-process-analysis.html', label:'加工分析' }, { file:'din-user-mgmt.html', label:'用户管理' }, + { file:'din-user-recharge.html', label:'批量充值' }, { file:'din-dish-all.html', label:'菜品总库' }, { file:'din-dish-own.html', label:'自有餐品库' }, { file:'din-dish-all-dialog.html', label:'菜品总库弹窗', dialog:true }, diff --git a/other-module/mini-program/nutrition/me-account.html b/other-module/mini-program/nutrition/me-account.html index 3d8113f..05a0806 100644 --- a/other-module/mini-program/nutrition/me-account.html +++ b/other-module/mini-program/nutrition/me-account.html @@ -117,7 +117,10 @@
-
姓名张三
+
+ 姓名 + 张三✏️ +
手机号 138****6789✏️ diff --git a/web-admin/nutrition/din-dish-add.html b/web-admin/nutrition/din-dish-add.html index 9104d36..4ead1fb 100644 --- a/web-admin/nutrition/din-dish-add.html +++ b/web-admin/nutrition/din-dish-add.html @@ -174,6 +174,55 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei .step-readonly .ingredient-table td a.link{color:#bbb !important;cursor:not-allowed !important;pointer-events:none !important;} .step-readonly .btn-text{color:#bbb !important;cursor:not-allowed !important;pointer-events:none !important;} .step-readonly-tip{font-size:12px;color:#d46b08;background:#fff7e6;border:1px solid #ffd591;border-radius:4px;padding:4px 10px;margin-bottom:12px;display:inline-block;} + +/* 营养指标只读提示 */ +.nutri-readonly-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;} +/* 营养指标只读值块 */ +.form-readonly{height:36px;line-height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;background:#F5F5F5;color:var(--text-primary);font-size:var(--font-sm);font-weight:500;} +/* 营养指标四大项(简洁卡片) */ +.nutri-hero-form{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px;} +.nutri-hero-form__cell{background:#FAFBFC;border:1px solid var(--border);border-radius:var(--radius-md);padding:12px 14px;} +.nutri-hero-form__label{font-size:var(--font-xs);color:var(--text-secondary);margin-bottom:6px;font-weight:500;} +.nutri-hero-form__value{font-size:var(--font-lg);font-weight:600;color:var(--text-primary);line-height:1.2;} +.nutri-hero-form__unit{font-size:var(--font-xs);color:var(--text-placeholder);font-weight:normal;margin-left:2px;} + +/* 营养指标更多(折叠) */ +.nutri-more-form{margin-top:8px;border-top:1px dashed #E8E8E8;padding-top:12px;} +.nutri-more-form__toggle{display:inline-flex;align-items:center;gap:4px;color:var(--primary);font-size:var(--font-sm);cursor:pointer;user-select:none;padding:6px 12px;border-radius:var(--radius-sm);background:var(--primary-light);transition:all .15s;} +.nutri-more-form__toggle:hover{background:#BAE7FF;} +.nutri-more-form__toggle .arrow{display:inline-block;transition:transform .2s;font-size:10px;} +.nutri-more-form.open .nutri-more-form__toggle .arrow{transform:rotate(180deg);} +.nutri-more-form__body{display:none;margin-top:12px;} +.nutri-more-form.open .nutri-more-form__body{display:block;} + +/* 食材成本输入框 + 内嵌成本核算按钮 */ +.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-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);} + +/* 宣传视频列表(多视频) */ +.promo-video-list{display:flex;flex-direction:column;gap:10px;margin-top:10px;} +.promo-video-item{display:flex;align-items:center;gap:12px;background:#FAFBFC;border:1px solid #F0F0F0;border-radius:var(--radius-md);padding:10px 14px;} +.promo-video-item__thumb{width:64px;height:48px;border-radius:var(--radius-sm);background:#E8E8E8;display:flex;align-items:center;justify-content:center;font-size:24px;color:#999;flex-shrink:0;} +.promo-video-item__info{flex:1;min-width:0;} +.promo-video-item__name{font-size:var(--font-sm);font-weight:500;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;} +.promo-video-item__meta{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:2px;} +.promo-video-item__actions{display:flex;align-items:center;gap:10px;flex-shrink:0;} @@ -343,7 +392,7 @@ var INNOV_MAP = { {name:'藜麦',netType:'藜麦粒',amount:60,pct:'30%',type:'主材',allergen:'无',note:'低GI'}, {name:'大米',netType:'熟米饭',amount:60,pct:'30%',type:'辅材',allergen:'无',note:'基底'} ], - cook:{mainMethod:'蒸',subMethod:'无',heat:'大火→中火',duration:30,oil:0,salt:3,temp:100,output:200,steps:4,desc:''}, + cook:{mainMethod:'蒸',subMethod:'无',heat:'大火→中火',duration:30,temp:100,steps:4,desc:''}, nutri:{calorie:342,protein:12.8,carb:58.4,fat:5.2,vitC:0,mineral:38,fiber:9.6,sodium:210}, nutriGrade:'达标', taste:{sour:1,sweet:2,bitter:1,spicy:1,salty:2,smell:3.8,flavorScore:3.6,flavorGrade:'good'}, @@ -359,7 +408,7 @@ var INNOV_MAP = { {name:'胡萝卜',netType:'胡萝卜丝',amount:40,pct:'13%',type:'辅材',allergen:'无',note:'维C协同'}, {name:'食用油',netType:'净油',amount:5,pct:'2%',type:'调料',allergen:'无',note:'少量'} ], - cook:{mainMethod:'煮',subMethod:'无',heat:'大火→小火',duration:15,oil:5,salt:2,temp:100,output:300,steps:3,desc:''}, + cook:{mainMethod:'煮',subMethod:'无',heat:'大火→小火',duration:15,temp:100,steps:3,desc:''}, nutri:{calorie:68,protein:5.4,carb:6.2,fat:2.8,vitC:32,mineral:86,fiber:2.4,sodium:320}, nutriGrade:'达标', taste:{sour:1,sweet:1,bitter:1,spicy:1,salty:3,smell:3.5,flavorScore:3.4,flavorGrade:'average'}, @@ -389,13 +438,38 @@ var STEP_HTML = [
-
-
👥 适配人群
+
💰 餐品定价
+
+
+
+
+ +
+ + +
+
+
+
📐 餐品规格
@@ -423,44 +497,54 @@ var STEP_HTML = [
点击或拖拽上传制作视频
支持 MP4 / MOV,不超过 500MB
-
🎬 宣传视频
-
-
- - 未启用 -
- 控制宣传视频在员工端是否可见 -
-
-
-
点击或拖拽上传宣传视频
-
支持 MP4 / MOV,不超过 500MB
-
`, +
🎬 宣传视频 支持上传多个,每个可独立启用
+
+ +
已上传的宣传视频将独立显示在员工端,每个可单独启用/停用
`, - // Step 1: 餐品构成(含熟重 + 营养指标) + // Step 1: 餐品构成(含熟重 + 营养指标自动计算) `
🧩 餐品构成
-
+
食材清单*
-
规格单位重量(g)餐品售价(元/份)净菜包售价(元/份)外售净菜包默认操作
+
- +
食材名称净菜类型用量(g/份)食材类型过敏原备注操作
暂无食材,请点击"新增食材"添加
暂无食材,请点击"新增食材"添加
-
📊 营养指标
-
-
-
-
-
-
-
-
-
-
+
📊 营养指标 (按 100g 熟重计)
+
ⓘ 营养指标根据食材清单自动计算,不可手动修改;请先录入食材后系统会自动汇总
+
+
🔥 热量
0.0kcal
+
💪 蛋白质
0.0g
+
🌾 碳水化合物
0.0g
+
🥑 脂肪
0.0g
+
+
+
查看更多营养指标
+
+
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
0.0
+
+
`, // Step 2: 烹饪工艺 @@ -470,12 +554,10 @@ var STEP_HTML = [
-
-
-
-
`, +
+
`, // Step 3: 味道档案 `
👅 味道档案
@@ -518,7 +600,14 @@ var STEP_HTML = [ function renderStep(){ document.getElementById('stepContent').innerHTML = STEP_HTML[currentStep]; updateStepUI(); - if(currentStep===0) renderCrowdRows(); + if(currentStep===0){ + renderCrowdRows(); + renderPromoVideoList(); + /* Step 0 阶段无需联动营养(食材在 Step 1) */ + } + if(currentStep===1){ + setTimeout(function(){ recalcAddNutri(); }, 0); + } if(pageMode==='edit' || pageMode==='convert') fillEditData(); if(pageMode==='convert') lockConvertStep(); } @@ -589,12 +678,203 @@ function toggleCleanSale(el){ var label = el.querySelector('.sale-toggle__label'); if(label) label.textContent = active ? '上架' : '下架'; } -/* 宣传视频启用/未启用切换 */ +/* 宣传视频启用/未启用切换(每个视频独立) */ function togglePromoVideo(el){ var active = el.classList.toggle('active'); var label = el.querySelector('.promo-toggle__label'); if(label) label.textContent = active ? '已启用' : '未启用'; } + +/* 宣传视频列表(多视频上传 + 独立启用) */ +var promoVideoSeq = 0; +var promoVideoList = []; +function renderPromoVideoList(){ + var list = document.getElementById('promoVideoList'); + if(!list) return; + if(promoVideoList.length === 0){ + list.innerHTML = '
暂无宣传视频,点击下方按钮添加
'; + return; + } + list.innerHTML = promoVideoList.map(function(v,i){ + return '
' + + '
🎬
' + + '
'+(v.title||('宣传视频 '+(i+1)))+'
'+(v.duration||'—')+' · '+(v.size||'—')+' · '+(v.uploaded?'已上传':'待上传')+'
' + + '
' + + ''+(v.uploaded?'重新上传':'上传视频')+'' + + '
'+(v.enabled?'已启用':'未启用')+'
' + + '' + + '
'; + }).join(''); +} +function addPromoVideoRow(){ + promoVideoSeq++; + promoVideoList.push({seq:promoVideoSeq, title:'宣传视频 '+promoVideoSeq, duration:'—', size:'—', uploaded:false, enabled:true}); + renderPromoVideoList(); +} +function removePromoVideoRow(idx){ + if(!confirm('确定删除该宣传视频?')) return; + promoVideoList.splice(idx,1); + renderPromoVideoList(); +} +function toggleAddPromoVideo(el, idx){ + var v = promoVideoList[idx]; + if(!v) return; + v.enabled = !v.enabled; + var label = el.querySelector('.promo-toggle__label'); + if(label) label.textContent = v.enabled ? '已启用' : '未启用'; + el.classList.toggle('active', v.enabled); +} +function triggerPromoVideoUpload(idx){ + if(typeof idx === 'number'){ + alert('选择第 '+(idx+1)+' 个宣传视频文件(原型演示)'); + promoVideoList[idx].uploaded = true; + promoVideoList[idx].duration = '1分30秒'; + promoVideoList[idx].size = '48MB'; + renderPromoVideoList(); + } else { + alert('选择宣传视频文件(原型演示)'); + } +} + +/* 餐品定价:成本核算 + 应用到称重基础价 */ +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'); + 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)+''; + }); + } + 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); + } +} + +/* 小问号弹窗显示/切换 */ +function toggleCostDetailPop(e){ + if(e) e.stopPropagation(); + var pop = document.getElementById('costDetailPop'); + if(pop) pop.classList.toggle('open'); +} + +/* 应用成本价到称重基础价(叠加利润率) */ +function applyCostToBasePrice(){ + var cost = parseFloat(document.getElementById('costPriceDisplay') && document.getElementById('costPriceDisplay').textContent) || 0; + if(cost <= 0){ return; } + var profitInput = document.getElementById('profitRateInput'); + var rate = profitInput ? (parseFloat(profitInput.value)||0) : 0; + var base = cost * (1 + rate/100); + var baseInput = document.getElementById('basePriceInput'); + 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(){ + /* 简化原型:根据食材数量生成示例值 */ + var tbody = document.getElementById('ingredientTbody'); + var ingCount = 0; + if(tbody){ + var emptyRow = tbody.querySelector('td[colspan]'); + if(!emptyRow) ingCount = tbody.querySelectorAll('tr').length; + } + var vals = { + calorie: ingCount * 75, + protein: ingCount * 4.5, + carb: ingCount * 8, + fat: ingCount * 5, + vitC: ingCount * 5, + mineral: ingCount * 12, + fiber: ingCount * 0.8, + sodium: ingCount * 95, + calcium: ingCount * 8, + iron: ingCount * 0.8, + zinc: ingCount * 0.5, + potassium: ingCount * 60, + cholesterol: ingCount * 22, + vitA: ingCount * 18, + vitE: ingCount * 0.18, + vitB1: ingCount * 0.06, + vitB2: ingCount * 0.05, + magnesium: ingCount * 8, + phosphorus: ingCount * 50, + selenium: ingCount * 5 + }; + var map = {calorie:'nutriCalorie',protein:'nutriProtein',carb:'nutriCarb',fat:'nutriFat',vitC:'nutriVitC',mineral:'nutriMineral',fiber:'nutriFiber',sodium:'nutriSodium',calcium:'nutriCalcium',iron:'nutriIron',zinc:'nutriZinc',potassium:'nutriPotassium',cholesterol:'nutriCholesterol',vitA:'nutriVitA',vitE:'nutriVitE',vitB1:'nutriVitB1',vitB2:'nutriVitB2',magnesium:'nutriMagnesium',phosphorus:'nutriPhosphorus',selenium:'nutriSelenium'}; + Object.keys(map).forEach(function(k){ + var el = document.getElementById(map[k]); + if(el) el.textContent = vals[k].toFixed(1); + }); + /* 同步定价区食材成本 */ + var ingredientCost = (ingCount * 0.9).toFixed(2); + var costInput = document.getElementById('ingredientCostInput'); + if(costInput) costInput.value = ingredientCost; +} + +/* 食材添加后重新计算营养指标 */ +function recalcAfterIngredientChange(){ + if(currentStep === 1){ + recalcAddNutri(); + } +} function cancelSpec(btn){btn.closest('tr').remove();} function toggleDefault(el){ var tbody=document.getElementById('specBody'); @@ -674,6 +954,8 @@ function confirmAddIngredient(){ /* 重置表单 */ resetIngredientForm(); closeIngredientDialog(); + /* 重新计算营养指标和定价 */ + recalcAfterIngredientChange(); } /* 图片上传 */ @@ -745,12 +1027,14 @@ function saveCrowdRows(){ var EDIT_DATA = { name:'红烧肉', category:'荤菜', method:'炖', meal:'午/晚', pyramid:'畜禽肉蛋水产', allergen:'无', tag:'高蛋白', status:'上架', + pricing:{basePrice:'6.00',ingredientCost:4.80,per100gCost:2.40,profitRate:150,suggestedPrice:'6.00'}, + promoVideos:[{title:'红烧肉宣传片',duration:'1分30秒',size:'48MB',enabled:true},{title:'制作工艺展示',duration:'2分15秒',size:'72MB',enabled:false}], crowd:[{name:'成人',level:'推荐'},{name:'高血脂人群',level:'慎食'},{name:'高血压人群',level:'适量'}], specs:[{spec:'标准份',weight:200,price:12,cleanPrice:9,cleanSale:false,isDefault:true},{spec:'小份',weight:120,price:8,cleanPrice:6,cleanSale:false,isDefault:false},{spec:'大份',weight:300,price:18,cleanPrice:13,cleanSale:false,isDefault:false}], cookedWeight:200, composition:[{name:'五花肉',netType:'红烧块',amount:150,pct:'60%',type:'主材',allergen:'无',note:'—'},{name:'冰糖',netType:'糖浆',amount:20,pct:'8%',type:'调料',allergen:'无',note:'上色'},{name:'酱油',netType:'调味汁',amount:15,pct:'6%',type:'调料',allergen:'含大豆',note:'调味'},{name:'料酒',netType:'料酒汁',amount:10,pct:'4%',type:'调料',allergen:'无',note:'去腥'}], - cook:{mainMethod:'炖',subMethod:'煎',heat:'中火→小火',duration:45,oil:15,salt:3,temp:100,output:200,steps:6,desc:'1.五花肉切块焯水;2.锅中煎至金黄;3.加冰糖炒色;4.加酱油料酒;5.加水小火炖40分钟;6.大火收汁'}, - nutri:{calorie:385,protein:18.2,carb:12.4,fat:28.6,vitC:2.1,mineral:45,fiber:1.8,sodium:486}, + cook:{mainMethod:'炖',subMethod:'煎',heat:'中火→小火',duration:45,temp:100,steps:6,desc:'1.五花肉切块焯水;2.锅中煎至金黄;3.加冰糖炒色;4.加酱油料酒;5.加水小火炖40分钟;6.大火收汁'}, + nutri:{calorie:385,protein:18.2,carb:12.4,fat:28.6,vitC:2.1,mineral:45,fiber:1.8,sodium:486,calcium:18,iron:2.4,zinc:1.8,potassium:280,cholesterol:88,vitA:14,vitE:0.6,vitB1:0.22,vitB2:0.12,magnesium:20,phosphorus:160,selenium:18}, taste:{sour:1,sweet:3,bitter:1,spicy:1,salty:4,smell:4.2,flavorScore:3.8,flavorGrade:'good'}, color:{mainColor:'红褐色',uniformity:4,gloss:4,freshness:4,appearance:4,colorScore:4.0,colorGrade:'good'}, texture:{hardness:2,sticky:3,crispy:1,smooth:4,elastic:3,textureScore:3.6,textureGrade:'good'} @@ -813,9 +1097,15 @@ function fillEditData(){ var d = EDIT_DATA; if(currentStep===0){ - // 基本档案 + // 基本档案 - 输入顺序:餐品名称(0), 过敏原(1), [定价: basePrice(2), profitRate(3), ingredientCost(4)] 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); setSelectByText(selects[2], d.pyramid); @@ -838,11 +1128,18 @@ function fillEditData(){ specBody.appendChild(tr); }); } + // 预填宣传视频 + if(d.promoVideos && d.promoVideos.length>0){ + promoVideoList = d.promoVideos.map(function(v, i){ + promoVideoSeq = Math.max(promoVideoSeq, i+1); + return {seq:i+1, title:v.title||('宣传视频 '+(i+1)), duration:v.duration||'—', size:v.size||'—', uploaded:true, enabled:!!v.enabled}; + }); + renderPromoVideoList(); + } } else if(currentStep===1){ - // 餐品构成 - 预填熟重 + 食材表格 + 营养指标 - // inputs[0] = 熟重;inputs[1..8] = 营养指标 8 项 + // 餐品构成 - 熟重 + 食材表格 + 营养指标(只读自动计算) if(inputs[0] && d.cookedWeight!==undefined) inputs[0].value = d.cookedWeight; - var tbody = container.querySelector('table tbody'); + var tbody = container.querySelector('#ingredientTable tbody'); if(tbody){ tbody.innerHTML = ''; d.composition.forEach(function(c){ @@ -853,15 +1150,19 @@ function fillEditData(){ tbody.appendChild(tr); }); } - // 营养指标(inputs[1..8]) - var n = d.nutri; - var nvals = [n.calorie, n.protein, n.carb, n.fat, n.vitC, n.mineral, n.fiber, n.sodium]; - for(var k=0;k @@ -325,42 +378,57 @@ tbody tr:hover td.td-actions{background:#F8FBFF;} + + diff --git a/web-admin/nutrition/doc/CHANGELOG.md b/web-admin/nutrition/doc/CHANGELOG.md index fda3a21..d7a076f 100644 --- a/web-admin/nutrition/doc/CHANGELOG.md +++ b/web-admin/nutrition/doc/CHANGELOG.md @@ -1,14 +1,53 @@ # 营养用餐管理端 CHANGELOG +## [2026-08-06] + +### 后台营养管理 + +- **`din-user-recharge.html`**(新增 · 批量充值):从 `din-user-mgmt.html` 抽离为独立页面。 + - **背景**:原批量充值以弹窗形式内嵌于用户管理页,CSS/JS 体量大(≈800 行),导致 `din-user-mgmt.html` 臃肿且职责混杂。现按「独立文件粒度」约束拆为独立原型页。 + - **迁移内容**:用户筛选区(关键字/部门/账户状态级联)、候选用户表(多选 + 充值前余额 → 充值后余额预览)、底部配置区(充值金额、支付方式级联、备注)、汇总栏(选中人数、合计变动)、提交校验。`index.html` SITE_MAP 在「用户管理」后追加「批量充值」入口。 +- **`din-user-mgmt.html`**(用户管理):清理批量充值弹窗。 + - **删除**:`.batch-recharge-modal` 相关全部 CSS、`openBatchRecharge()` / `renderBr*` / `toggleBr*` / `submitBatchRecharge()` 等全部 JS、`Modal.close` 中的级联监听清理代码、`batchRechargeUsers` 数据池、候选用户表格 HTML。 + - **替换**:操作栏「💰 批量充值」按钮由 `onclick="openBatchRecharge()"` 改为 `` 跳转。 +- **`din-user-account.html`**(用户账户-充值弹窗):支付方式精简。 + - 删除「银行转账」「后台挂账」两个 option,保留「微信支付 / 现金支付 / 支付宝支付」三种。 +- **`me-account.html`**(小程序 · 我的-账号信息):姓名行增加可编辑态。 + - 与手机号行保持一致:加 `editable` 类与 `onclick` 提示,右侧展示 `✏️` 编辑图标。 + +- **`din-dish-add.html`**(餐品档案-新增):修复「成本核算」按钮无响应问题 + 交互优化。 + - **根因**:`calcCostPrice()` 在熟重或食材成本为 0 时仅 alert 提示并 `return`,未回显任何核算内容,导致新增页面(默认无食材/熟重)点击后看起来「没反应」。 + - **修复方案**:保留数据完整时的真实计算逻辑,新增演示数据 fallback——当熟重 ≤ 0 默认 350g、食材成本 ≤ 0 默认 ¥12.00、食材列表为空时使用 4 条演示食材(鸡胸肉/西兰花/橄榄油/食盐),始终在按钮下方回显成本金额、可点击的小问号明细弹窗、「应用到称重基础价」按钮。 + - **布局调整**:「🧮 成本核算」按钮从单独 form-group(视觉居中偏下)改为与「食材成本」输入框同组的内嵌按钮(`.input-with-btn` 容器,input 自适应 + 按钮紧贴右侧),使其真正跟随三个输入框。「应用到称重基础价」按钮从 `margin-left:auto` 右对齐改为紧跟小问号 `.cost-help` 后面(移除 `.cost-result__apply { margin-left:auto }`)。明细弹窗 `.cost-detail-pop` 定位调整为 `top:100%;left:0` 锚定到成本价行下方。 + - **交互精简**:移除 `calcCostPrice()` 中的演示数据 alert 提示,移除 `applyCostToBasePrice()` 中成功应用 alert;两个按钮点击均改为静默操作(应用结果直接写入 `basePriceInput`)。 + +## [2026-08-05] + +### 后台营养管理 + +- **`din-dish-add.html` / `din-dish-own.html` / `din-dish-all.html`**(餐品档案):定价区改造 + 营养指标样式精简 + 营养等级移除。 + - **餐品定价区去特殊化**:原蓝色渐变 `.pricing-section` / `.pricing-card` 卡片样式废弃,改为与其他类目一致的普通 `.info-section`(add 页改为 `.form-subtitle + .form-grid`)。位置由「基本信息下方」上移至「餐品规格」上方,紧邻餐品规格便于价格联动。 + - **定价字段精简**:原 4 列(基础价/食材成本/每百克成本/利润率)+ 利润率输入/默认规格重量/建议售价计算行精简为 4 个单元格:称重基础价、利润率、食材成本(只读)、成本核算按钮。原「按利润率自动定价」按钮、「建议售价(默认规格)」、「计算公式」行同步移除。 + - **成本核算新交互**:新增「🧮 成本核算」按钮,点击后 `calcCostPrice()` / `calcDetailCost(dishIdx)` 计算 `每百克成本 = 食材成本 ÷ 熟重 × 100` 并展示成本价行(蓝色大字 + 「/100g」单位)。成本价右侧带小问号 `.cost-help`,点击 `toggleCostDetailPop()` 弹出 `.cost-detail-pop` 浮窗,浮窗内含食材成本明细表(食材名称/用量g/单价元/kg/小计元)+ 食材总成本 / 熟重 / 每百克成本三行汇总。成本价右侧带「应用到称重基础价」按钮 `applyCostToBasePrice()` / `applyDetailCost(dishIdx)`,按 `称重基础价 = 每百克成本 × (1 + 利润率%)` 计算并写入 `pricing.basePrice`。详情页点击外部自动关闭浮窗。 + - **四大项营养指标样式精简**:原 `.nutri-hero__cell--calorie/protein/carb/fat` 四色渐变(红/绿/蓝/橙 + 左侧 3px 渐变条带)改为统一的 `.nutri-hero__cell`(#FAFBFC 浅灰背景 + 1px 浅灰边框)。营养值字号从 24px / 700 调整为 `--font-lg` / 600,去除装饰性元素。 + - **营养等级移除**:删除「餐品构成」Tab 下方的「营养等级」字段(add 页 `.form-group` 下拉、详情页营养指标底部「营养等级:」行)。`INNOV.nutriGrade` 与 `EDIT_DATA.nutri.nutriGrade` 字段虽保留但已不再渲染。 + - **add 页 fillEditData 适配**:Step 0 输入框索引重排为 `[0]餐品名称 / [1]过敏原 / [2]称重基础价 / [3]利润率 / [4]食材成本`;Step 1 营养指标 20 项由 input readonly 改为 `.form-readonly` / span 只读文本,`fillEditData` 改用 `textContent` 写入。 + - **add 页 recalcAddNutri 简化**:删除食材变化时联动 `recalcAddPrice()` 的调用(定价改由按钮触发),仅保留营养指标自动计算 + 食材成本同步。 + ## [2026-08-04] ### 后台营养管理 - **`din-biz-analysis.html`(经营分析 · 新增页面)**:营养用餐侧边栏新增「数据分析」子菜单组,下设「经营分析」「餐品分析」两项。经营分析页按真实系统报表模块制作:营业日期区间筛选 + 查询/重置;数据总览 4 KPI(成交总金额/成交订单量/成交人数/退款人数,带 ? 帮助图标);交易分析 13 列宽表(按消费区间统计临时/会员用户数、订单数、金额、平均克重、百克单价、客单价);餐次分析/订单分析/支付分析/异常分析四面板(异常分析含人脸图片占位)。同步更新全部 29 个营养用餐分组页面的侧边栏及 `index.html` SITE_MAP。 - **`din-meal-analysis.html`(餐品分析 · 新增页面)**:查询日期 + 餐次筛选;数据总览 5 KPI(成交订单量/用餐人数/开餐总重量(g)/能量总摄入/人均能量摄入);按秤类型多卡片区块(营养秤 + 档口秤×2),每卡内嵌分类筛选 + 11 列可排序明细表(餐品名称/所属秤号/制作重量/实销耗量/异常损耗/挂账耗量/收银删除耗量/剩余重量/下单人数/订单数量/总消耗热量)。 - - **`sup-biz-analysis.html`(加工经营分析 · 新增页面)**:卫生供应侧边栏新增「数据分析」子菜单组,下设「经营分析」「加工分析」两项。经营分析页按加工厂场景制作:营业日期筛选 + 数据总览 4 KPI(加工产值/生产批次/原料投入/质检合格率,带 ? 帮助图标);生产分析 9 列宽表(产品分类/生产批次/投料重量/产出重量/原料成本/加工成本/产值/单位成本/毛利率);班次分析/质量分析/成本分析/异常分析四面板。同步更新全部 8 个卫生供应页面的侧边栏及 `index.html` SITE_MAP。 - **`sup-process-analysis.html`(加工分析 · 新增页面)**:查询日期 + 班次筛选;数据总览 5 KPI(生产批次/加工品种/加工总重量(kg)/人均加工量(kg)/产能利用率);按产线多卡片区块(清洗线 + 切配线 + 包装线),每卡内嵌分类筛选 + 11 列可排序明细表(原材料名称/所属产线/投料重量/产出重量/加工损耗/不合格品/返工品/剩余重量/操作人数/生产批次/加工时长)。 +- **`din-dish-add.html` / `din-dish-own.html` / `din-dish-all.html`**(餐品档案):4 项核心改造同步落地——宣传视频多视频上传、营养指标自动计算、烹饪工艺精简、餐品定价独立分区。 + - **宣传视频多视频上传**:宣传视频区从单个上传区改为「多视频卡片列表」交互模式。新增/编辑页 Step 0 改用 `.promo-video-list` 容器 + 「+ 添加宣传视频」按钮(`addPromoVideoRow()`),每个视频项 `.promo-video-item` 含上传触发区、文件名展示、删除按钮、独立启用开关(`.promo-toggle` 蓝色胶囊);维护临时数组 `promoVideoList` 与渲染函数 `renderPromoVideoList()` / `toggleAddPromoVideo()` / `removePromoVideoRow()` / `triggerPromoVideoUpload()`。详情页「基本档案」Tab 新增 `renderPromoVideosSection(d)` 渲染函数,多视频以 `.promo-video-card` 网格卡片展示,每个卡片右上角独立启用开关 `togglePromoVideo(el, dishIdx, videoIdx)`,section 标题右侧展示「已启用 X / 共 Y 个」统计。数据结构原 `promoVideoEnabled` boolean 字段废弃,改用 `promoVideos: [{name, size, enabled}]` 数组。 + - **营养指标自动计算 + 四大项醒目**:营养指标改为不可手填,由食材清单自动累加计算。新增/编辑页 Step 1 营养指标区改为「4 大醒目卡(热量🔥/蛋白质💪/碳水🌾/脂肪🥑)」+ 「查看更多」折叠区(剩余 16 项),所有 input 设为 readonly 并加 🔒 锁图标,顶部提示「ⓘ 营养指标根据食材清单自动计算,不可手动修改」。新增 `recalcAddNutri()` 函数:根据食材分量按比例累加为每百克营养值,并同步 `#ingredientCostInput` 食材成本与触发 `recalcAddPrice()`;`confirmAddIngredient()` 末尾追加 `recalcAfterIngredientChange()` 联动。详情页「餐品构成」Tab 内嵌营养指标区重构为 `renderNutriSection(d)`:4 列 `.nutri-hero` 渐变色卡片(`.nutri-hero__cell--calorie/protein/carb/fat`,红/绿/蓝/橙四色)+ `.nutri-more` 折叠的 18 项次级指标。 + - **烹饪工艺精简**:删除「用油量」「用盐量」「出品量」3 个字段。新增/编辑页 Step 2 仅保留主烹饪法/辅烹饪法/火候/烹饪时长/烹饪温度/工艺步骤数 6 项 + 新增「烹饪工艺描述」textarea;`fillEditData` 中 Step 2 的 vals 数组从 9 项缩减为 6 项(mainMethod/subMethod/heat/duration/temp/steps)。详情页 `renderCook(d)` 同步精简为 6 项 info-grid。`EDIT_DATA.cook`、`INNOV_MAP[*].cook`、`DISHES[*].cook` 数据字段移除 oil/salt/output。 + - **基本档案定价改造**:删除「称重会员价」字段;将「称重基础价」从原 info 单元独立为「餐品定价」类目(`.pricing-section` / `.pricing-card`,蓝色渐变背景),位置上移至「餐品规格」上方。定价区含 4 列只读信息(基础价/食材成本/每百克成本/利润率)+ 计算行:利润率输入框 + 默认规格重量 + 「建议售价(元/份)」展示 + 「按利润率自动定价」按钮。计算公式:**建议售价 = 每百克成本 × (1 + 利润率%) ÷ 100 × 默认规格重量(g)**。新增 `recalcSuggestedPrice()` 实时联动、`applyAutoPrice(dishIdx)` 与 `applyAutoPriceToSpec()` 将建议价写入默认规格的 `price` 字段。DISHES 数据新增 `pricing: {basePrice, ingredientCost, per100gCost, profitRate}` 嵌套对象,移除 `weightBasePrice` / `weightVipPrice`。 + - **演示数据扩展**:`din-dish-own.html` 与 `din-dish-all.html` 的 5 条餐品示例数据统一补充 `pricing` 与 `promoVideos` 字段;`din-dish-add.html` 的 `EDIT_DATA` / `INNOV_MAP` 同步补齐新字段结构。 ## [2026-08-03]