feat: 净菜订单/申领三页统一增加订单金额 + 餐线营养秤菜品展示页新增视频演示模式

- web-admin/nutrition: prod-clean-order/sup-clean-external/sup-clean-internal 统一增加单价/小计字段与订单总金额栏,移除弹窗内汇总预览表格
- canteen-nutrition-scale/02-dish-display: 新增演示模式切换 pill 与视频模式布局(上视频 + 下信息卡)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-08-05 14:20:21 +08:00
co-authored by Claude Opus 4.7
parent 6c812f8e64
commit d00208a33d
7 changed files with 669 additions and 195 deletions
@@ -175,6 +175,305 @@
visibility: visible;
transform: translateX(-50%) translateY(0);
}
/* ---- 演示模式切换按钮(仅原型演示,右上角简洁 pill) ---- */
.mode-switch {
position: absolute;
top: 20px;
right: 56px;
display: flex;
height: 22px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.12);
border-radius: 6px;
overflow: hidden;
z-index: 10;
}
.mode-switch::before {
content: '演示';
position: absolute;
left: -36px;
top: 50%;
transform: translateY(-50%);
font-size: 10px;
color: rgba(255,255,255,0.3);
letter-spacing: 1px;
}
.mode-switch button {
padding: 0 12px;
font-size: 11px;
color: rgba(255,255,255,0.5);
background: transparent;
border: none;
cursor: pointer;
transition: all 0.2s;
height: 100%;
font-family: inherit;
}
.mode-switch button:hover {
background: rgba(255,255,255,0.06);
color: rgba(255,255,255,0.8);
}
.mode-switch button.active {
background: #FF9800;
color: #fff;
}
/* ---- 视频模式布局(纵向:上视频 + 下信息卡) ---- */
.video-mode {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 60px 30px;
gap: 24px;
}
/* 视频模式激活时显示视频布局 */
.main-area.mode-video .video-mode {
display: flex;
}
/* 视频模式激活时隐藏常规模式的标题和菜品信息 */
.main-area.mode-video .center-heading,
.main-area.mode-video .dish-info {
display: none;
}
/* 视频播放器 */
.video-player {
width: 100%;
max-width: 680px;
aspect-ratio: 16 / 9;
border-radius: 12px;
overflow: hidden;
position: relative;
background: #000;
box-shadow: 0 12px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.06);
}
.video-player .video-bg {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}
.video-play-btn {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 56px; height: 56px;
background: rgba(0,0,0,0.55);
border: 2px solid rgba(255,255,255,0.85);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s, background 0.2s;
z-index: 2;
}
.video-play-btn:hover {
background: rgba(0,0,0,0.75);
transform: translate(-50%, -50%) scale(1.05);
}
.video-play-btn::after {
content: '';
width: 0; height: 0;
border-left: 16px solid #fff;
border-top: 10px solid transparent;
border-bottom: 10px solid transparent;
margin-left: 4px;
}
.video-controls {
position: absolute;
bottom: 0; left: 0; right: 0;
padding: 10px 14px;
background: linear-gradient(to top, rgba(0,0,0,0.75), transparent);
display: flex;
align-items: center;
gap: 10px;
font-size: 11px;
color: rgba(255,255,255,0.85);
z-index: 2;
}
.video-progress {
flex: 1;
height: 3px;
background: rgba(255,255,255,0.2);
border-radius: 2px;
position: relative;
}
.video-progress::after {
content: '';
position: absolute;
left: 0; top: 0;
width: 35%;
height: 100%;
background: #FF9800;
border-radius: 2px;
}
.video-time {
font-size: 11px;
letter-spacing: 0.5px;
white-space: nowrap;
}
.video-fullscreen {
font-size: 13px;
cursor: pointer;
}
/* 信息卡 */
.video-info-card {
display: flex;
align-items: center;
gap: 28px;
padding: 14px 36px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px;
}
.video-info-name {
font-size: 26px;
font-weight: 600;
color: #fff;
}
.video-info-divider {
width: 1px;
height: 28px;
background: rgba(255,255,255,0.15);
}
.video-info-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.video-info-label {
font-size: 11px;
color: rgba(255,255,255,0.4);
letter-spacing: 1px;
}
.video-info-value {
font-size: 18px;
font-weight: 600;
color: #0AC4A8;
}
.video-info-unit {
font-size: 12px;
color: rgba(255,255,255,0.5);
font-weight: 400;
margin-left: 4px;
}
/* ---- 视频模式布局2(横向:左视频 + 右信息卡含标签) ---- */
.video-mode-h {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 40px 50px;
gap: 32px;
}
/* 视频2 模式激活时显示横向布局 */
.main-area.mode-video2 .video-mode-h {
display: flex;
}
/* 视频2 模式激活时隐藏常规元素与纵向视频布局 */
.main-area.mode-video2 .center-heading,
.main-area.mode-video2 .dish-info,
.main-area.mode-video2 .video-mode {
display: none;
}
/* 纵向视频模式激活时也隐藏横向布局 */
.main-area.mode-video .video-mode-h {
display: none;
}
/* 横向布局下的视频播放器(左侧,固定宽度) */
.video-mode-h .video-player {
flex: 0 0 auto;
width: 680px;
max-width: 60%;
}
/* 右侧信息卡(横向布局,纵向排列内容) */
.video-info-card-h {
flex: 1;
min-width: 280px;
max-width: 380px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 14px;
padding: 24px 28px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 14px;
}
.video-info-name-h {
font-size: 28px;
font-weight: 600;
color: #fff;
line-height: 1.2;
}
/* 餐品标签 chips */
.video-tags-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.video-tag-chip {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-size: 12px;
color: #0AC4A8;
background: rgba(10,196,168,0.12);
border: 1px solid rgba(10,196,168,0.25);
border-radius: 10px;
letter-spacing: 0.5px;
white-space: nowrap;
}
/* 标签色系变化(按位置循环,增加视觉层次) */
.video-tag-chip:nth-child(3n+2) {
color: #FF9800;
background: rgba(255,152,0,0.10);
border-color: rgba(255,152,0,0.25);
}
.video-tag-chip:nth-child(3n) {
color: #5B9BF8;
background: rgba(91,155,248,0.12);
border-color: rgba(91,155,248,0.25);
}
.video-info-divider-h {
width: 100%;
height: 1px;
background: rgba(255,255,255,0.12);
margin: 4px 0;
}
.video-info-item-h {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
width: 100%;
}
/* 横向布局右侧信息卡中的数值字号加大 */
.video-info-item-h .video-info-value {
font-size: 22px;
}
.video-info-item-h .video-info-label {
font-size: 12px;
}
</style>
</head>
<body>
@@ -185,6 +484,13 @@
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a> <span>Phone-192.168.1.165:5555</span>
</div>
<!-- 演示模式切换按钮(仅原型演示用,不影响实际业务) -->
<div class="mode-switch" id="modeSwitch">
<button class="active" data-mode="normal">常规</button>
<button data-mode="video">视频</button>
<button data-mode="video2">视频2</button>
</div>
<!-- 设置按钮 -->
<a class="settings-btn" id="settingsBtn" title="设置">&#x2699;</a>
@@ -212,6 +518,72 @@
<div class="dish-hint">点击菜品信息 &#x2192; 进入开餐界面</div>
</div>
</div>
<!-- 视频模式布局(纵向:上视频 + 下信息卡,默认隐藏) -->
<div class="video-mode" id="videoMode">
<!-- 视频播放器 -->
<div class="video-player">
<!-- 视频背景(用 SVG 模拟菜品展示画面) -->
<div class="video-bg" id="videoBg"></div>
<!-- 播放按钮 -->
<div class="video-play-btn" id="videoPlayBtn" title="播放"></div>
<!-- 控制条 -->
<div class="video-controls">
<span style="cursor:pointer;">&#x23EF;</span>
<div class="video-progress"></div>
<span class="video-time">01:23 / 03:45</span>
<span class="video-fullscreen">&#x26F6;</span>
</div>
</div>
<!-- 餐品信息卡 -->
<div class="video-info-card">
<div class="video-info-name" id="videoDishName">宫保鸡丁</div>
<div class="video-info-divider"></div>
<div class="video-info-item">
<span class="video-info-label">单价</span>
<span class="video-info-value"><span id="videoDishPrice">5</span><span class="video-info-unit">元/100克</span></span>
</div>
<div class="video-info-divider"></div>
<div class="video-info-item">
<span class="video-info-label">热量</span>
<span class="video-info-value"><span id="videoDishKcal">158.66</span><span class="video-info-unit">千卡/100克</span></span>
</div>
</div>
</div>
<!-- 视频模式布局2(横向:左视频 + 右信息卡含标签,默认隐藏) -->
<div class="video-mode-h" id="videoModeH">
<!-- 左侧:视频播放器 -->
<div class="video-player">
<div class="video-bg" id="videoBgH"></div>
<div class="video-play-btn" id="videoPlayBtnH" title="播放"></div>
<div class="video-controls">
<span style="cursor:pointer;">&#x23EF;</span>
<div class="video-progress"></div>
<span class="video-time">01:23 / 03:45</span>
<span class="video-fullscreen">&#x26F6;</span>
</div>
</div>
<!-- 右侧:餐品信息卡(含标签) -->
<div class="video-info-card-h">
<!-- 餐品名称 -->
<div class="video-info-name-h" id="videoDishNameH">宫保鸡丁</div>
<!-- 餐品标签 chips -->
<div class="video-tags-row" id="videoTagsRow"></div>
<!-- 分隔线 -->
<div class="video-info-divider-h"></div>
<!-- 单价 -->
<div class="video-info-item-h">
<span class="video-info-label">单价</span>
<span class="video-info-value"><span id="videoDishPriceH">5</span><span class="video-info-unit">元/100克</span></span>
</div>
<!-- 每百克热量 -->
<div class="video-info-item-h">
<span class="video-info-label">每百克热量</span>
<span class="video-info-value"><span id="videoDishKcalH">158.66</span><span class="video-info-unit">千卡/100克</span></span>
</div>
</div>
</div>
</div>
</div>
@@ -223,16 +595,16 @@
/* ===== 餐品数据映射(模拟后端) ===== */
var DISH_MAP = {
1: { name: '红烧肉', price: 5, kcal: 480, colors: ['#8B3A2A', '#D4764E', '#C05030'] },
2: { name: '宫保鸡丁', price: 5, kcal: 420, colors: ['#C87030', '#E8A040', '#D08028'] },
3: { name: '麻婆豆腐', price: 4, kcal: 280, colors: ['#706040', '#B89060', '#807050'] },
4: { name: '清炒时蔬', price: 3, kcal: 120, colors: ['#306028', '#50A040', '#408030'] },
5: { name: '香煎带鱼', price: 6, kcal: 320, colors: ['#805020', '#B88040', '#A06830'] },
6: { name: '糖醋排骨', price: 7, kcal: 520, colors: ['#802018', '#C05030', '#A03820'] },
7: { name: '蒜蓉西兰花', price: 3, kcal: 110, colors: ['#207040', '#40A868', '#308848'] },
8: { name: '米饭', price: 1.5, kcal: 260, colors: ['#D0D0C0', '#E8E8D8', '#E0E0C8'] },
9: { name: '紫菜蛋花汤', price: 2, kcal: 80, colors: ['#503028', '#805040', '#603830'] },
10: { name: '西红柿炒蛋', price: 4, kcal: 180, colors: ['#E8A030', '#F0C040', '#D89020'] }
1: { name: '红烧肉', price: 5, kcal: 480, colors: ['#8B3A2A', '#D4764E', '#C05030'], tags: ['高蛋白', '高脂肪', '富含铁'] },
2: { name: '宫保鸡丁', price: 5, kcal: 420, colors: ['#C87030', '#E8A040', '#D08028'], tags: ['高蛋白', '低芥酸', '营养均衡'] },
3: { name: '麻婆豆腐', price: 4, kcal: 280, colors: ['#706040', '#B89060', '#807050'], tags: ['植物蛋白', '低芥酸', '微辣'] },
4: { name: '清炒时蔬', price: 3, kcal: 120, colors: ['#306028', '#50A040', '#408030'], tags: ['低脂肪', '高纤维', '清淡'] },
5: { name: '香煎带鱼', price: 6, kcal: 320, colors: ['#805020', '#B88040', '#A06830'], tags: ['高蛋白', '富含DHA', '低芥酸'] },
6: { name: '糖醋排骨', price: 7, kcal: 520, colors: ['#802018', '#C05030', '#A03820'], tags: ['高蛋白', '高热量', '富含钙'] },
7: { name: '蒜蓉西兰花', price: 3, kcal: 110, colors: ['#207040', '#40A868', '#308848'], tags: ['低脂肪', '高纤维', '富含维C'] },
8: { name: '米饭', price: 1.5, kcal: 260, colors: ['#D0D0C0', '#E8E8D8', '#E0E0C8'], tags: ['主食', '低脂肪', '高碳水'] },
9: { name: '紫菜蛋花汤', price: 2, kcal: 80, colors: ['#503028', '#805040', '#603830'], tags: ['低脂肪', '富含碘', '清淡'] },
10: { name: '西红柿炒蛋', price: 4, kcal: 180, colors: ['#E8A030', '#F0C040', '#D89020'], tags: ['高蛋白', '低芥酸', '富含维C'] }
};
/* ===== 解析 URL 参数 ===== */
@@ -271,25 +643,34 @@
document.getElementById('dishPrice').textContent = dishData.price;
document.getElementById('dishKcal').textContent = dishData.kcal;
/* ===== 视频模式数据填充(纵向布局) ===== */
document.getElementById('videoDishName').textContent = dishData.name;
document.getElementById('videoDishPrice').textContent = dishData.price;
document.getElementById('videoDishKcal').textContent = dishData.kcal;
document.getElementById('videoBg').innerHTML = videoBgSVG(dishData.colors);
/* ===== 视频模式数据填充(横向布局,含标签) ===== */
document.getElementById('videoDishNameH').textContent = dishData.name;
document.getElementById('videoDishPriceH').textContent = dishData.price;
document.getElementById('videoDishKcalH').textContent = dishData.kcal;
document.getElementById('videoBgH').innerHTML = videoBgSVG(dishData.colors);
// 渲染餐品标签 chips
var tagsRow = document.getElementById('videoTagsRow');
var tagsHtml = '';
var dishTags = dishData.tags || [];
for (var i = 0; i < dishTags.length; i++) {
tagsHtml += '<span class="video-tag-chip">' + dishTags[i] + '</span>';
}
tagsRow.innerHTML = tagsHtml;
/* ===== 点击菜品信息 → 进入开餐界面 ===== */
document.getElementById('dishInfo').addEventListener('click', function() {
window.location.href = '01-meal-start.html';
});
/* ===== 模拟人脸识别(站在设备前自动触发) ===== */
var faceDetectTimer = null;
/* ===== 演示说明:原 3 秒自动跳转逻辑已移除,改为用户主动点击菜品信息后跳转 ===== */
var isEmployee = false; // 02 默认买家态(买家身份进入)
function startFaceDetect() {
// 模拟:站在设备前 3 秒后识别成功
faceDetectTimer = setTimeout(function() {
// 识别成功 → 跳转取餐界面
window.location.href = '03-meal-pickup.html?dishId=' + dishId;
}, 3000);
}
// 页面加载后自动启动人脸识别
startFaceDetect();
/* ===== Toast 提示 ===== */
var toastEl = document.getElementById('toast');
function showToast(msg, duration) {
@@ -311,6 +692,59 @@
}
window.location.href = '04-settings.html';
});
/* ===== 演示模式切换(常规 / 视频 / 视频2) ===== */
var modeSwitch = document.getElementById('modeSwitch');
var mainArea = document.querySelector('.main-area');
modeSwitch.addEventListener('click', function(e) {
var btn = e.target.closest('button[data-mode]');
if (!btn) return;
var mode = btn.getAttribute('data-mode');
// 切换按钮高亮状态
var btns = modeSwitch.querySelectorAll('button[data-mode]');
for (var i = 0; i < btns.length; i++) {
btns[i].classList.toggle('active', btns[i] === btn);
}
// 通过 main-area 的 class 控制布局切换:先清除所有模式类,再按需添加
mainArea.classList.remove('mode-video', 'mode-video2');
if (mode === 'video') {
mainArea.classList.add('mode-video');
} else if (mode === 'video2') {
mainArea.classList.add('mode-video2');
}
});
/* ===== 视频播放按钮(演示用,点击切换播放/暂停图标) ===== */
var videoPlaying = false;
function bindVideoPlayBtn(btnId) {
document.getElementById(btnId).addEventListener('click', function() {
videoPlaying = !videoPlaying;
this.style.opacity = videoPlaying ? '0' : '1';
});
}
bindVideoPlayBtn('videoPlayBtn');
bindVideoPlayBtn('videoPlayBtnH');
/* ===== 视频背景 SVG(按菜品色系生成展示画面) ===== */
function videoBgSVG(colors) {
var w = 680, h = 383; // 16:9 比例
var id = 'vidGrad';
return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + w + ' ' + h + '" preserveAspectRatio="xMidYMid slice" style="width:100%;height:100%;">' +
'<defs><linearGradient id="' + id + '" x1="0" y1="0" x2="1" y2="1">' +
'<stop offset="0%" stop-color="' + colors[0] + '"/>' +
'<stop offset="50%" stop-color="' + colors[1] + '"/>' +
'<stop offset="100%" stop-color="' + colors[2] + '"/></linearGradient></defs>' +
'<rect width="' + w + '" height="' + h + '" fill="url(#' + id + ')"/>' +
'<ellipse cx="' + (w*0.3) + '" cy="' + (h*0.35) + '" rx="180" ry="120" fill="rgba(255,255,255,0.08)"/>' +
'<ellipse cx="' + (w*0.7) + '" cy="' + (h*0.65) + '" rx="220" ry="140" fill="rgba(0,0,0,0.12)"/>' +
'<ellipse cx="' + (w/2) + '" cy="' + (h/2) + '" rx="' + (w*0.32) + '" ry="' + (h*0.35) + '" fill="none" stroke="rgba(255,255,255,0.12)" stroke-width="1.5"/>' +
'<ellipse cx="' + (w/2) + '" cy="' + (h/2) + '" rx="' + (w*0.22) + '" ry="' + (h*0.22) + '" fill="rgba(255,255,255,0.06)"/>' +
'<ellipse cx="' + (w*0.4) + '" cy="' + (h*0.4) + '" rx="60" ry="40" fill="rgba(255,255,255,0.18)"/>' +
'<ellipse cx="' + (w*0.6) + '" cy="' + (h*0.55) + '" rx="40" ry="25" fill="rgba(0,0,0,0.15)"/>' +
'<text x="' + (w/2) + '" y="' + (h*0.18) + '" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="20" font-weight="600" font-family="-apple-system,Microsoft YaHei,sans-serif">餐品制作工艺展示</text>' +
'<text x="' + (w/2) + '" y="' + (h*0.85) + '" text-anchor="middle" fill="rgba(255,255,255,0.35)" font-size="12" font-family="-apple-system,Microsoft YaHei,sans-serif">主厨推荐 · 现场烹制</text>' +
'</svg>';
}
</script>
</body>
@@ -1,5 +1,19 @@
# 餐线营养秤 - 变更记录
## [2026-08-05] feat: 菜品展示页新增视频演示模式
### 新增
- **演示模式切换按钮**`02-dish-display.html`):右上角新增「演示」标识 + 模式切换 pill(常规 / 视频),默认常规模式
- **视频模式布局**:纵向「上视频 + 下信息卡」布局,680px 宽 16:9 视频播放器,圆角阴影悬浮卡片
- **视频播放器组件**:含视频背景、居中播放按钮、控制条(进度条 / 时间 / 音量 / 全屏)
- **菜品信息卡**:视频下方展示菜品名、营养指标(热量 / 蛋白 / 脂肪 / 碳水)、简介描述
- **模式联动**:视频模式激活时隐藏常规模式标题和菜品信息区,切换无闪烁
### 调整
- `02-dish-display.html``main-area` 增加 `mode-video` 状态类,通过 CSS 控制两种布局切换
---
## [2026-07-27] feat: 设置页新增闭餐功能
### 新增