feat: 营养小程序自记餐品功能与若干交互完善
吃模块 - 新增 eat-self-record.html 自记餐品二级页面:左分类右列表+餐品搜索+份/克双维度(克模式数字框可键盘直接输入并保留 +/- 步进),按早中晚餐次独立记录并回显,底部固定栏含明细弹窗与总热量 - eat-home.html 早中晚餐次列表下方追加「+ 自记餐品」入口,餐品右侧热量下方新增来源徽标(员工食堂/自记餐品) 做模块 - make-follow.html 增加 ?from=mine 入口区分,从「我的创作-已发布」进入时主按钮替换为「取消发布」+ 二次确认 - make-my-recipes.html 已发布列表查看链接补 from=mine 参数 我模块 - me-health.html 疾病信息与营养目标分区箭头改为弹窗编辑,疾病史/家族病史/用药情况均支持添加多个标签 导航与文档 - index.html SITE_MAP 新增「自记餐品」条目 - 营养小程序 CHANGELOG 追加 1.2.0 版本记录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
9b57f6164e
commit
42fc2c67f0
@@ -787,6 +787,7 @@ const SITE_MAP = [
|
||||
{ id:'other-terminals', label:'其他终端', icon:'other', color:'#b37feb', modules:[
|
||||
{ name:'营养管理小程序', dir:'other-module/mini-program/nutrition', entry:'eat-home.html', pages:[
|
||||
{label:'吃-首页', file:'eat-home.html'},
|
||||
{label:'自记餐品', file:'eat-self-record.html'},
|
||||
{label:'营养明细', file:'eat-nutrition.html'},
|
||||
{label:'今日食谱', file:'eat-recipe.html'},
|
||||
{label:'今日食谱展示', file:'eat-recipe-today.html'},
|
||||
|
||||
@@ -62,3 +62,23 @@
|
||||
**我模块**
|
||||
- `me-favorites.html` — 收藏列表统计行补充收藏图标,统一为四图标
|
||||
- `make-my-recipes.html` — 已发布食谱列表统计行改为独立四图标展示
|
||||
|
||||
## [1.2.0] - 2026-06-10
|
||||
|
||||
### 新增
|
||||
|
||||
**吃模块**
|
||||
- `eat-self-record.html` — 新增「自记餐品」二级页面:左侧分类、右侧餐品列表,支持餐品名称搜索;按早/中/晚餐次独立记录并回显历史选择;摄入分量支持「份」与「克重」两种维度(克模式数字框可键盘直接输入,并保留 +/- 步进);底部固定栏左侧明细图标可弹窗查看已记餐品清单与总热量,右侧为保存按钮(保存后返回首页)
|
||||
- `index.html` — `SITE_MAP` 营养小程序模块新增「自记餐品」条目
|
||||
|
||||
### 变更
|
||||
|
||||
**吃模块**
|
||||
- `eat-home.html` — 早中晚餐次列表下方新增「+ 自记餐品」按钮,点击跳转 `eat-self-record.html?meal=[餐次]`;餐品列表项右侧热量下方新增来源徽标(员工食堂 / 自记餐品,自记为蓝色);初始列表改由 JS 统一渲染以保持来源字段一致
|
||||
|
||||
**做模块**
|
||||
- `make-follow.html` — 新增 `?from=mine` 入口区分:从「我的创作-已发布」进入时,底部「开始照做」按钮替换为「取消发布」(红色警告样式 + 二次确认弹窗);其他入口保持原按钮
|
||||
- `make-my-recipes.html` — 「已发布」列表中 5 个查看按钮跳转链接补 `?from=mine` 参数,向详情页传递入口标识
|
||||
|
||||
**我模块**
|
||||
- `me-health.html` — 「疾病信息」与「营养目标」两个分区的右侧箭头改为打开编辑弹窗;疾病信息弹窗内三组(疾病史 / 家族病史 / 用药情况)均支持添加多个标签,单个标签可独立 × 删除;营养目标弹窗支持编辑四项指标(每日热量 / 蛋白质 / 膳食纤维 / 饮水量);保存后页面对应区块同步刷新
|
||||
|
||||
@@ -88,8 +88,20 @@
|
||||
.meal-item .food-info { flex: 1; min-width: 0; }
|
||||
.meal-item .food-name { font-size: 14px; font-weight: 500; }
|
||||
.meal-item .food-meta { font-size: 12px; color: #999; margin-top: 2px; }
|
||||
.meal-item .food-cal { font-size: 13px; font-weight: 600; color: #fa8c16; flex-shrink: 0; }
|
||||
</style>
|
||||
/* 右侧:热量 + 来源徽标(员工食堂 / 自记餐品) */
|
||||
.meal-item .food-right { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
|
||||
.meal-item .food-cal { font-size: 13px; font-weight: 600; color: #fa8c16; }
|
||||
.meal-item .food-source { font-size: 10px; color: #999; padding: 1px 6px; border-radius: 3px; background: #f5f5f5; }
|
||||
.meal-item .food-source.self { color: #1677ff; background: #e6f4ff; }
|
||||
/* 自记餐品按钮(位于餐品列表下方) */
|
||||
.self-record-btn-wrap { padding: 4px 16px 16px; background: #fff; }
|
||||
.self-record-btn {
|
||||
width: 100%; padding: 11px 0; border-radius: 12px; border: 1.5px dashed #1677ff;
|
||||
background: #f0f7ff; color: #1677ff; font-size: 14px; font-weight: 600;
|
||||
cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 4px;
|
||||
}
|
||||
.self-record-btn:active { background: #d6e8ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="phone-shell">
|
||||
@@ -121,55 +133,12 @@
|
||||
<div class="meal-tab active">午餐</div>
|
||||
<div class="meal-tab">晚餐</div>
|
||||
</div>
|
||||
<div class="meal-list">
|
||||
<a class="meal-item" href="eat-dish-detail.html">
|
||||
<div class="food-icon">🍗</div>
|
||||
<div class="food-info">
|
||||
<div class="food-name">宫保鸡丁</div>
|
||||
<div class="food-meta">蛋白质 22.4g · 脂肪 14.2g</div>
|
||||
</div>
|
||||
<span class="food-cal">285kcal</span>
|
||||
</a>
|
||||
<a class="meal-item" href="eat-dish-detail.html">
|
||||
<div class="food-icon">🐟</div>
|
||||
<div class="food-info">
|
||||
<div class="food-name">清蒸鱼</div>
|
||||
<div class="food-meta">蛋白质 20.8g · 脂肪 4.6g</div>
|
||||
</div>
|
||||
<span class="food-cal">142kcal</span>
|
||||
</a>
|
||||
<a class="meal-item" href="eat-dish-detail.html">
|
||||
<div class="food-icon">🥦</div>
|
||||
<div class="food-info">
|
||||
<div class="food-name">炒西兰花</div>
|
||||
<div class="food-meta">蛋白质 3.2g · 脂肪 3.8g</div>
|
||||
</div>
|
||||
<span class="food-cal">68kcal</span>
|
||||
</a>
|
||||
<a class="meal-item" href="eat-dish-detail.html">
|
||||
<div class="food-icon">🍚</div>
|
||||
<div class="food-info">
|
||||
<div class="food-name">米饭(中碗)</div>
|
||||
<div class="food-meta">碳水 58g · 蛋白质 4.2g</div>
|
||||
</div>
|
||||
<span class="food-cal">232kcal</span>
|
||||
</a>
|
||||
<a class="meal-item" href="eat-dish-detail.html">
|
||||
<div class="food-icon">🥬</div>
|
||||
<div class="food-info">
|
||||
<div class="food-name">蒜蓉油麦菜</div>
|
||||
<div class="food-meta">蛋白质 1.8g · 脂肪 3.2g</div>
|
||||
</div>
|
||||
<span class="food-cal">52kcal</span>
|
||||
</a>
|
||||
<a class="meal-item" href="eat-dish-detail.html">
|
||||
<div class="food-icon">🍲</div>
|
||||
<div class="food-info">
|
||||
<div class="food-name">番茄蛋花汤</div>
|
||||
<div class="food-meta">蛋白质 4.6g · 脂肪 2.1g</div>
|
||||
</div>
|
||||
<span class="food-cal">48kcal</span>
|
||||
</a>
|
||||
<div class="meal-list"></div>
|
||||
<!-- 自记餐品按钮:跳转至自记餐品二级页面,按当前激活餐次 -->
|
||||
<div class="self-record-btn-wrap">
|
||||
<button class="self-record-btn" onclick="goSelfRecord()">
|
||||
<span style="font-size:16px;">+</span>自记餐品
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -186,52 +155,144 @@
|
||||
<div class="home-indicator"></div>
|
||||
</div>
|
||||
<script>
|
||||
/* 餐次菜品数据 */
|
||||
/* ========== 餐次菜品数据(来源:员工食堂) ========== */
|
||||
var mealData = {
|
||||
'早餐': [
|
||||
{ icon: '🥛', name: '豆浆', meta: '蛋白质 3.6g · 碳水 8.2g', cal: '65kcal' },
|
||||
{ icon: '🥖', name: '油条', meta: '碳水 24g · 脂肪 12.8g', cal: '198kcal' },
|
||||
{ icon: '🥚', name: '水煮蛋', meta: '蛋白质 6.3g · 脂肪 5.3g', cal: '78kcal' },
|
||||
{ icon: '🥣', name: '小米粥', meta: '碳水 16g · 蛋白质 1.8g', cal: '72kcal' }
|
||||
{ icon: '🥛', name: '豆浆', meta: '蛋白质 3.6g · 碳水 8.2g', cal: '65kcal', source: '员工食堂' },
|
||||
{ icon: '🥖', name: '油条', meta: '碳水 24g · 脂肪 12.8g', cal: '198kcal', source: '员工食堂' },
|
||||
{ icon: '🥚', name: '水煮蛋', meta: '蛋白质 6.3g · 脂肪 5.3g', cal: '78kcal', source: '员工食堂' },
|
||||
{ icon: '🥣', name: '小米粥', meta: '碳水 16g · 蛋白质 1.8g', cal: '72kcal', source: '员工食堂' }
|
||||
],
|
||||
'午餐': [
|
||||
{ icon: '🍗', name: '宫保鸡丁', meta: '蛋白质 22.4g · 脂肪 14.2g', cal: '285kcal' },
|
||||
{ icon: '🐟', name: '清蒸鱼', meta: '蛋白质 20.8g · 脂肪 4.6g', cal: '142kcal' },
|
||||
{ icon: '🥦', name: '炒西兰花', meta: '蛋白质 3.2g · 脂肪 3.8g', cal: '68kcal' },
|
||||
{ icon: '🍚', name: '米饭(中碗)', meta: '碳水 58g · 蛋白质 4.2g', cal: '232kcal' },
|
||||
{ icon: '🥬', name: '蒜蓉油麦菜', meta: '蛋白质 1.8g · 脂肪 3.2g', cal: '52kcal' },
|
||||
{ icon: '🍲', name: '番茄蛋花汤', meta: '蛋白质 4.6g · 脂肪 2.1g', cal: '48kcal' }
|
||||
{ icon: '🍗', name: '宫保鸡丁', meta: '蛋白质 22.4g · 脂肪 14.2g', cal: '285kcal', source: '员工食堂' },
|
||||
{ icon: '🐟', name: '清蒸鱼', meta: '蛋白质 20.8g · 脂肪 4.6g', cal: '142kcal', source: '员工食堂' },
|
||||
{ icon: '🥦', name: '炒西兰花', meta: '蛋白质 3.2g · 脂肪 3.8g', cal: '68kcal', source: '员工食堂' },
|
||||
{ icon: '🍚', name: '米饭(中碗)', meta: '碳水 58g · 蛋白质 4.2g', cal: '232kcal', source: '员工食堂' },
|
||||
{ icon: '🥬', name: '蒜蓉油麦菜', meta: '蛋白质 1.8g · 脂肪 3.2g', cal: '52kcal', source: '员工食堂' },
|
||||
{ icon: '🍲', name: '番茄蛋花汤', meta: '蛋白质 4.6g · 脂肪 2.1g', cal: '48kcal', source: '员工食堂' }
|
||||
],
|
||||
'晚餐': [
|
||||
{ icon: '🍳', name: '番茄炒蛋', meta: '蛋白质 10.2g · 脂肪 8.6g', cal: '156kcal' },
|
||||
{ icon: '🫑', name: '青椒肉丝', meta: '蛋白质 14.8g · 脂肪 9.2g', cal: '178kcal' },
|
||||
{ icon: '🥬', name: '紫菜蛋花汤', meta: '蛋白质 3.4g · 脂肪 1.2g', cal: '32kcal' },
|
||||
{ icon: '🍞', name: '馒头', meta: '碳水 44g · 蛋白质 7.0g', cal: '221kcal' }
|
||||
{ icon: '🍳', name: '番茄炒蛋', meta: '蛋白质 10.2g · 脂肪 8.6g', cal: '156kcal', source: '员工食堂' },
|
||||
{ icon: '🫑', name: '青椒肉丝', meta: '蛋白质 14.8g · 脂肪 9.2g', cal: '178kcal', source: '员工食堂' },
|
||||
{ icon: '🥬', name: '紫菜蛋花汤', meta: '蛋白质 3.4g · 脂肪 1.2g', cal: '32kcal', source: '员工食堂' },
|
||||
{ icon: '🍞', name: '馒头', meta: '碳水 44g · 蛋白质 7.0g', cal: '221kcal', source: '员工食堂' }
|
||||
]
|
||||
};
|
||||
|
||||
/* 餐次 Tab 切换 */
|
||||
var tabs = document.querySelectorAll('.meal-tab');
|
||||
/* ========== 自记餐品库(与 eat-self-record.html 保持一致) ========== */
|
||||
/* cal: 每份热量(kcal),gram: 每份重量(g) */
|
||||
var selfDishLib = {
|
||||
'热菜': [
|
||||
{ id: 'h1', icon: '🍗', name: '宫保鸡丁', desc: '鸡胸花生爆炒', cal: 285, gram: 200 },
|
||||
{ id: 'h2', icon: '🐟', name: '清蒸鱼', desc: '草鱼清蒸', cal: 142, gram: 180 },
|
||||
{ id: 'h3', icon: '🥦', name: '炒西兰花', desc: '快炒翠绿', cal: 68, gram: 150 },
|
||||
{ id: 'h4', icon: '🍅', name: '番茄炒蛋', desc: '酸甜可口', cal: 168, gram: 200 },
|
||||
{ id: 'h5', icon: '🍖', name: '红烧肉', desc: '五花肉慢炖', cal: 395, gram: 180 }
|
||||
],
|
||||
'凉菜': [
|
||||
{ id: 'l1', icon: '🥒', name: '凉拌黄瓜', desc: '蒜泥拍黄瓜', cal: 35, gram: 120 },
|
||||
{ id: 'l2', icon: '🥬', name: '凉拌木耳', desc: '醋香爽脆', cal: 56, gram: 100 }
|
||||
],
|
||||
'主食': [
|
||||
{ id: 'z1', icon: '🍚', name: '米饭(中碗)', desc: '蒸米饭 1 碗', cal: 232, gram: 200 },
|
||||
{ id: 'z2', icon: '🍞', name: '馒头', desc: '白面馒头 1 个', cal: 221, gram: 100 },
|
||||
{ id: 'z3', icon: '🍝', name: '面条', desc: '汤面 1 碗', cal: 245, gram: 250 }
|
||||
],
|
||||
'汤品': [
|
||||
{ id: 't1', icon: '🍲', name: '番茄蛋花汤', desc: '清爽番茄', cal: 48, gram: 200 },
|
||||
{ id: 't2', icon: '🥣', name: '紫菜汤', desc: '紫菜虾皮', cal: 32, gram: 200 }
|
||||
],
|
||||
'水果': [
|
||||
{ id: 'f1', icon: '🍎', name: '苹果', desc: '中等大小 1 个', cal: 52, gram: 100 },
|
||||
{ id: 'f2', icon: '🍌', name: '香蕉', desc: '中等大小 1 根', cal: 89, gram: 100 }
|
||||
],
|
||||
'饮品': [
|
||||
{ id: 'y1', icon: '🥛', name: '牛奶', desc: '盒装牛奶 1 盒', cal: 65, gram: 250 },
|
||||
{ id: 'y2', icon: '🧃', name: '豆浆', desc: '原味豆浆 1 杯', cal: 65, gram: 250 }
|
||||
]
|
||||
};
|
||||
|
||||
/* 当前激活的餐次 */
|
||||
var currentMeal = '午餐';
|
||||
|
||||
/* 通过 id 查找自记餐品定义 */
|
||||
function findSelfDish(id) {
|
||||
var hit = null;
|
||||
Object.keys(selfDishLib).forEach(function(cat) {
|
||||
selfDishLib[cat].forEach(function(d) { if (d.id === id) hit = d; });
|
||||
});
|
||||
return hit;
|
||||
}
|
||||
|
||||
/* 读取 sessionStorage 中某餐次的自记原始记录 */
|
||||
function loadSelfRecord(mealType) {
|
||||
try {
|
||||
var raw = sessionStorage.getItem('selfRecord_' + mealType);
|
||||
return raw ? JSON.parse(raw) : {};
|
||||
} catch (e) { return {}; }
|
||||
}
|
||||
|
||||
/* 把某餐次的自记记录转为主列表渲染格式 */
|
||||
function computeSelfItems(mealType) {
|
||||
var state = loadSelfRecord(mealType);
|
||||
var items = [];
|
||||
Object.keys(state).forEach(function(id) {
|
||||
var rec = state[id];
|
||||
if (!rec || !rec.qty || rec.qty <= 0) return;
|
||||
var d = findSelfDish(id);
|
||||
if (!d) return;
|
||||
var cal, qtyText;
|
||||
if (rec.mode === '份') {
|
||||
cal = Math.round(d.cal * rec.qty);
|
||||
qtyText = rec.qty + ' 份';
|
||||
} else {
|
||||
cal = Math.round(d.cal * rec.qty / d.gram);
|
||||
qtyText = rec.qty + ' g';
|
||||
}
|
||||
items.push({
|
||||
icon: d.icon, name: d.name,
|
||||
meta: qtyText + ' · ' + d.desc,
|
||||
cal: cal + 'kcal',
|
||||
source: '自记餐品', isSelf: true
|
||||
});
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
/* ========== 主列表渲染 ========== */
|
||||
var mealTabs = document.querySelectorAll('.meal-tab');
|
||||
var mealList = document.querySelector('.meal-list');
|
||||
|
||||
function renderMeal(mealType) {
|
||||
var items = mealData[mealType] || [];
|
||||
currentMeal = mealType;
|
||||
var items = (mealData[mealType] || []).slice().concat(computeSelfItems(mealType));
|
||||
mealList.innerHTML = items.map(function(d) {
|
||||
return '<a class="meal-item" href="eat-dish-detail.html">'
|
||||
+ '<div class="food-icon">' + d.icon + '</div>'
|
||||
+ '<div class="food-info"><div class="food-name">' + d.name + '</div>'
|
||||
+ '<div class="food-meta">' + d.meta + '</div></div>'
|
||||
+ '<span class="food-cal">' + d.cal + '</span></a>';
|
||||
+ '<div class="food-right">'
|
||||
+ '<span class="food-cal">' + d.cal + '</span>'
|
||||
+ '<span class="food-source' + (d.isSelf ? ' self' : '') + '">' + d.source + '</span>'
|
||||
+ '</div></a>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
tabs.forEach(function(tab) {
|
||||
mealTabs.forEach(function(tab) {
|
||||
tab.addEventListener('click', function() {
|
||||
tabs.forEach(function(t) { t.classList.remove('active'); });
|
||||
mealTabs.forEach(function(t) { t.classList.remove('active'); });
|
||||
tab.classList.add('active');
|
||||
renderMeal(tab.textContent);
|
||||
});
|
||||
});
|
||||
|
||||
/* 跳转到自记餐品二级页面,传当前餐次 */
|
||||
function goSelfRecord() {
|
||||
location.href = 'eat-self-record.html?meal=' + encodeURIComponent(currentMeal);
|
||||
}
|
||||
|
||||
/* 初始渲染:午餐 */
|
||||
renderMeal('午餐');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,538 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>自记餐品 | 智能餐养小程序</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
background: #2a2a2a; display: flex; justify-content: center; align-items: flex-start;
|
||||
min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif;
|
||||
padding: 24px 0 40px;
|
||||
}
|
||||
/* 手机壳 */
|
||||
.phone-shell {
|
||||
width: 390px; height: 844px; background: #fff; border-radius: 44px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3), inset 0 0 0 2px #e0e0e0;
|
||||
overflow: hidden; position: relative; display: flex; flex-direction: column;
|
||||
}
|
||||
.status-bar {
|
||||
height: 44px; background: #fff; display: flex; align-items: center;
|
||||
justify-content: space-between; padding: 0 24px; font-size: 12px; font-weight: 600; flex-shrink: 0;
|
||||
}
|
||||
.status-bar .time { font-size: 15px; font-weight: 700; }
|
||||
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
|
||||
.nav-bar {
|
||||
height: 44px; background: #fff; display: flex; align-items: center;
|
||||
padding: 0 16px; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0; position: relative;
|
||||
}
|
||||
.nav-bar .back { font-size: 22px; color: #1677ff; cursor: pointer; text-decoration: none; line-height: 1; }
|
||||
.nav-bar .title { position: absolute; left: 50%; transform: translateX(-50%); font-size: 17px; font-weight: 600; }
|
||||
.home-indicator {
|
||||
height: 20px; display: flex; align-items: center; justify-content: center; background: #fff; flex-shrink: 0;
|
||||
}
|
||||
.home-indicator::after { content: ''; width: 120px; height: 5px; background: #1a1a1a; border-radius: 3px; opacity: 0.2; }
|
||||
/* === 业务样式 === */
|
||||
/* 搜索栏 */
|
||||
.search-bar { padding: 8px 12px; background: #fff; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0; }
|
||||
.search-input-wrap {
|
||||
display: flex; align-items: center; gap: 6px; height: 34px;
|
||||
background: #f5f5f5; border-radius: 17px; padding: 0 14px;
|
||||
}
|
||||
.search-input-wrap .s-icon { font-size: 14px; color: #999; }
|
||||
.search-input-wrap input {
|
||||
flex: 1; border: none; outline: none; background: transparent;
|
||||
font-size: 13px; color: #333;
|
||||
}
|
||||
.search-input-wrap input::placeholder { color: #bbb; }
|
||||
.search-input-wrap .s-clear {
|
||||
width: 18px; height: 18px; border-radius: 50%; background: #ccc; color: #fff;
|
||||
font-size: 11px; line-height: 18px; text-align: center; cursor: pointer; display: none;
|
||||
}
|
||||
.search-input-wrap .s-clear.show { display: block; }
|
||||
/* 主体:左侧分类 + 右侧餐品列表 */
|
||||
.record-body { display: flex; flex: 1; overflow: hidden; background: #f5f5f5; }
|
||||
.cat-sidebar {
|
||||
width: 84px; background: #f8f8f8; overflow-y: auto; flex-shrink: 0;
|
||||
}
|
||||
.cat-sidebar::-webkit-scrollbar { display: none; }
|
||||
.cat-item {
|
||||
padding: 14px 6px; text-align: center; font-size: 12px; color: #666;
|
||||
border-left: 3px solid transparent; cursor: pointer; position: relative;
|
||||
}
|
||||
.cat-item.active {
|
||||
background: #fff; color: #1677ff; font-weight: 600; border-left-color: #1677ff;
|
||||
}
|
||||
.cat-item .cat-badge {
|
||||
position: absolute; top: 8px; right: 6px;
|
||||
min-width: 16px; height: 16px; line-height: 16px; padding: 0 4px;
|
||||
border-radius: 8px; background: #1677ff; color: #fff;
|
||||
font-size: 10px; font-weight: 600;
|
||||
}
|
||||
.menu-area { flex: 1; overflow-y: auto; padding: 8px 12px 12px; background: #fff; }
|
||||
.menu-area::-webkit-scrollbar { display: none; }
|
||||
.cat-block { margin-bottom: 14px; }
|
||||
.cat-block .cat-title { font-size: 13px; font-weight: 600; color: #333; margin: 6px 0 4px; padding-left: 2px; }
|
||||
.dish-item { display: flex; gap: 10px; padding: 12px 0; border-bottom: 0.5px solid #f0f0f0; }
|
||||
.dish-item:last-child { border-bottom: none; }
|
||||
.dish-item .food-icon {
|
||||
width: 44px; height: 44px; border-radius: 10px; background: #f5f5f5;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 24px; flex-shrink: 0;
|
||||
}
|
||||
.dish-item .dish-body { flex: 1; min-width: 0; }
|
||||
.dish-item .dish-name { font-size: 14px; font-weight: 600; color: #1a1a1a; }
|
||||
.dish-item .dish-meta { font-size: 11px; color: #999; margin-top: 2px; line-height: 1.4; }
|
||||
.dish-ctrl { display: flex; align-items: center; gap: 10px; margin-top: 8px; flex-wrap: wrap; }
|
||||
/* 份/克 切换 */
|
||||
.mode-toggle { display: inline-flex; border: 1px solid #d9d9d9; border-radius: 6px; overflow: hidden; }
|
||||
.mode-opt { padding: 4px 10px; font-size: 12px; color: #666; cursor: pointer; line-height: 1.4; user-select: none; }
|
||||
.mode-opt.active { background: #1677ff; color: #fff; }
|
||||
/* 数量步进(份/克都展示 +/-,克模式数字可手动输入) */
|
||||
.qty-input { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.qty-input .qty-btn {
|
||||
width: 24px; height: 24px; border-radius: 50%; border: 1px solid #d9d9d9;
|
||||
background: #fff; font-size: 14px; color: #333; cursor: pointer;
|
||||
display: flex; align-items: center; justify-content: center; line-height: 1; padding: 0;
|
||||
}
|
||||
.qty-input .qty-btn:active { background: #f5f5f5; }
|
||||
.qty-input .qty-num {
|
||||
width: 54px; height: 24px; text-align: center; font-size: 13px;
|
||||
border: 1px solid #e5e5e5; border-radius: 4px; outline: none;
|
||||
color: #1a1a1a; font-weight: 600; background: #fff; padding: 0 4px;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.qty-input .qty-num:focus { border-color: #1677ff; }
|
||||
.qty-input .qty-num::-webkit-outer-spin-button,
|
||||
.qty-input .qty-num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.qty-input .qty-num:read-only { background: #fafafa; color: #1a1a1a; }
|
||||
.qty-unit { font-size: 12px; color: #666; }
|
||||
/* 空状态(搜索无结果) */
|
||||
.empty-state { padding: 60px 16px; text-align: center; color: #bbb; font-size: 13px; }
|
||||
.empty-state .empty-icon { font-size: 36px; margin-bottom: 8px; }
|
||||
/* 底部操作栏 */
|
||||
.bottom-bar {
|
||||
height: 56px; background: #fff; border-top: 0.5px solid #f0f0f0;
|
||||
display: flex; align-items: center; padding: 0 14px; gap: 10px; flex-shrink: 0;
|
||||
}
|
||||
.bottom-left { display: flex; align-items: center; gap: 10px; flex: 1; }
|
||||
.detail-icon {
|
||||
width: 40px; height: 40px; border-radius: 50%; background: #f0f7ff;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 20px;
|
||||
cursor: pointer; position: relative; flex-shrink: 0;
|
||||
}
|
||||
.detail-icon .detail-badge {
|
||||
position: absolute; top: -2px; right: -2px; min-width: 16px; height: 16px; line-height: 16px;
|
||||
padding: 0 4px; border-radius: 8px; background: #f5222d; color: #fff;
|
||||
font-size: 10px; font-weight: 700; text-align: center;
|
||||
}
|
||||
.detail-icon .detail-badge.empty { display: none; }
|
||||
.bottom-stat .stat-cal { font-size: 16px; font-weight: 700; color: #fa8c16; line-height: 1.2; }
|
||||
.bottom-stat .stat-desc { font-size: 11px; color: #999; margin-top: 2px; }
|
||||
.save-btn {
|
||||
height: 40px; padding: 0 24px; border-radius: 20px; border: none;
|
||||
background: #1677ff; color: #fff; font-size: 15px; font-weight: 600; cursor: pointer;
|
||||
}
|
||||
.save-btn:active { background: #0e5fcc; }
|
||||
/* 已记录餐品明细弹窗 */
|
||||
.modal-mask {
|
||||
position: absolute; inset: 0; background: rgba(0,0,0,0.45);
|
||||
display: none; align-items: flex-end; justify-content: center; z-index: 60;
|
||||
}
|
||||
.modal-mask.active { display: flex; }
|
||||
.modal-panel {
|
||||
width: 100%; max-height: 70%; background: #fff;
|
||||
border-top-left-radius: 16px; border-top-right-radius: 16px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.modal-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 16px; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0;
|
||||
}
|
||||
.modal-header .m-title { font-size: 15px; font-weight: 600; color: #1a1a1a; }
|
||||
.modal-header .m-close { font-size: 18px; color: #999; cursor: pointer; line-height: 1; }
|
||||
.modal-body { padding: 8px 16px 16px; overflow-y: auto; flex: 1; }
|
||||
.modal-body::-webkit-scrollbar { display: none; }
|
||||
.detail-row {
|
||||
display: flex; align-items: center; gap: 10px; padding: 10px 0;
|
||||
border-bottom: 0.5px solid #f5f5f5;
|
||||
}
|
||||
.detail-row:last-child { border-bottom: none; }
|
||||
.detail-row .d-icon {
|
||||
width: 36px; height: 36px; border-radius: 8px; background: #f5f5f5;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0;
|
||||
}
|
||||
.detail-row .d-info { flex: 1; min-width: 0; }
|
||||
.detail-row .d-name { font-size: 13px; font-weight: 600; color: #1a1a1a; }
|
||||
.detail-row .d-meta { font-size: 11px; color: #999; margin-top: 2px; }
|
||||
.detail-row .d-cal { font-size: 13px; font-weight: 700; color: #fa8c16; flex-shrink: 0; }
|
||||
.detail-empty { padding: 40px 16px; text-align: center; color: #bbb; font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="phone-shell">
|
||||
<div class="status-bar">
|
||||
<span class="time">09:41</span>
|
||||
<div class="icons"><span>📶</span><span>WiFi</span><span>🔋</span></div>
|
||||
</div>
|
||||
<div class="nav-bar">
|
||||
<a class="back" href="eat-home.html">‹</a>
|
||||
<span class="title">自记 <span id="mealName">午餐</span></span>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<div class="search-input-wrap">
|
||||
<span class="s-icon">🔍</span>
|
||||
<input type="text" id="searchInput" placeholder="搜索餐品名称">
|
||||
<span class="s-clear" id="searchClear">×</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体:左侧分类 + 右侧餐品列表 -->
|
||||
<div class="record-body">
|
||||
<div class="cat-sidebar" id="catSidebar"></div>
|
||||
<div class="menu-area" id="menuArea"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏:左侧明细 + 总热量,右侧保存 -->
|
||||
<div class="bottom-bar">
|
||||
<div class="bottom-left">
|
||||
<div class="detail-icon" onclick="openDetailModal()">
|
||||
📋<span class="detail-badge empty" id="detailBadge">0</span>
|
||||
</div>
|
||||
<div class="bottom-stat">
|
||||
<div class="stat-cal"><span id="totalCal">0</span> kcal</div>
|
||||
<div class="stat-desc">已记 <span id="totalCount">0</span> 项</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="save-btn" onclick="saveAndBack()">保存</button>
|
||||
</div>
|
||||
<div class="home-indicator"></div>
|
||||
|
||||
<!-- 已记录餐品明细弹窗 -->
|
||||
<div class="modal-mask" id="detailModal">
|
||||
<div class="modal-panel" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<span class="m-title">已记录餐品明细</span>
|
||||
<span class="m-close" onclick="closeDetailModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body" id="detailBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/* ========== 自记餐品库(与 eat-home.html 保持一致) ========== */
|
||||
var selfDishLib = {
|
||||
'热菜': [
|
||||
{ id: 'h1', icon: '🍗', name: '宫保鸡丁', desc: '鸡胸花生爆炒', cal: 285, gram: 200 },
|
||||
{ id: 'h2', icon: '🐟', name: '清蒸鱼', desc: '草鱼清蒸', cal: 142, gram: 180 },
|
||||
{ id: 'h3', icon: '🥦', name: '炒西兰花', desc: '快炒翠绿', cal: 68, gram: 150 },
|
||||
{ id: 'h4', icon: '🍅', name: '番茄炒蛋', desc: '酸甜可口', cal: 168, gram: 200 },
|
||||
{ id: 'h5', icon: '🍖', name: '红烧肉', desc: '五花肉慢炖', cal: 395, gram: 180 }
|
||||
],
|
||||
'凉菜': [
|
||||
{ id: 'l1', icon: '🥒', name: '凉拌黄瓜', desc: '蒜泥拍黄瓜', cal: 35, gram: 120 },
|
||||
{ id: 'l2', icon: '🥬', name: '凉拌木耳', desc: '醋香爽脆', cal: 56, gram: 100 }
|
||||
],
|
||||
'主食': [
|
||||
{ id: 'z1', icon: '🍚', name: '米饭(中碗)', desc: '蒸米饭 1 碗', cal: 232, gram: 200 },
|
||||
{ id: 'z2', icon: '🍞', name: '馒头', desc: '白面馒头 1 个', cal: 221, gram: 100 },
|
||||
{ id: 'z3', icon: '🍝', name: '面条', desc: '汤面 1 碗', cal: 245, gram: 250 }
|
||||
],
|
||||
'汤品': [
|
||||
{ id: 't1', icon: '🍲', name: '番茄蛋花汤', desc: '清爽番茄', cal: 48, gram: 200 },
|
||||
{ id: 't2', icon: '🥣', name: '紫菜汤', desc: '紫菜虾皮', cal: 32, gram: 200 }
|
||||
],
|
||||
'水果': [
|
||||
{ id: 'f1', icon: '🍎', name: '苹果', desc: '中等大小 1 个', cal: 52, gram: 100 },
|
||||
{ id: 'f2', icon: '🍌', name: '香蕉', desc: '中等大小 1 根', cal: 89, gram: 100 }
|
||||
],
|
||||
'饮品': [
|
||||
{ id: 'y1', icon: '🥛', name: '牛奶', desc: '盒装牛奶 1 盒', cal: 65, gram: 250 },
|
||||
{ id: 'y2', icon: '🧃', name: '豆浆', desc: '原味豆浆 1 杯', cal: 65, gram: 250 }
|
||||
]
|
||||
};
|
||||
|
||||
/* ========== 餐次解析 ========== */
|
||||
var params = new URLSearchParams(location.search);
|
||||
var currentMeal = params.get('meal') || '午餐';
|
||||
if (!['早餐','午餐','晚餐'].includes(currentMeal)) currentMeal = '午餐';
|
||||
document.getElementById('mealName').textContent = currentMeal;
|
||||
|
||||
/* ========== 编辑态:从 sessionStorage 回显 ========== */
|
||||
/* 结构:{ dishId: { mode: '份'|'克', qty: number } } */
|
||||
var editingState = {};
|
||||
try {
|
||||
var stored = sessionStorage.getItem('selfRecord_' + currentMeal);
|
||||
if (stored) editingState = JSON.parse(stored) || {};
|
||||
} catch (e) { editingState = {}; }
|
||||
|
||||
/* ========== 搜索关键字 ========== */
|
||||
var keyword = '';
|
||||
|
||||
/* 通过 id 查找餐品定义 */
|
||||
function findDish(id) {
|
||||
var hit = null;
|
||||
Object.keys(selfDishLib).forEach(function(cat) {
|
||||
selfDishLib[cat].forEach(function(d) { if (d.id === id) hit = d; });
|
||||
});
|
||||
return hit;
|
||||
}
|
||||
|
||||
/* ========== 渲染左侧分类导航 ========== */
|
||||
var catSidebar = document.getElementById('catSidebar');
|
||||
var menuArea = document.getElementById('menuArea');
|
||||
|
||||
function renderCatSidebar() {
|
||||
var cats = Object.keys(selfDishLib);
|
||||
catSidebar.innerHTML = cats.map(function(c, i) {
|
||||
var count = getCatRecordCount(c);
|
||||
var badgeHtml = count > 0 ? '<span class="cat-badge">' + count + '</span>' : '';
|
||||
return '<div class="cat-item' + (i === 0 ? ' active' : '') + '" data-cat="' + c + '">' + c + badgeHtml + '</div>';
|
||||
}).join('');
|
||||
catSidebar.querySelectorAll('.cat-item').forEach(function(el) {
|
||||
el.addEventListener('click', function() {
|
||||
catSidebar.querySelectorAll('.cat-item').forEach(function(x) { x.classList.remove('active'); });
|
||||
el.classList.add('active');
|
||||
var target = document.getElementById('cat-' + el.dataset.cat);
|
||||
if (target) menuArea.scrollTop = target.offsetTop - 4;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* 统计某分类下已记录的餐品数量(用于左侧角标) */
|
||||
function getCatRecordCount(cat) {
|
||||
var count = 0;
|
||||
(selfDishLib[cat] || []).forEach(function(d) {
|
||||
if (editingState[d.id] && editingState[d.id].qty > 0) count++;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
/* ========== 渲染右侧餐品列表(支持搜索过滤) ========== */
|
||||
function renderMenu() {
|
||||
var html = '';
|
||||
var hasResult = false;
|
||||
var kw = keyword.trim().toLowerCase();
|
||||
Object.keys(selfDishLib).forEach(function(cat) {
|
||||
var matched = (selfDishLib[cat] || []).filter(function(d) {
|
||||
return !kw || d.name.toLowerCase().indexOf(kw) !== -1;
|
||||
});
|
||||
if (matched.length === 0) return;
|
||||
hasResult = true;
|
||||
html += '<div class="cat-block" id="cat-' + cat + '">';
|
||||
html += '<div class="cat-title">' + cat + '</div>';
|
||||
matched.forEach(function(d) {
|
||||
var rec = editingState[d.id] || { mode: '份', qty: 0 };
|
||||
var isFen = rec.mode === '份';
|
||||
html += ''
|
||||
+ '<div class="dish-item" data-id="' + d.id + '">'
|
||||
+ '<div class="food-icon">' + d.icon + '</div>'
|
||||
+ '<div class="dish-body">'
|
||||
+ '<div class="dish-name">' + d.name + '</div>'
|
||||
+ '<div class="dish-meta">' + d.cal + 'kcal/份 · 约' + d.gram + 'g · ' + d.desc + '</div>'
|
||||
+ '<div class="dish-ctrl">'
|
||||
+ '<div class="mode-toggle" data-id="' + d.id + '">'
|
||||
+ '<span class="mode-opt' + (isFen ? ' active' : '') + '" data-mode="份">份</span>'
|
||||
+ '<span class="mode-opt' + (!isFen ? ' active' : '') + '" data-mode="克">克</span>'
|
||||
+ '</div>'
|
||||
+ '<div class="qty-input" data-id="' + d.id + '">'
|
||||
+ '<button class="qty-btn minus" type="button">−</button>'
|
||||
+ '<input type="number" class="qty-num" value="' + rec.qty + '" min="0">'
|
||||
+ '<button class="qty-btn plus" type="button">+</button>'
|
||||
+ '</div>'
|
||||
+ '<span class="qty-unit">' + rec.mode + '</span>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
});
|
||||
if (!hasResult) {
|
||||
html = '<div class="empty-state"><div class="empty-icon">🔍</div>没有找到匹配的餐品</div>';
|
||||
}
|
||||
menuArea.innerHTML = html;
|
||||
bindDishEvents();
|
||||
}
|
||||
|
||||
/* ========== 行内交互绑定 ========== */
|
||||
function bindDishEvents() {
|
||||
// 模式切换:份 ↔ 克
|
||||
menuArea.querySelectorAll('.mode-toggle').forEach(function(tg) {
|
||||
var id = tg.dataset.id;
|
||||
tg.querySelectorAll('.mode-opt').forEach(function(opt) {
|
||||
opt.addEventListener('click', function() {
|
||||
if (opt.classList.contains('active')) return;
|
||||
tg.querySelectorAll('.mode-opt').forEach(function(o) { o.classList.remove('active'); });
|
||||
opt.classList.add('active');
|
||||
var mode = opt.dataset.mode;
|
||||
var rec = editingState[id] || { mode: '份', qty: 0 };
|
||||
rec.mode = mode;
|
||||
// 切换维度时数量归零,避免单位错乱
|
||||
rec.qty = 0;
|
||||
editingState[id] = rec;
|
||||
var row = tg.closest('.dish-item');
|
||||
row.querySelector('.qty-num').value = 0;
|
||||
row.querySelector('.qty-unit').textContent = mode;
|
||||
refreshSummary();
|
||||
});
|
||||
});
|
||||
});
|
||||
// 数量加减 + 手动输入
|
||||
menuArea.querySelectorAll('.qty-input').forEach(function(qi) {
|
||||
var id = qi.dataset.id;
|
||||
var numInput = qi.querySelector('.qty-num');
|
||||
function getStep() {
|
||||
var rec = editingState[id] || { mode: '份', qty: 0 };
|
||||
return rec.mode === '份' ? 1 : 50;
|
||||
}
|
||||
function commit(v) {
|
||||
v = Math.max(0, v | 0);
|
||||
numInput.value = v;
|
||||
var rec = editingState[id] || { mode: '份', qty: 0 };
|
||||
rec.qty = v;
|
||||
editingState[id] = rec;
|
||||
refreshSummary();
|
||||
}
|
||||
qi.querySelector('.minus').addEventListener('click', function() {
|
||||
commit((parseInt(numInput.value) || 0) - getStep());
|
||||
});
|
||||
qi.querySelector('.plus').addEventListener('click', function() {
|
||||
commit((parseInt(numInput.value) || 0) + getStep());
|
||||
});
|
||||
// 份模式:输入框只读以避免出现小数;克模式:支持直接键盘输入
|
||||
function syncReadonly() {
|
||||
var rec = editingState[id] || { mode: '份', qty: 0 };
|
||||
if (rec.mode === '份') {
|
||||
numInput.setAttribute('readonly', 'readonly');
|
||||
} else {
|
||||
numInput.removeAttribute('readonly');
|
||||
}
|
||||
}
|
||||
syncReadonly();
|
||||
// 监听模式切换后的只读态变化
|
||||
qi.closest('.dish-ctrl').querySelectorAll('.mode-opt').forEach(function(o) {
|
||||
o.addEventListener('click', syncReadonly);
|
||||
});
|
||||
numInput.addEventListener('input', function() {
|
||||
// 仅在克模式下生效(份模式 readonly)
|
||||
commit(parseInt(numInput.value) || 0);
|
||||
});
|
||||
numInput.addEventListener('blur', function() {
|
||||
commit(parseInt(numInput.value) || 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ========== 底部汇总刷新 ========== */
|
||||
function computeTotals() {
|
||||
var totalCal = 0, count = 0;
|
||||
Object.keys(editingState).forEach(function(id) {
|
||||
var rec = editingState[id];
|
||||
if (!rec || !rec.qty || rec.qty <= 0) return;
|
||||
var d = findDish(id); if (!d) return;
|
||||
count++;
|
||||
if (rec.mode === '份') totalCal += d.cal * rec.qty;
|
||||
else totalCal += d.cal * rec.qty / d.gram;
|
||||
});
|
||||
return { totalCal: Math.round(totalCal), count: count };
|
||||
}
|
||||
|
||||
function refreshSummary() {
|
||||
var s = computeTotals();
|
||||
document.getElementById('totalCal').textContent = s.totalCal;
|
||||
document.getElementById('totalCount').textContent = s.count;
|
||||
var badge = document.getElementById('detailBadge');
|
||||
badge.textContent = s.count;
|
||||
badge.classList.toggle('empty', s.count === 0);
|
||||
// 同步左侧分类角标
|
||||
catSidebar.querySelectorAll('.cat-item').forEach(function(el) {
|
||||
var cat = el.dataset.cat;
|
||||
var c = getCatRecordCount(cat);
|
||||
var oldBadge = el.querySelector('.cat-badge');
|
||||
if (c > 0) {
|
||||
if (oldBadge) oldBadge.textContent = c;
|
||||
else {
|
||||
var b = document.createElement('span');
|
||||
b.className = 'cat-badge';
|
||||
b.textContent = c;
|
||||
el.appendChild(b);
|
||||
}
|
||||
} else if (oldBadge) {
|
||||
oldBadge.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ========== 已记录明细弹窗 ========== */
|
||||
function openDetailModal() {
|
||||
var body = document.getElementById('detailBody');
|
||||
var rows = [];
|
||||
Object.keys(editingState).forEach(function(id) {
|
||||
var rec = editingState[id];
|
||||
if (!rec || !rec.qty || rec.qty <= 0) return;
|
||||
var d = findDish(id); if (!d) return;
|
||||
var cal, qtyText;
|
||||
if (rec.mode === '份') {
|
||||
cal = Math.round(d.cal * rec.qty);
|
||||
qtyText = rec.qty + ' 份';
|
||||
} else {
|
||||
cal = Math.round(d.cal * rec.qty / d.gram);
|
||||
qtyText = rec.qty + ' g';
|
||||
}
|
||||
rows.push(''
|
||||
+ '<div class="detail-row">'
|
||||
+ '<div class="d-icon">' + d.icon + '</div>'
|
||||
+ '<div class="d-info"><div class="d-name">' + d.name + '</div>'
|
||||
+ '<div class="d-meta">' + qtyText + ' · ' + d.desc + '</div></div>'
|
||||
+ '<div class="d-cal">' + cal + ' kcal</div>'
|
||||
+ '</div>'
|
||||
);
|
||||
});
|
||||
body.innerHTML = rows.length
|
||||
? rows.join('')
|
||||
: '<div class="detail-empty">还没有记录任何餐品</div>';
|
||||
document.getElementById('detailModal').classList.add('active');
|
||||
}
|
||||
function closeDetailModal() {
|
||||
document.getElementById('detailModal').classList.remove('active');
|
||||
}
|
||||
document.getElementById('detailModal').addEventListener('click', closeDetailModal);
|
||||
|
||||
/* ========== 搜索 ========== */
|
||||
var searchInput = document.getElementById('searchInput');
|
||||
var searchClear = document.getElementById('searchClear');
|
||||
searchInput.addEventListener('input', function() {
|
||||
keyword = searchInput.value;
|
||||
searchClear.classList.toggle('show', !!keyword);
|
||||
renderMenu();
|
||||
});
|
||||
searchClear.addEventListener('click', function() {
|
||||
searchInput.value = '';
|
||||
keyword = '';
|
||||
searchClear.classList.remove('show');
|
||||
renderMenu();
|
||||
searchInput.focus();
|
||||
});
|
||||
|
||||
/* ========== 保存并返回 ========== */
|
||||
function saveAndBack() {
|
||||
var clean = {};
|
||||
Object.keys(editingState).forEach(function(id) {
|
||||
var rec = editingState[id];
|
||||
if (rec && rec.qty > 0) clean[id] = { mode: rec.mode, qty: rec.qty };
|
||||
});
|
||||
try {
|
||||
sessionStorage.setItem('selfRecord_' + currentMeal, JSON.stringify(clean));
|
||||
} catch (e) { /* 忽略存储异常,保持原型可演示 */ }
|
||||
location.href = 'eat-home.html';
|
||||
}
|
||||
|
||||
/* ========== 初始化 ========== */
|
||||
renderCatSidebar();
|
||||
renderMenu();
|
||||
refreshSummary();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -101,6 +101,8 @@
|
||||
.btn-collect { background: #fff7e6; color: #fa8c16; border: 1px solid #ffd591 !important; }
|
||||
.intro-text { font-size: 13px; color: #555; line-height: 1.7; }
|
||||
.btn-start { background: #1677ff; color: #fff; }
|
||||
/* 取消发布按钮(仅当从「我的创作-已发布」入口进入时显示) */
|
||||
.btn-unpublish { background: #fff1f0; color: #f5222d; border: 1px solid #ffccc7 !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -168,9 +170,28 @@
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-like">❤️ 128</button>
|
||||
<button class="btn btn-collect">⭐ 95</button>
|
||||
<button class="btn btn-start">开始照做</button>
|
||||
<button class="btn btn-start" id="actionMainBtn">开始照做</button>
|
||||
</div>
|
||||
<div class="home-indicator"></div>
|
||||
</div>
|
||||
<script>
|
||||
/* 入口区分:?from=mine 表示从「我的创作-已发布」进入,主按钮改为「取消发布」 */
|
||||
(function() {
|
||||
var params = new URLSearchParams(location.search);
|
||||
if (params.get('from') === 'mine') {
|
||||
var btn = document.getElementById('actionMainBtn');
|
||||
if (btn) {
|
||||
btn.textContent = '取消发布';
|
||||
btn.classList.remove('btn-start');
|
||||
btn.classList.add('btn-unpublish');
|
||||
btn.addEventListener('click', function() {
|
||||
if (confirm('确认取消发布该食谱?取消后将移至草稿箱。')) {
|
||||
alert('已取消发布(原型演示)');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
<span class="stat">👁️ 1.2k</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-btn view" href="make-follow.html">查看</a>
|
||||
<a class="action-btn view" href="make-follow.html?from=mine">查看</a>
|
||||
</div>
|
||||
<div class="recipe-card">
|
||||
<div class="cover" style="background:#f6ffed">🥗</div>
|
||||
@@ -154,7 +154,7 @@
|
||||
<span class="stat">👁️ 860</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-btn view" href="make-follow.html">查看</a>
|
||||
<a class="action-btn view" href="make-follow.html?from=mine">查看</a>
|
||||
</div>
|
||||
<div class="recipe-card">
|
||||
<div class="cover" style="background:#e6f4ff">🐟</div>
|
||||
@@ -167,7 +167,7 @@
|
||||
<span class="stat">👁️ 420</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-btn view" href="make-follow.html">查看</a>
|
||||
<a class="action-btn view" href="make-follow.html?from=mine">查看</a>
|
||||
</div>
|
||||
<div class="recipe-card">
|
||||
<div class="cover" style="background:#fff1f0">🥦</div>
|
||||
@@ -180,7 +180,7 @@
|
||||
<span class="stat">👁️ 180</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-btn view" href="make-follow.html">查看</a>
|
||||
<a class="action-btn view" href="make-follow.html?from=mine">查看</a>
|
||||
</div>
|
||||
<div class="recipe-card">
|
||||
<div class="cover" style="background:#f9f0ff">🍜</div>
|
||||
@@ -193,7 +193,7 @@
|
||||
<span class="stat">👁️ 90</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="action-btn view" href="make-follow.html">查看</a>
|
||||
<a class="action-btn view" href="make-follow.html?from=mine">查看</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -63,6 +63,71 @@
|
||||
.goal-item:last-child { border-bottom: none; }
|
||||
.goal-item .g-label { font-size: 13px; color: #333; }
|
||||
.goal-item .g-value { font-size: 13px; color: #1677ff; font-weight: 600; }
|
||||
/* ========== 编辑弹窗通用样式 ========== */
|
||||
/* 遮罩层:覆盖整个 phone-shell */
|
||||
.modal-mask {
|
||||
position: absolute; inset: 0; background: rgba(0,0,0,0.45);
|
||||
display: none; align-items: flex-end; justify-content: center; z-index: 50;
|
||||
}
|
||||
.modal-mask.active { display: flex; }
|
||||
/* 弹窗容器:底部弹出 */
|
||||
.modal-panel {
|
||||
width: 100%; max-height: 80%; background: #fff;
|
||||
border-top-left-radius: 16px; border-top-right-radius: 16px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.modal-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 16px; border-bottom: 0.5px solid #f0f0f0; flex-shrink: 0;
|
||||
}
|
||||
.modal-header .m-cancel { font-size: 14px; color: #999; cursor: pointer; }
|
||||
.modal-header .m-title { font-size: 15px; font-weight: 600; color: #1a1a1a; }
|
||||
.modal-header .m-save { font-size: 14px; color: #1677ff; font-weight: 600; cursor: pointer; }
|
||||
.modal-body { padding: 12px 16px 8px; overflow-y: auto; flex: 1; }
|
||||
.modal-body::-webkit-scrollbar { display: none; }
|
||||
/* 弹窗中分组 */
|
||||
.m-group { margin-bottom: 16px; }
|
||||
.m-group .m-g-label { font-size: 13px; color: #666; font-weight: 500; margin-bottom: 8px; }
|
||||
/* 可编辑的标签列表(带 × 删除) */
|
||||
.m-tag-list { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.m-tag {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 5px 8px 5px 12px; border-radius: 14px; font-size: 12px;
|
||||
background: #fff1f0; color: #f5222d;
|
||||
}
|
||||
.m-tag.med { background: #f0f7ff; color: #1677ff; }
|
||||
.m-tag .m-del {
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
background: rgba(0,0,0,0.08); color: #666;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 12px; line-height: 1; cursor: pointer; font-weight: 700;
|
||||
}
|
||||
/* 添加输入区 */
|
||||
.m-add-row { display: flex; gap: 8px; margin-top: 10px; }
|
||||
.m-add-row input {
|
||||
flex: 1; height: 32px; border: 1px solid #e5e5e5; border-radius: 16px;
|
||||
padding: 0 12px; font-size: 13px; outline: none; background: #fafafa;
|
||||
}
|
||||
.m-add-row input:focus { border-color: #1677ff; background: #fff; }
|
||||
.m-add-row .m-add-btn {
|
||||
height: 32px; padding: 0 16px; border-radius: 16px; border: none;
|
||||
background: #1677ff; color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;
|
||||
}
|
||||
/* 营养目标弹窗:数值输入行 */
|
||||
.m-goal-row {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 12px 0; border-bottom: 0.5px solid #f0f0f0;
|
||||
}
|
||||
.m-goal-row:last-child { border-bottom: none; }
|
||||
.m-goal-row .mg-label { font-size: 13px; color: #333; }
|
||||
.m-goal-row .mg-input-wrap { display: flex; align-items: center; gap: 4px; }
|
||||
.m-goal-row input {
|
||||
width: 80px; height: 30px; border: 1px solid #e5e5e5; border-radius: 6px;
|
||||
padding: 0 10px; font-size: 13px; outline: none; text-align: right;
|
||||
color: #1677ff; font-weight: 600;
|
||||
}
|
||||
.m-goal-row input:focus { border-color: #1677ff; }
|
||||
.m-goal-row .mg-unit { font-size: 12px; color: #999; min-width: 36px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -128,41 +193,205 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 疾病信息 -->
|
||||
<div class="section" onclick="alert('【交互注释】进入疾病信息编辑页')" style="cursor:pointer;">
|
||||
<div class="section" id="diseaseSection" onclick="openDiseaseModal()" style="cursor:pointer;">
|
||||
<div class="sec-header"><span class="sec-title">疾病信息</span><span style="font-size:14px;color:#ccc;">›</span></div>
|
||||
<div class="disease-list">
|
||||
<div class="disease-item">
|
||||
<div class="d-label">疾病史</div>
|
||||
<div class="d-tags">
|
||||
<div class="d-tags" id="viewDiseaseHistory">
|
||||
<span class="d-tag">胃炎</span>
|
||||
<span class="d-tag">高尿酸</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="disease-item">
|
||||
<div class="d-label">家族病史</div>
|
||||
<div class="d-tags">
|
||||
<div class="d-tags" id="viewFamilyHistory">
|
||||
<span class="d-tag">高血压</span>
|
||||
<span class="d-tag">糖尿病</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="disease-item">
|
||||
<div class="d-label">用药情况</div>
|
||||
<div class="d-tags">
|
||||
<div class="d-tags" id="viewMedications">
|
||||
<span class="d-tag med">别嘌醇片</span><span class="d-tag med">依那普利</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 营养目标 -->
|
||||
<div class="section" onclick="alert('【交互注释】进入营养目标编辑页')" style="cursor:pointer;">
|
||||
<div class="section" id="goalSection" onclick="openGoalModal()" style="cursor:pointer;">
|
||||
<div class="sec-header"><span class="sec-title">营养目标</span><span style="font-size:14px;color:#ccc;">›</span></div>
|
||||
<div class="goal-item"><span class="g-label">每日热量</span><span class="g-value">2000 kcal</span></div>
|
||||
<div class="goal-item"><span class="g-label">蛋白质</span><span class="g-value">≥60g</span></div>
|
||||
<div class="goal-item"><span class="g-label">膳食纤维</span><span class="g-value">≥25g</span></div>
|
||||
<div class="goal-item"><span class="g-label">饮水量</span><span class="g-value">≥2000ml</span></div>
|
||||
<div class="goal-item"><span class="g-label">每日热量</span><span class="g-value" id="viewGoalCalorie">2000 kcal</span></div>
|
||||
<div class="goal-item"><span class="g-label">蛋白质</span><span class="g-value" id="viewGoalProtein">≥60g</span></div>
|
||||
<div class="goal-item"><span class="g-label">膳食纤维</span><span class="g-value" id="viewGoalFiber">≥25g</span></div>
|
||||
<div class="goal-item"><span class="g-label">饮水量</span><span class="g-value" id="viewGoalWater">≥2000ml</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-indicator"></div>
|
||||
|
||||
<!-- ========== 疾病信息编辑弹窗 ========== -->
|
||||
<div class="modal-mask" id="diseaseModal">
|
||||
<div class="modal-panel" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<span class="m-cancel" onclick="closeDiseaseModal()">取消</span>
|
||||
<span class="m-title">编辑疾病信息</span>
|
||||
<span class="m-save" onclick="saveDiseaseModal()">保存</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- 疾病史 -->
|
||||
<div class="m-group">
|
||||
<div class="m-g-label">疾病史(可添加多个)</div>
|
||||
<div class="m-tag-list" id="editDiseaseHistory"></div>
|
||||
<div class="m-add-row">
|
||||
<input type="text" id="inputDiseaseHistory" placeholder="如:胃炎、高血压" maxlength="20">
|
||||
<button class="m-add-btn" onclick="addDiseaseTag('editDiseaseHistory','inputDiseaseHistory')">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 家族病史 -->
|
||||
<div class="m-group">
|
||||
<div class="m-g-label">家族病史(可添加多个)</div>
|
||||
<div class="m-tag-list" id="editFamilyHistory"></div>
|
||||
<div class="m-add-row">
|
||||
<input type="text" id="inputFamilyHistory" placeholder="如:糖尿病、冠心病" maxlength="20">
|
||||
<button class="m-add-btn" onclick="addDiseaseTag('editFamilyHistory','inputFamilyHistory')">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 用药情况 -->
|
||||
<div class="m-group">
|
||||
<div class="m-g-label">用药情况(可添加多个)</div>
|
||||
<div class="m-tag-list" id="editMedications"></div>
|
||||
<div class="m-add-row">
|
||||
<input type="text" id="inputMedications" placeholder="如:别嘌醇片" maxlength="20">
|
||||
<button class="m-add-btn" onclick="addDiseaseTag('editMedications','inputMedications')">添加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 营养目标编辑弹窗 ========== -->
|
||||
<div class="modal-mask" id="goalModal">
|
||||
<div class="modal-panel" onclick="event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<span class="m-cancel" onclick="closeGoalModal()">取消</span>
|
||||
<span class="m-title">编辑营养目标</span>
|
||||
<span class="m-save" onclick="saveGoalModal()">保存</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="m-goal-row">
|
||||
<span class="mg-label">每日热量</span>
|
||||
<span class="mg-input-wrap"><input type="number" id="inputCalorie" value="2000"><span class="mg-unit">kcal</span></span>
|
||||
</div>
|
||||
<div class="m-goal-row">
|
||||
<span class="mg-label">蛋白质(≥)</span>
|
||||
<span class="mg-input-wrap"><input type="number" id="inputProtein" value="60"><span class="mg-unit">g</span></span>
|
||||
</div>
|
||||
<div class="m-goal-row">
|
||||
<span class="mg-label">膳食纤维(≥)</span>
|
||||
<span class="mg-input-wrap"><input type="number" id="inputFiber" value="25"><span class="mg-unit">g</span></span>
|
||||
</div>
|
||||
<div class="m-goal-row">
|
||||
<span class="mg-label">饮水量(≥)</span>
|
||||
<span class="mg-input-wrap"><input type="number" id="inputWater" value="2000"><span class="mg-unit">ml</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/* ========== 疾病信息编辑弹窗 ========== */
|
||||
// 当前编辑状态(页面初始数据;保存时同步回页面)
|
||||
var diseaseState = {
|
||||
editDiseaseHistory: { tags: ['胃炎', '高尿酸'], cls: '' },
|
||||
editFamilyHistory: { tags: ['高血压', '糖尿病'], cls: '' },
|
||||
editMedications: { tags: ['别嘌醇片', '依那普利'], cls: 'med' }
|
||||
};
|
||||
|
||||
// 渲染弹窗中某一组的标签列表(带 × 删除)
|
||||
function renderDiseaseGroup(groupId) {
|
||||
var box = document.getElementById(groupId);
|
||||
var state = diseaseState[groupId];
|
||||
box.innerHTML = '';
|
||||
state.tags.forEach(function(tag, idx) {
|
||||
var span = document.createElement('span');
|
||||
span.className = 'm-tag' + (state.cls ? ' ' + state.cls : '');
|
||||
span.innerHTML = tag + '<span class="m-del">×</span>';
|
||||
span.querySelector('.m-del').addEventListener('click', function() {
|
||||
state.tags.splice(idx, 1);
|
||||
renderDiseaseGroup(groupId);
|
||||
});
|
||||
box.appendChild(span);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加一个新标签到指定分组
|
||||
function addDiseaseTag(groupId, inputId) {
|
||||
var input = document.getElementById(inputId);
|
||||
var val = (input.value || '').trim();
|
||||
if (!val) { alert('请输入内容'); return; }
|
||||
if (diseaseState[groupId].tags.indexOf(val) !== -1) { alert('该项已存在'); return; }
|
||||
diseaseState[groupId].tags.push(val);
|
||||
input.value = '';
|
||||
renderDiseaseGroup(groupId);
|
||||
}
|
||||
|
||||
// 打开疾病信息弹窗
|
||||
function openDiseaseModal() {
|
||||
renderDiseaseGroup('editDiseaseHistory');
|
||||
renderDiseaseGroup('editFamilyHistory');
|
||||
renderDiseaseGroup('editMedications');
|
||||
document.getElementById('diseaseModal').classList.add('active');
|
||||
}
|
||||
function closeDiseaseModal() {
|
||||
document.getElementById('diseaseModal').classList.remove('active');
|
||||
}
|
||||
// 保存:将编辑结果写回页面对应区块
|
||||
function saveDiseaseModal() {
|
||||
function syncBack(viewId, groupId) {
|
||||
var view = document.getElementById(viewId);
|
||||
var state = diseaseState[groupId];
|
||||
view.innerHTML = '';
|
||||
if (state.tags.length === 0) {
|
||||
view.innerHTML = '<span class="d-empty">暂无</span>';
|
||||
return;
|
||||
}
|
||||
state.tags.forEach(function(tag) {
|
||||
var s = document.createElement('span');
|
||||
s.className = 'd-tag' + (state.cls ? ' ' + state.cls : '');
|
||||
s.textContent = tag;
|
||||
view.appendChild(s);
|
||||
});
|
||||
}
|
||||
syncBack('viewDiseaseHistory', 'editDiseaseHistory');
|
||||
syncBack('viewFamilyHistory', 'editFamilyHistory');
|
||||
syncBack('viewMedications', 'editMedications');
|
||||
closeDiseaseModal();
|
||||
alert('已保存(原型演示)');
|
||||
}
|
||||
|
||||
/* ========== 营养目标编辑弹窗 ========== */
|
||||
function openGoalModal() {
|
||||
document.getElementById('goalModal').classList.add('active');
|
||||
}
|
||||
function closeGoalModal() {
|
||||
document.getElementById('goalModal').classList.remove('active');
|
||||
}
|
||||
function saveGoalModal() {
|
||||
var cal = document.getElementById('inputCalorie').value || '0';
|
||||
var pro = document.getElementById('inputProtein').value || '0';
|
||||
var fib = document.getElementById('inputFiber').value || '0';
|
||||
var wat = document.getElementById('inputWater').value || '0';
|
||||
document.getElementById('viewGoalCalorie').textContent = cal + ' kcal';
|
||||
document.getElementById('viewGoalProtein').textContent = '≥' + pro + 'g';
|
||||
document.getElementById('viewGoalFiber').textContent = '≥' + fib + 'g';
|
||||
document.getElementById('viewGoalWater').textContent = '≥' + wat + 'ml';
|
||||
closeGoalModal();
|
||||
alert('已保存(原型演示)');
|
||||
}
|
||||
|
||||
/* 点击遮罩层空白处关闭弹窗 */
|
||||
document.getElementById('diseaseModal').addEventListener('click', closeDiseaseModal);
|
||||
document.getElementById('goalModal').addEventListener('click', closeGoalModal);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user