feat: 管理端场所/分类管理增强及小程序端交互优化

- 后台营养管理:场所新增/编辑页重构(营业时段/图片/资质)、食材及餐品分类管理弹窗、从已有食材复制
- 营养管理小程序:净菜包订单按状态差异化展示、时间选择器交互重构、新增收货地址页、移除营养目标分区
- CHANGELOG 更新至 2026-07-23

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fengpu
2026-07-23 16:49:25 +08:00
co-authored by Claude Opus 4.7
parent bab86f72f3
commit ea8239debf
19 changed files with 1911 additions and 190 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
_Last synced: 2026-07-20 | Base commit: `5c518cb`_
_Last synced: 2026-07-21 | Base commit: `f671764`_
# 记忆索引
| 文件 | 描述 | 类型 | 最后更新 |
|------|------|------|----------|
| project_overview.md | 项目技术栈、架构与模块结构 | project | 2026-05-14 |
| project_progress.md | 各模块原型开发进度 | project | 2026-07-20 |
| project_progress.md | 各模块原型开发进度 | project | 2026-07-21 |
| decisions.md | 关键设计决策与架构约定 | project | 2026-06-15 |
| feedback.md | 协作规范与用户纠正记录 | feedback | 2026-06-15 |
| user_profile.md | 用户角色与偏好 | user | 2026-04-21 |
+6 -6
View File
@@ -2,12 +2,12 @@
name: 记忆健康检查报告
description: memory-lint 最新一次执行的检查结果与待处理项
type: lint
last_updated: 2026-07-20
last_updated: 2026-07-21
---
# 记忆健康检查报告
> _执行时间: 2026-07-20 | Base commit: `5c518cb` | Last synced: 2026-07-20_
> _执行时间: 2026-07-21 | Base commit: `f671764` | Last synced: 2026-07-21_
## 健康概览
@@ -35,17 +35,17 @@ last_updated: 2026-07-20
### [ERROR] 过期记忆
- **文件**`user_profile.md`last_updated: 2026-04-21,已过期 90 天)
- **文件**`user_profile.md`last_updated: 2026-04-21,已过期 91 天)
- **建议**:若用户角色或协作偏好发生变化,执行 `/memory-update` 更新;否则可忽略
### [WARN] 过期记忆
- **文件**`project_overview.md`last_updated: 2026-05-14,已过期 67 天)
- **文件**`project_overview.md`last_updated: 2026-05-14,已过期 68 天)
- 自上次更新后项目新增净菜贴标秤、净菜组配秤重构、营养边缘服务、西安应急大屏 v2/v3、蓝牙体重秤小程序等模块
- **建议**:若需同步技术栈/架构变化,执行 `/memory-update` 或手动补充模块结构说明
- **文件**`decisions.md`last_updated: 2026-06-15,已过期 35 天)
- **文件**`decisions.md`last_updated: 2026-06-15,已过期 36 天)
- **建议**:若期间产生新的关键决策,执行 `/memory-update` 补充;否则可忽略
- **文件**`feedback.md`last_updated: 2026-06-15,已过期 35 天)
- **文件**`feedback.md`last_updated: 2026-06-15,已过期 36 天)
- **建议**:若期间产生新的协作规范纠正,执行 `/memory-update` 补充;否则可忽略
---
+1
View File
@@ -89,6 +89,7 @@ Web 管理端和员工端 APP 的其余模块(系统管理、运动管理、
- 覆盖模块:18 个(含三端+其他终端)
## 近期里程碑
- 2026-07-21:导航页 popover 事件冒泡回归再次修复(同一根因第二次出现)——0603113 提交将 popover 重构为延迟渲染模式(首次点击"X 页"才动态构建 DOM),但 `togglePop` 函数内 `insertAdjacentHTML` 模板漏加 `onclick="event.stopPropagation()"`,导致同样的双 tab 打开回归。修复方式:在延迟构建模板中补上 stopPropagationcommit f671764)。系统性教训:嵌套 onclick 结构中子容器的 stopPropagation 在代码重构时极易遗漏,后续涉及 popover/弹层 DOM 创建位置变更时需检查事件冒泡拦截。
- 2026-07-20:导航页 popover 事件冒泡回归修复——8a96ead 提交重构 popover 关闭逻辑时误删 `<div class="page-popover">` 上的 `onclick="event.stopPropagation()"`,导致 popover 内 `<a>` 点击事件冒泡到父级 `module-card` 触发 `window.open` 打开模块入口页,与 `<a>` 默认跳转叠加造成点击"页面总览"等列表项时同时打开两个 tab。修复方式:恢复 popover 容器上的 stopPropagation,与新版本 document click 监听器(靠 `closest` 判断)兼容(commit 5c518cb)。
- 2026-07:本月重要变更摘要——新增蓝牙体重秤小程序模块 `other-module/bluetooth-scale-miniprogram/`(11 页:登录/搜索设备/wifi连接/添加成员/设备成员/主页/数据详情/资料完善/帮助中心/帮助详情/资料);西安应急大屏 v3 系列重构与扩展(主屏 v3 重构+路径规划纵向逐步导航+体检雷达/穿戴统计数值带单位+体重管理今日维度、cv 副屏重构为穿戴告警监测+新增告警趋势/心脑血管预警/告警时段分布、新增 help-monitor 应急求助副屏、新增多个业务记录弹窗含筛选与分页、站点入口指向 v3);营养管理后台菜单用户操作权限划分;营养管理小程序原型页面调整(小程序购买净菜包+健康档案+加餐数据展示);体重管理 web-admin 多页面更新(archive-unit-report-detail、history-personnel-list/detail、plan-edit、action-record 等);健康监测模块更新。
- 2026-06-26:餐厅终端设备净菜包装秤与组配秤迭代——净菜包装秤全模块完成“贴标”到“包装”的术语统一,包装作业页清单按 `packSize` 展开为单份、移除称重弹窗并改为直接读秤,新增搜索框与 `label-preview.html` 标签预览页;净菜组配秤任务/历史页按单份拆分展示,食材名称去除丝/段/片/块/液等后缀,组配作业页移除确认弹窗并优化底部按钮布局;同步删除根目录个人化全平台进度生成脚本 `scripts/generate-overview.js`
-2
View File
@@ -1,5 +1,3 @@
<!-- Last updated: 2026-07-20 | Commit: 5c518cb -->
# 项目约束
本项目所有工作必须严格遵守 `项目背景.md` 中定义的全部规范,包括:
+5 -4
View File
@@ -439,8 +439,8 @@ stateDiagram-v2
**主要UI元素:**
- 导航栏标题:当前日期
- 今日摘要栏:显示已记录餐数和总摄入热量,右侧"历史记录"链接跳转到摄入记录页面
- 四餐卡片(早餐/午餐/晚餐/加餐):各显示已摄入热量、推荐热量参考、来源徽章(智记/自记/未记录)
- 今日摘要栏:显示当日已记录餐数和总摄入热量,右侧"历史记录"链接跳转到摄入记录页面
- 四餐卡片(早餐/午餐/晚餐/加餐):各显示已摄入热量、推荐热量参考、来源徽章(智记/自记/智记+自记/未记录)
- 类型显示当日总记录卡片(智记/自记)
- 自记区域:搜索框、左侧分类列表、右侧菜品列表(含+/-按钮)
- 底部固定栏:购物袋图标(显示已选菜品数量)、总热量、提交按钮
@@ -470,8 +470,8 @@ stateDiagram-v2
**主要UI元素:**
- 导航栏标题:当前周日期范围(如"08-10 ~ 08-16"
- 今日摘要栏:显示当天日期、星期、今日消耗热量,"历史记录"链接跳转到消耗记录页面
- 一周日期卡片(2行,4+3布局):周一至周日,已记录天显示步数/消耗热量+来源徽章(微信/穿戴/器械/自记)、推荐步数,当天高亮
- 来源类型卡片:穿戴设备(步数)、器械运动(已记录时长)、微信运动(步数+同步按钮)、自记运动(已记录时长
- 一周日期卡片(2行,4+3布局):周一至周日,已记录天显示步数/消耗热量(有步数只显示步数,没有步数则显示消耗热量)+来源徽章(微信/穿戴/器械/自记)、推荐步数,当天高亮
- 来源类型卡片:穿戴设备(步数)、器械运动(已记录时长)、微信运动(步数+同步按钮)、自记运动(步数/已记录运动时长【有步数只显示步数,没有步数则显示运动时长】
- 自记区域:搜索框、运动分类、运动项目列表(含+/-按钮)
- 底部固定栏:购物袋图标(显示已选运动数)、总消耗热量、提交按钮
@@ -488,6 +488,7 @@ stateDiagram-v2
- 穿戴设备未记录时,点击提示“您未配备或未连接穿戴设备”弹窗
- 日期卡片中的已记录数据,如果穿戴、自记、微信都有步数,取最大值。如果没有步数,则显示消耗千卡
- 微信运动需手动触发同步
- 注意:因穿戴设备数据以及微信运动数据存在补传历史数据的情况,体重管理业务中只再做数据归档时允许修正T-2的数据(在归档昨天的数据前要修正前天的数据);
---
+1
View File
@@ -851,6 +851,7 @@ const SITE_MAP = [
{label:'我的创作', file:'make-my-recipes.html'},
{label:'微信授权登录', file:'me-login.html'},
{label:'个人中心', file:'me-home.html'},
{label:'收货地址', file:'me-address.html'},
{label:'健康档案', file:'me-health.html'},
{label:'我的订单', file:'me-orders.html'},
{label:'订单详情', file:'me-order-detail.html'},
@@ -92,16 +92,42 @@
.pickup-method .pm-name { font-size: 13px; font-weight: 600; margin-top: 4px; color: #333; }
.pickup-method .pm-desc { font-size: 10px; color: #999; margin-top: 2px; }
.pickup-method.active .pm-name { color: #1677ff; }
/* 日期/时段 chip */
.chip-label { font-size: 11px; color: #999; margin: 8px 0 6px; }
.date-chips, .time-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.date-chip, .time-chip {
padding: 6px 10px; border-radius: 14px; background: #f5f5f5;
font-size: 11px; color: #666; cursor: pointer; line-height: 1.4;
/* 期望配送时间:下拉框风格 */
.time-picker-trigger {
display: flex; align-items: center; justify-content: space-between;
height: 38px; padding: 0 12px; border-radius: 8px;
border: 1px solid #e8e8e8; background: #fff; cursor: pointer;
}
.date-chip.active, .time-chip.active {
background: #1677ff; color: #fff; font-weight: 600;
.tpt-text { font-size: 13px; color: #333; }
.tpt-arrow { font-size: 10px; color: #999; transition: transform 0.2s; }
.tpt-arrow.open { transform: rotate(180deg); }
.time-picker-panel {
display: none; margin-top: 6px; gap: 8px;
}
.time-picker-panel.show { display: flex; }
.tp-date-col {
width: 88px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px;
}
.tp-date-card {
padding: 7px 6px; border-radius: 6px; border: 1px solid #e8e8e8;
text-align: center; cursor: pointer; background: #fff; transition: all 0.15s;
}
.tp-date-card.active { border-color: #1677ff; background: #e6f4ff; }
.tp-date-card .tp-date-week { font-size: 10px; color: #999; margin-bottom: 2px; }
.tp-date-card.active .tp-date-week { color: #1677ff; font-weight: 600; }
.tp-date-card .tp-date-day { font-size: 12px; color: #333; font-weight: 600; }
.tp-date-card.active .tp-date-day { color: #1677ff; }
.tp-time-col {
flex: 1; max-height: 220px; overflow-y: auto;
display: flex; flex-direction: column; gap: 2px;
border: 1px solid #e8e8e8; border-radius: 6px; padding: 4px 6px;
}
.tp-time-col::-webkit-scrollbar { display: none; }
.tp-time-item {
padding: 6px 10px; border-radius: 6px; font-size: 12px; color: #333;
cursor: pointer; text-align: center; transition: all 0.15s;
}
.tp-time-item.active { background: #1677ff; color: #fff; font-weight: 600; }
/* 地址卡片 */
.address-card, .pickup-card {
padding: 12px; border-radius: 8px; border: 1px solid #e8e8e8; background: #fafafa;
@@ -225,8 +251,6 @@
</div>
<!-- 净菜包订单:履约信息表单(预售,必填取货方式 + 期望时间) -->
<div class="fulfill-card hidden" id="fulfillBlock">
<div class="fulfill-title">📦 履约信息</div>
<div class="presale-tip">⏰ 净菜包为<b>预售商品</b>,当日 <b>15:00 截单</b>,次日按所选时段配送/自提。</div>
<!-- 取货方式 -->
<div class="form-group">
<div class="fg-label">取货方式 <span class="required">*</span></div>
@@ -243,20 +267,43 @@
</div>
</div>
</div>
<!-- 期望配送时间 -->
<!-- 期望配送时间:下拉框风格,左日期右时段 -->
<div class="form-group">
<div class="fg-label">期望配送时间 <span class="required">*</span></div>
<div class="chip-label">选择日期</div>
<div class="date-chips">
<div class="date-chip">今日(4月28日)</div>
<div class="date-chip active">明日(4月29日)</div>
<div class="date-chip">4月30日</div>
<div class="time-picker-trigger" id="timePickerTrigger">
<span class="tpt-text" id="tptText">4月30日 周五 11:00-12:00</span>
<span class="tpt-arrow" id="tptArrow"></span>
</div>
<div class="chip-label">选择时段</div>
<div class="time-chips">
<div class="time-chip">早餐 07:00-08:00</div>
<div class="time-chip active">午餐 11:00-12:00</div>
<div class="time-chip">晚餐 17:00-18:00</div>
<div class="time-picker-panel" id="timePickerPanel">
<div class="tp-date-col">
<div class="tp-date-card active" data-week="周五" data-day="4月30日">
<div class="tp-date-week">周五</div>
<div class="tp-date-day">4月30日</div>
</div>
<div class="tp-date-card" data-week="周六" data-day="5月1日">
<div class="tp-date-week">周六</div>
<div class="tp-date-day">5月1日</div>
</div>
<div class="tp-date-card" data-week="周日" data-day="5月2日">
<div class="tp-date-week">周日</div>
<div class="tp-date-day">5月2日</div>
</div>
</div>
<div class="tp-time-col">
<div class="tp-time-item">07:00-08:00</div>
<div class="tp-time-item">08:00-09:00</div>
<div class="tp-time-item">09:00-10:00</div>
<div class="tp-time-item">10:00-11:00</div>
<div class="tp-time-item active">11:00-12:00</div>
<div class="tp-time-item">12:00-13:00</div>
<div class="tp-time-item">13:00-14:00</div>
<div class="tp-time-item">14:00-15:00</div>
<div class="tp-time-item">15:00-16:00</div>
<div class="tp-time-item">16:00-17:00</div>
<div class="tp-time-item">17:00-18:00</div>
<div class="tp-time-item">18:00-19:00</div>
<div class="tp-time-item">19:00-20:00</div>
</div>
</div>
</div>
<!-- 配送地址(仅 delivery,支持选择/新增) -->
@@ -371,7 +418,8 @@
var orderAmount = document.getElementById('orderAmount');
var actionBtns = document.getElementById('actionBtns');
/* 取货方式切换:联动地址组/自提组显示 */
/* 取货方式切换:联动地址组/自提组 + 时间标签文本 */
var timeLabel = document.querySelectorAll('.fulfill-card .fg-label')[1];
document.querySelectorAll('.pickup-method').forEach(function(m) {
m.addEventListener('click', function() {
document.querySelectorAll('.pickup-method').forEach(function(x) { x.classList.remove('active'); });
@@ -379,22 +427,63 @@
var isDelivery = m.dataset.method === 'delivery';
document.getElementById('addressGroup').classList.toggle('hidden', !isDelivery);
document.getElementById('pickupGroup').classList.toggle('hidden', isDelivery);
/* 切换标签文本 */
if (isDelivery) {
timeLabel.innerHTML = '期望配送时间 <span class="required">*</span>';
} else {
timeLabel.innerHTML = '自提时间 <span class="required">*</span>';
}
});
});
/* 日期 chip 单选切换 */
document.querySelectorAll('.date-chip').forEach(function(c) {
c.addEventListener('click', function() {
document.querySelectorAll('.date-chip').forEach(function(x) { x.classList.remove('active'); });
/* 下拉面板展开/收起 */
var timePickerTrigger = document.getElementById('timePickerTrigger');
var timePickerPanel = document.getElementById('timePickerPanel');
var tptText = document.getElementById('tptText');
var tptArrow = document.getElementById('tptArrow');
timePickerTrigger.addEventListener('click', function(e) {
e.stopPropagation();
var isOpen = timePickerPanel.classList.toggle('show');
if (isOpen) { tptArrow.classList.add('open'); }
else { tptArrow.classList.remove('open'); }
});
/* 点击面板外部关闭 */
document.addEventListener('click', function(e) {
if (!timePickerTrigger.contains(e.target) && !timePickerPanel.contains(e.target)) {
timePickerPanel.classList.remove('show');
tptArrow.classList.remove('open');
}
});
/* 更新触发区显示文本 */
function updateTriggerText() {
var activeDate = document.querySelector('.tp-date-card.active');
var activeTime = document.querySelector('.tp-time-item.active');
var week = activeDate ? activeDate.dataset.week : '';
var day = activeDate ? activeDate.dataset.day : '';
var time = activeTime ? activeTime.textContent : '';
tptText.textContent = day + ' ' + week + ' ' + time;
}
/* 日期卡片单选切换 */
document.querySelectorAll('.tp-date-card').forEach(function(c) {
c.addEventListener('click', function(e) {
e.stopPropagation();
document.querySelectorAll('.tp-date-card').forEach(function(x) { x.classList.remove('active'); });
c.classList.add('active');
updateTriggerText();
});
});
/* 时段 chip 单选切换 */
document.querySelectorAll('.time-chip').forEach(function(c) {
c.addEventListener('click', function() {
document.querySelectorAll('.time-chip').forEach(function(x) { x.classList.remove('active'); });
/* 时段列表项单选切换 */
document.querySelectorAll('.tp-time-item').forEach(function(c) {
c.addEventListener('click', function(e) {
e.stopPropagation();
document.querySelectorAll('.tp-time-item').forEach(function(x) { x.classList.remove('active'); });
c.classList.add('active');
updateTriggerText();
});
});
@@ -473,7 +562,7 @@
/* 净菜包订单:改为"确认订单"页,隐藏取餐码,展示履约表单 */
successIcon.textContent = '📝';
successTitle.textContent = '确认订单信息';
successDesc.textContent = '请补充履约信息后提交订单';
successDesc.textContent = '请先选择配送方式后再提交订单';
pickupBlock.classList.add('hidden');
fulfillBlock.classList.remove('hidden');
orderItems.innerHTML =
@@ -68,9 +68,15 @@
.menu-item .food-icon {
width: 56px; height: 56px; border-radius: 10px; background: #f5f5f5;
display: flex; align-items: center; justify-content: center; font-size: 28px; flex-shrink: 0;
cursor: pointer;
}
.menu-item .food-info { flex: 1; min-width: 0; }
.menu-item .food-name { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.menu-item .food-name {
font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
cursor: pointer;
}
.menu-item .food-name:hover { color: #1677ff; }
.menu-item .food-desc { cursor: pointer; }
.food-tag {
font-size: 10px; font-weight: 600; padding: 1px 5px; border-radius: 3px;
white-space: nowrap; line-height: 16px;
@@ -236,7 +242,7 @@
<div class="menu-item" data-price="22">
<div class="food-icon">🍗</div>
<div class="food-info">
<div class="food-name">宫保鸡丁净菜包</div>
<div class="food-name">宫保鸡丁</div>
<div class="food-desc">腌好鸡丁+配料包 · 约300g · 285kcal</div>
<div class="food-bottom">
<span class="food-price">¥22</span>
@@ -248,7 +254,7 @@
<div class="menu-item" data-price="10">
<div class="food-icon">🍅</div>
<div class="food-info">
<div class="food-name">番茄炒蛋净菜包</div>
<div class="food-name">番茄炒蛋</div>
<div class="food-desc">切好番茄+调好蛋液 · 约250g · 168kcal</div>
<div class="food-bottom">
<span class="food-price">¥10</span>
@@ -260,7 +266,7 @@
<div class="menu-item" data-price="26">
<div class="food-icon">🍖</div>
<div class="food-info">
<div class="food-name">红烧肉净菜包</div>
<div class="food-name">红烧肉</div>
<div class="food-desc">炖好五花肉+酱料包 · 约350g · 395kcal</div>
<div class="food-bottom">
<span class="food-price">¥26</span>
@@ -272,7 +278,7 @@
<div class="menu-item" data-price="28">
<div class="food-icon">🐟</div>
<div class="food-info">
<div class="food-name">清蒸鱼净菜包</div>
<div class="food-name">清蒸鱼</div>
<div class="food-desc">处理净草鱼+葱姜丝 · 约220g · 142kcal</div>
<div class="food-bottom">
<span class="food-price">¥28</span>
@@ -284,7 +290,7 @@
<div class="menu-item" data-price="35">
<div class="food-icon">🥗</div>
<div class="food-info">
<div class="food-name">低脂控糖套餐净菜包</div>
<div class="food-name">低脂控糖套餐</div>
<div class="food-desc">鸡胸+糙米饭+西兰花 · 约400g · 320kcal</div>
<div class="food-bottom">
<span class="food-price">¥35</span>
@@ -296,7 +302,7 @@
<div class="menu-item" data-price="68">
<div class="food-icon">🍱</div>
<div class="food-info">
<div class="food-name">2-3人家庭套餐净菜包</div>
<div class="food-name">2-3人家庭套餐</div>
<div class="food-desc">3菜1汤半成品 · 约1.2kg · 1200kcal</div>
<div class="food-bottom">
<span class="food-price">¥68</span>
@@ -420,6 +426,27 @@
/* 初始化:购物车归零 */
updateCart();
/* 菜品点击查看详情:餐品和净菜包统一跳转 eat-dish-detail.html */
var detailTarget = { 'panel-meal': 'eat-dish-detail.html', 'panel-clean': 'eat-dish-detail.html' };
document.querySelectorAll('.menu-item').forEach(function(item) {
/* 根据 menu-item 所在面板解析目标页 */
var panel = item.closest('.order-layout');
var target = panel && detailTarget[panel.id] ? detailTarget[panel.id] : 'eat-dish-detail.html';
var triggerHandler = function(e) {
/* 避免点到加减按钮区域 */
if (e.target.closest('.food-bottom')) return;
location.href = target;
};
/* 图标、名称、描述可点击跳转,加减按钮不受影响 */
var icon = item.querySelector('.food-icon');
var name = item.querySelector('.food-name');
var desc = item.querySelector('.food-desc');
[icon, name, desc].forEach(function(el) {
if (el) el.addEventListener('click', triggerHandler);
});
});
</script>
</body>
</html>
@@ -0,0 +1,426 @@
<!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; }
.page-content { flex: 1; overflow-y: auto; background: #f5f5f5; padding-bottom: 16px; }
.page-content::-webkit-scrollbar { display: none; }
/* 地址卡片 */
.addr-card {
margin: 12px 16px 0; background: #fff; border-radius: 12px; padding: 14px;
position: relative; box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.addr-card .ac-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.addr-card .ac-name { font-size: 15px; font-weight: 600; color: #333; }
.addr-card .ac-phone { font-size: 13px; color: #666; }
.addr-card .ac-default {
font-size: 10px; padding: 1px 6px; border-radius: 8px; background: #fff7e6; color: #fa8c16; font-weight: 600;
}
.addr-card .ac-detail { font-size: 13px; color: #555; line-height: 1.6; padding-right: 70px; }
.addr-card .ac-actions {
position: absolute; right: 12px; bottom: 12px; display: flex; gap: 6px;
}
.addr-card .ac-btn {
font-size: 12px; padding: 3px 10px; border-radius: 12px; border: none; cursor: pointer;
background: #f5f5f5; color: #666;
}
.addr-card .ac-btn.primary { background: #e6f4ff; color: #1677ff; }
/* 编辑表单 */
.addr-form {
margin: 10px 0 0; padding: 12px; border-radius: 8px; background: #fafafa; border: 1px solid #e8e8e8;
display: none;
}
.addr-form.show { display: block; }
.addr-form .form-row { padding: 8px 0; border-bottom: 0.5px solid #f0f0f0; }
.addr-form .form-row:last-of-type { border-bottom: none; }
.addr-form .form-label { font-size: 12px; color: #666; margin-bottom: 6px; }
.addr-form .form-label .required { color: #f5222d; }
.addr-form .form-input {
width: 100%; height: 34px; border-radius: 6px; border: 1px solid #e8e8e8;
padding: 0 10px; font-size: 13px; background: #fff; outline: none;
}
.addr-form .form-input:focus { border-color: #1677ff; }
.addr-form .form-textarea {
width: 100%; min-height: 56px; border-radius: 6px; border: 1px solid #e8e8e8;
padding: 8px 10px; font-size: 13px; background: #fff; outline: none; resize: none; font-family: inherit;
}
.addr-form .form-textarea:focus { border-color: #1677ff; }
.addr-form .form-switch {
display: flex; align-items: center; justify-content: space-between; font-size: 13px; color: #333;
}
.switch-toggle {
width: 36px; height: 20px; border-radius: 10px; background: #d9d9d9; position: relative;
cursor: pointer; transition: background 0.2s; border: none;
}
.switch-toggle::after {
content: ''; position: absolute; left: 2px; top: 2px; width: 16px; height: 16px;
border-radius: 50%; background: #fff; transition: left 0.2s;
}
.switch-toggle.on { background: #1677ff; }
.switch-toggle.on::after { left: 18px; }
.addr-form-actions { display: flex; gap: 8px; margin-top: 12px; }
.addr-form-actions .btn {
flex: 1; height: 36px; border-radius: 18px; border: none; font-size: 13px;
font-weight: 600; cursor: pointer;
}
.addr-form-actions .btn-cancel { background: #f5f5f5; color: #666; }
.addr-form-actions .btn-save { background: #1677ff; color: #fff; }
/* 新增按钮 */
.add-btn-wrap { margin: 16px 16px 0; }
.add-addr-btn {
width: 100%; padding: 14px 0; border-radius: 12px; border: 1.5px dashed #1677ff;
background: #fff; color: #1677ff; font-size: 14px; font-weight: 600; cursor: pointer;
display: flex; align-items: center; justify-content: center; gap: 4px;
}
/* 空状态 */
.empty-state { text-align: center; padding: 60px 20px; color: #ccc; }
.empty-state .empty-icon { font-size: 48px; margin-bottom: 12px; }
.empty-state .empty-text { font-size: 14px; }
.hidden { display: none !important; }
</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="me-home.html"></a>
<span class="title">收货地址</span>
</div>
<div class="page-content" id="pageContent">
<!-- 地址列表 -->
<div class="addr-card" data-id="1">
<div class="ac-top">
<span class="ac-name">张三</span>
<span class="ac-phone">138****6688</span>
<span class="ac-default">默认</span>
</div>
<div class="ac-detail">渝北区龙湖路123号4栋1单元1801</div>
<div class="ac-actions">
<button class="ac-btn primary" data-action="edit">编辑</button>
<button class="ac-btn" data-action="delete">删除</button>
</div>
<!-- 编辑表单 -->
<div class="addr-form">
<div class="form-row">
<div class="form-label">收货人 <span class="required">*</span></div>
<input class="form-input" value="张三" placeholder="请输入收货人姓名">
</div>
<div class="form-row">
<div class="form-label">手机号 <span class="required">*</span></div>
<input class="form-input" value="138****6688" placeholder="请输入11位手机号">
</div>
<div class="form-row">
<div class="form-label">所在地区 <span class="required">*</span></div>
<input class="form-input" value="重庆市 渝北区" placeholder="省 / 市 / 区" readonly>
</div>
<div class="form-row">
<div class="form-label">详细地址 <span class="required">*</span></div>
<textarea class="form-textarea" placeholder="街道、楼牌号等">渝北区龙湖路123号4栋1单元1801</textarea>
</div>
<div class="form-row form-switch">
<span>设为默认收货地址</span>
<button class="switch-toggle on"></button>
</div>
<div class="addr-form-actions">
<button class="btn btn-cancel" data-action="cancel">取消</button>
<button class="btn btn-save" data-action="save">保存</button>
</div>
</div>
</div>
<div class="addr-card" data-id="2">
<div class="ac-top">
<span class="ac-name">张三</span>
<span class="ac-phone">138****6688</span>
</div>
<div class="ac-detail">南岸区江南大道28号写字楼12层</div>
<div class="ac-actions">
<button class="ac-btn primary" data-action="edit">编辑</button>
<button class="ac-btn" data-action="delete">删除</button>
</div>
<div class="addr-form">
<div class="form-row">
<div class="form-label">收货人 <span class="required">*</span></div>
<input class="form-input" value="张三" placeholder="请输入收货人姓名">
</div>
<div class="form-row">
<div class="form-label">手机号 <span class="required">*</span></div>
<input class="form-input" value="138****6688" placeholder="请输入11位手机号">
</div>
<div class="form-row">
<div class="form-label">所在地区 <span class="required">*</span></div>
<input class="form-input" value="重庆市 南岸区" placeholder="省 / 市 / 区" readonly>
</div>
<div class="form-row">
<div class="form-label">详细地址 <span class="required">*</span></div>
<textarea class="form-textarea" placeholder="街道、楼牌号等">南岸区江南大道28号写字楼12层</textarea>
</div>
<div class="form-row form-switch">
<span>设为默认收货地址</span>
<button class="switch-toggle"></button>
</div>
<div class="addr-form-actions">
<button class="btn btn-cancel" data-action="cancel">取消</button>
<button class="btn btn-save" data-action="save">保存</button>
</div>
</div>
</div>
<div class="addr-card" data-id="3">
<div class="ac-top">
<span class="ac-name">李四</span>
<span class="ac-phone">139****1234</span>
</div>
<div class="ac-detail">渝北区黄山大道中段55号2栋802</div>
<div class="ac-actions">
<button class="ac-btn primary" data-action="edit">编辑</button>
<button class="ac-btn" data-action="delete">删除</button>
</div>
<div class="addr-form">
<div class="form-row">
<div class="form-label">收货人 <span class="required">*</span></div>
<input class="form-input" value="李四" placeholder="请输入收货人姓名">
</div>
<div class="form-row">
<div class="form-label">手机号 <span class="required">*</span></div>
<input class="form-input" value="139****1234" placeholder="请输入11位手机号">
</div>
<div class="form-row">
<div class="form-label">所在地区 <span class="required">*</span></div>
<input class="form-input" value="重庆市 渝北区" placeholder="省 / 市 / 区" readonly>
</div>
<div class="form-row">
<div class="form-label">详细地址 <span class="required">*</span></div>
<textarea class="form-textarea" placeholder="街道、楼牌号等">渝北区黄山大道中段55号2栋802</textarea>
</div>
<div class="form-row form-switch">
<span>设为默认收货地址</span>
<button class="switch-toggle"></button>
</div>
<div class="addr-form-actions">
<button class="btn btn-cancel" data-action="cancel">取消</button>
<button class="btn btn-save" data-action="save">保存</button>
</div>
</div>
</div>
<!-- 新增按钮 -->
<div class="add-btn-wrap" id="addBtnWrap">
<button class="add-addr-btn" id="showAddBtn"> 新增收货地址</button>
</div>
<!-- 新增地址表单 -->
<div class="addr-card hidden" id="addCard">
<div class="ac-top">
<span class="ac-name" style="color:#999;">新地址</span>
</div>
<div class="addr-form show" id="addForm">
<div class="form-row">
<div class="form-label">收货人 <span class="required">*</span></div>
<input class="form-input" placeholder="请输入收货人姓名">
</div>
<div class="form-row">
<div class="form-label">手机号 <span class="required">*</span></div>
<input class="form-input" placeholder="请输入11位手机号">
</div>
<div class="form-row">
<div class="form-label">所在地区 <span class="required">*</span></div>
<input class="form-input" placeholder="省 / 市 / 区" readonly>
</div>
<div class="form-row">
<div class="form-label">详细地址 <span class="required">*</span></div>
<textarea class="form-textarea" placeholder="街道、楼牌号等"></textarea>
</div>
<div class="form-row form-switch">
<span>设为默认收货地址</span>
<button class="switch-toggle" id="addDefaultToggle"></button>
</div>
<div class="addr-form-actions">
<button class="btn btn-cancel" id="addCancelBtn">取消</button>
<button class="btn btn-save" id="addSaveBtn">保存</button>
</div>
</div>
</div>
</div>
<div class="home-indicator"></div>
</div>
<script>
/* 编辑/删除按钮事件委托 */
document.getElementById('pageContent').addEventListener('click', function(e) {
var actionBtn = e.target.closest('[data-action]');
if (!actionBtn) {
/* 点击开关切换 */
var toggle = e.target.closest('.switch-toggle');
if (toggle) { toggle.classList.toggle('on'); }
return;
}
var card = actionBtn.closest('.addr-card');
var action = actionBtn.dataset.action;
if (action === 'edit') {
/* 展开/收起编辑表单 */
var form = card.querySelector('.addr-form');
var isOpen = form.classList.contains('show');
/* 关闭所有其他表单 */
document.querySelectorAll('.addr-form.show').forEach(function(f) { f.classList.remove('show'); });
if (!isOpen) { form.classList.add('show'); }
} else if (action === 'delete') {
/* 原型演示:直接移除卡片 */
if (confirm('确定要删除该收货地址吗?')) {
card.remove();
checkEmpty();
}
} else if (action === 'cancel') {
/* 取消编辑:关闭表单 */
card.querySelector('.addr-form').classList.remove('show');
} else if (action === 'save') {
/* 保存编辑:同步表单数据到卡片并关闭 */
var form = card.querySelector('.addr-form');
var inputs = form.querySelectorAll('.form-input, .form-textarea');
var name = inputs[0].value || '未命名';
var phone = inputs[1].value || '';
var area = inputs[2].value || '';
var detail = inputs[3].value || '';
var isDefault = form.querySelector('.switch-toggle').classList.contains('on');
/* 更新卡片显示 */
card.querySelector('.ac-name').textContent = name;
card.querySelector('.ac-phone').textContent = phone;
card.querySelector('.ac-detail').textContent = area ? area + ' ' + detail : detail;
/* 默认地址标识 */
var defaultTag = card.querySelector('.ac-default');
if (isDefault) {
/* 其他卡片移除默认 */
document.querySelectorAll('.ac-default').forEach(function(t) { t.style.display = 'none'; });
if (defaultTag) { defaultTag.style.display = ''; }
else {
var tag = document.createElement('span');
tag.className = 'ac-default';
tag.textContent = '默认';
card.querySelector('.ac-top').appendChild(tag);
}
} else {
if (defaultTag) defaultTag.style.display = 'none';
}
form.classList.remove('show');
}
});
/* 显示新增表单 */
document.getElementById('showAddBtn').addEventListener('click', function() {
document.getElementById('addCard').classList.remove('hidden');
document.getElementById('addBtnWrap').classList.add('hidden');
});
/* 新增表单默认开关 */
document.getElementById('addDefaultToggle').addEventListener('click', function() {
this.classList.toggle('on');
});
/* 取消新增 */
document.getElementById('addCancelBtn').addEventListener('click', function() {
document.getElementById('addCard').classList.add('hidden');
document.getElementById('addBtnWrap').classList.remove('hidden');
/* 清空表单 */
var form = document.getElementById('addForm');
form.querySelectorAll('.form-input, .form-textarea').forEach(function(el) { el.value = ''; });
form.querySelector('.switch-toggle').classList.remove('on');
});
/* 保存新增 */
document.getElementById('addSaveBtn').addEventListener('click', function() {
var form = document.getElementById('addForm');
var inputs = form.querySelectorAll('.form-input, .form-textarea');
var name = inputs[0].value || '新地址';
var phone = inputs[1].value || '';
var area = inputs[2].value || '';
var detail = inputs[3].value || '';
var isDefault = form.querySelector('.switch-toggle').classList.contains('on');
/* 生成新卡片 */
var html = '<div class="addr-card" data-id="' + Date.now() + '">'
+ '<div class="ac-top">'
+ '<span class="ac-name">' + name + '</span>'
+ '<span class="ac-phone">' + phone + '</span>'
+ (isDefault ? '<span class="ac-default">默认</span>' : '')
+ '</div>'
+ '<div class="ac-detail">' + (area ? area + ' ' : '') + detail + '</div>'
+ '<div class="ac-actions">'
+ '<button class="ac-btn primary" data-action="edit">编辑</button>'
+ '<button class="ac-btn" data-action="delete">删除</button>'
+ '</div>'
+ '<div class="addr-form">'
+ '<div class="form-row"><div class="form-label">收货人 <span class="required">*</span></div><input class="form-input" value="' + name + '" placeholder="请输入收货人姓名"></div>'
+ '<div class="form-row"><div class="form-label">手机号 <span class="required">*</span></div><input class="form-input" value="' + phone + '" placeholder="请输入11位手机号"></div>'
+ '<div class="form-row"><div class="form-label">所在地区 <span class="required">*</span></div><input class="form-input" value="' + area + '" placeholder="省 / 市 / 区" readonly></div>'
+ '<div class="form-row"><div class="form-label">详细地址 <span class="required">*</span></div><textarea class="form-textarea" placeholder="街道、楼牌号等">' + detail + '</textarea></div>'
+ '<div class="form-row form-switch"><span>设为默认收货地址</span><button class="switch-toggle' + (isDefault ? ' on' : '') + '"></button></div>'
+ '<div class="addr-form-actions"><button class="btn btn-cancel" data-action="cancel">取消</button><button class="btn btn-save" data-action="save">保存</button></div>'
+ '</div>'
+ '</div>';
/* 如果设为默认,移除其他地址的默认标记 */
if (isDefault) {
document.querySelectorAll('.ac-default').forEach(function(t) { t.style.display = 'none'; });
}
/* 插入到新增按钮之前 */
document.getElementById('addBtnWrap').insertAdjacentHTML('beforebegin', html);
/* 隐藏新增表单并重置 */
document.getElementById('addCard').classList.add('hidden');
document.getElementById('addBtnWrap').classList.remove('hidden');
inputs.forEach(function(el) { el.value = ''; });
form.querySelector('.switch-toggle').classList.remove('on');
checkEmpty();
});
/* 检查地址列表是否为空 */
function checkEmpty() {
var cards = document.querySelectorAll('.addr-card:not(#addCard)');
if (cards.length === 0) {
var emptyHtml = '<div class="empty-state" id="emptyHint"><div class="empty-icon">📭</div><div class="empty-text">暂无收货地址</div></div>';
document.getElementById('addBtnWrap').insertAdjacentHTML('beforebegin', emptyHtml);
} else {
var hint = document.getElementById('emptyHint');
if (hint) hint.remove();
}
}
</script>
</body>
</html>
@@ -381,17 +381,6 @@
</div>
</div>
<!-- 9. 营养目标 保留 -->
<div class="section" id="goalSection" onclick="openGoalModal()" style="cursor:pointer;">
<div class="sec-header">
<span class="sec-title"><span class="sec-icon">🎯</span>营养目标</span>
<span class="sec-action">编辑 </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>
@@ -585,34 +574,6 @@
</div>
</div>
<!-- ========== 营养目标编辑弹窗 ========== -->
<div class="modal-mask" id="goalModal">
<div class="modal-panel" onclick="event.stopPropagation()">
<div class="modal-header">
<span class="m-cancel" onclick="closeModal('goalModal')">取消</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>
/* ========== 通用弹窗控制 ========== */
@@ -764,21 +725,6 @@
alert('已保存(原型演示)');
}
/* ========== 营养目标 ========== */
function openGoalModal() { document.getElementById('goalModal').classList.add('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';
closeModal('goalModal');
alert('已保存(原型演示)');
}
/* ========== 通用回写:将编辑态标签同步到页面展示区 ========== */
// viewId: 展示区容器id; groupId: 编辑态分组id; cls: 展示态span的class; asText: true则拼成文本而非标签
function syncGroupBack(viewId, groupId, cls, asText) {
@@ -802,7 +748,7 @@
}
/* ========== 点击遮罩层空白处关闭弹窗 ========== */
['bodyModal','diseaseModal','tasteModal','tabooModal','dishModal','momModal','goalModal'].forEach(function(id) {
['bodyModal','diseaseModal','tasteModal','tabooModal','dishModal','momModal'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.addEventListener('click', function() { closeModal(id); });
});
@@ -133,6 +133,7 @@
<div class="menu-group">
<a class="menu-item" href="me-health.html"><span class="m-icon">💚</span><span class="m-label">健康档案</span><span class="m-arrow"></span></a>
<a class="menu-item" href="me-orders.html"><span class="m-icon">📋</span><span class="m-label">我的订单</span><span class="m-badge">2</span><span class="m-arrow"></span></a>
<a class="menu-item" href="me-address.html"><span class="m-icon">📍</span><span class="m-label">收货地址</span><span class="m-arrow"></span></a>
<a class="menu-item" href="me-favorites.html"><span class="m-icon"></span><span class="m-label">收藏食谱</span><span class="m-arrow"></span></a>
<a class="menu-item" href="me-wallet.html"><span class="m-icon">💰</span><span class="m-label">我的余额</span><span class="m-arrow"></span></a>
<a class="menu-item" href="me-vip.html"><span class="m-icon">👑</span><span class="m-label">会员中心</span><span class="m-arrow"></span></a>
@@ -56,6 +56,7 @@
.ing-name { font-size: 12px; font-weight: 500; color: #444; }
.ing-code { font-size: 9px; color: #bbb; font-family: monospace; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ing-ok { font-size: 10px; padding: 1px 5px; border-radius: 5px; background: #f6ffed; color: #52c41a; flex-shrink: 0; white-space: nowrap; }
.ing-role { font-size: 10px; padding: 1px 5px; border-radius: 4px; flex-shrink: 0; white-space: nowrap; background: #f5f5f5; color: #999; }
.ing-trace { padding: 3px 8px; border-radius: 8px; border: 1px solid #1677ff; background: #fff; color: #1677ff; font-size: 10px; cursor: pointer; flex-shrink: 0; white-space: nowrap; text-decoration: none; }
/* 营养建议 */
.advice-card { background: #fff; border-radius: 12px; padding: 12px 14px; margin: 0 16px 24px; }
@@ -124,16 +125,11 @@
<div class="oh-stat"><div class="sv" style="color:#1677ff">3</div><div class="sl">菜品数</div></div>
<div class="oh-stat"><div class="sv" style="color:#555">442</div><div class="sl">总热量kcal</div></div>
</div>
<div class="oh-tags">
<span class="oh-tag" style="background:#e6f4ff;color:#1677ff">点餐取餐</span>
<span class="oh-tag" style="background:#f5f5f5;color:#666">A区中餐线</span>
<span class="oh-tag" style="background:#f5f5f5;color:#666">余额支付</span>
</div>
</div>
<!-- 净菜包专属:履约信息 + 状态时间线(默认隐藏) -->
<!-- 净菜包专属:配送信息 + 状态时间线(默认隐藏) -->
<div class="fulfill-section hidden" id="cleanExtra">
<div class="sec-card">
<div class="sec-title">📦 履约信息</div>
<div class="sec-title">📦 配送信息</div>
<div class="info-row">
<span class="ir-label">取货方式</span>
<span class="ir-value">🚚 配送到家</span>
@@ -293,7 +289,22 @@
}
var type = getQuery('type') === 'clean' ? 'clean' : 'meal';
/* 净菜包订单状态:delivering=待配送 / shipped=已发货 / done=已完成;缺省按待配送处理 */
var rawStatus = getQuery('status');
var status = ['delivering', 'shipped', 'done'].indexOf(rawStatus) >= 0 ? rawStatus : 'delivering';
/* 是否展示食材来源与溯源按钮:仅 已发货 / 已完成 可见 */
var showTrace = status === 'shipped' || status === 'done';
if (type === 'clean') {
/* 状态徽标:文案与配色随 status 切换 */
var badgeMap = {
delivering: { text: '🚚 待配送', bg: '#e6f4ff', color: '#1677ff' },
shipped: { text: '🚚 配送中', bg: '#fff7e6', color: '#fa8c16' },
done: { text: '✅ 已完成', bg: '#f6ffed', color: '#52c41a' }
};
var b = badgeMap[status];
/* 替换订单头部:净菜包订单基本信息 */
document.getElementById('orderHeader').innerHTML =
'<div class="oh-top">'
@@ -301,26 +312,62 @@
+ '<div class="oh-date">2026-04-29 午餐时段配送</div>'
+ '<div class="oh-id">CL-20260428-017</div>'
+ '</div>'
+ '<span class="oh-badge" style="background:#e6f4ff;color:#1677ff;">🚚 待配送</span>'
+ '<span class="oh-badge" style="background:' + b.bg + ';color:' + b.color + ';">' + b.text + '</span>'
+ '</div>'
+ '<div class="oh-stats">'
+ '<div class="oh-stat"><div class="sv" style="color:#fa8c16">890</div><div class="sl">总热量kcal</div></div>'
+ '<div class="oh-stat"><div class="sv" style="color:#f5222d">¥79</div><div class="sl">消费金额</div></div>'
+ '<div class="oh-stat"><div class="sv" style="color:#1677ff">2</div><div class="sl">净菜包种类</div></div>'
+ '<div class="oh-stat"><div class="sv" style="color:#722ed1">1.0kg</div><div class="sl">总净重</div></div>'
+ '</div>'
+ '<div class="oh-tags">'
+ '<span class="oh-tag" style="background:#fff7e6;color:#fa8c16">预售订单</span>'
+ '<span class="oh-tag" style="background:#e6f4ff;color:#1677ff">配送到家</span>'
+ '<span class="oh-tag" style="background:#f5f5f5;color:#666">余额支付</span>'
+ '</div>';
/* 显示履约信息 + 订单进度时间线 */
/* 显示配送信息 + 订单进度时间线 */
document.getElementById('cleanExtra').classList.remove('hidden');
/* 替换菜品明细:餐品 → 净菜包明细 */
/* 替换菜品明细:餐品 → 净菜包明细;待配送状态下不展示食材来源/溯源 */
var dishTitle = showTrace ? '净菜包明细 · 食材 · 溯源' : '净菜包明细';
/* 宫保鸡丁净菜包:食材明细(已发货/已完成 有溯源按钮,待配送无溯源按钮;均含克重) */
var gongBaoIngRows = [
{ name: '鸡胸肉', weight: '150g', code: 'NC-PROC-JBR-20260422-01', role: '主材' },
{ name: '花生米', weight: '30g', code: 'ST-EX-HS05-PT-HSMI-050K-20260420-001', role: '辅材' }
];
var gongBaoIng = showTrace
? '<div class="ing-section">'
+ '<div class="ing-label">食材来源</div>'
+ gongBaoIngRows.map(function(r) {
return '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">' + r.name + ' ' + r.weight + '</div><div class="ing-code">' + r.code + '</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>';
}).join('')
+ '</div>'
: '<div class="ing-section">'
+ '<div class="ing-label">食材明细</div>'
+ gongBaoIngRows.map(function(r) {
return '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">' + r.name + ' ' + r.weight + '</div></div><span class="ing-role">' + r.role + '</span></div>';
}).join('')
+ '</div>';
/* 低脂控糖套餐净菜包:食材明细(同上规则) */
var lowFatIngRows = [
{ name: '鸡胸肉', weight: '150g', code: 'NC-PROC-JBR-20260422-01', role: '主材' },
{ name: '西兰花', weight: '80g', code: 'ST-EX-LY01-VG-XLHU-080K-20260416-001', role: '辅材' },
{ name: '糙米', weight: '120g', code: 'ST-EX-LY03-DG-CAMI-500K-20260413-002', role: '主材' }
];
var lowFatIng = showTrace
? '<div class="ing-section">'
+ '<div class="ing-label">食材来源</div>'
+ lowFatIngRows.map(function(r) {
return '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">' + r.name + ' ' + r.weight + '</div><div class="ing-code">' + r.code + '</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>';
}).join('')
+ '</div>'
: '<div class="ing-section">'
+ '<div class="ing-label">食材明细</div>'
+ lowFatIngRows.map(function(r) {
return '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">' + r.name + ' ' + r.weight + '</div></div><span class="ing-role">' + r.role + '</span></div>';
}).join('')
+ '</div>';
document.getElementById('dishSection').innerHTML =
'<div class="dish-sec-title">净菜包明细 · 食材 · 溯源</div>'
'<div class="dish-sec-title">' + dishTitle + '</div>'
+ '<div class="dish-card">'
+ '<div class="dish-head">'
+ '<div class="dish-icon">🍗</div>'
@@ -333,11 +380,7 @@
+ '</div>'
+ '<span class="dish-price">¥44</span>'
+ '</div>'
+ '<div class="ing-section">'
+ '<div class="ing-label">食材来源</div>'
+ '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">鸡胸肉</div><div class="ing-code">NC-PROC-JBR-20260422-01</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>'
+ '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">花生米</div><div class="ing-code">ST-EX-HS05-PT-HSMI-050K-20260420-001</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>'
+ '</div>'
+ gongBaoIng
+ '</div>'
+ '<div class="dish-card">'
+ '<div class="dish-head">'
@@ -351,17 +394,51 @@
+ '</div>'
+ '<span class="dish-price">¥35</span>'
+ '</div>'
+ '<div class="ing-section">'
+ '<div class="ing-label">食材来源</div>'
+ '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">鸡胸肉</div><div class="ing-code">NC-PROC-JBR-20260422-01</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>'
+ '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">西兰花</div><div class="ing-code">ST-EX-LY01-VG-XLHU-080K-20260416-001</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>'
+ '<div class="ing-row"><div class="ing-dot"></div><div class="ing-info"><div class="ing-name">糙米</div><div class="ing-code">ST-EX-LY03-DG-CAMI-500K-20260413-002</div></div><span class="ing-ok">✅合格</span><a class="ing-trace" href="trace-detail.html">🔗溯源</a></div>'
+ '</div>'
+ lowFatIng
+ '</div>'
+ '<div class="advice-card" style="margin:0;">'
+ '<div class="advice-title">🍳 烹制提示</div>'
+ '<div class="advice-text" style="color:#1677ff;">宫保鸡丁净菜包:热锅冷油下鸡丁滑炒至变色,加入配料包翻炒 2 分钟即可出锅;糙米饭需另行准备。</div>'
+ '</div>';
/* 订单进度时间线:按状态高亮对应节点 */
renderCleanTimeline(status);
}
/* 渲染净菜包订单进度时间线 */
function renderCleanTimeline(status) {
var states = ['submitted', 'prepared', 'delivering', 'shipping', 'arrived'];
var activeIdx;
if (status === 'delivering') {
activeIdx = 2; /* 高亮:待配送 */
} else if (status === 'shipped') {
activeIdx = 3; /* 高亮:配送中 */
} else { /* done */
activeIdx = 4; /* 全部完成:已送达 */
}
var nodes = [
{ title: '订单提交', time: '2026-04-28 14:23' },
{ title: '备货完成', time: '2026-04-28 15:00 截单' },
{ title: '待配送', time: '预计 2026-04-29 11:00 开始配送' },
{ title: '配送中', time: status === 'shipped' ? '骑手已取货,预计 30 分钟内送达' : '-' },
{ title: '已送达', time: status === 'done' ? '2026-04-29 12:08 送达' : '-' }
];
var html = '';
for (var i = 0; i < nodes.length; i++) {
var cls = '';
if (i < activeIdx) cls = 'done';
else if (i === activeIdx) cls = i === nodes.length - 1 ? 'done' : 'active';
else if (status === 'done' && i === activeIdx) cls = 'done';
html += '<div class="tl-item ' + cls + '">'
+ '<div class="tl-dot"></div>'
+ '<div class="tl-content">'
+ '<div class="tl-title">' + nodes[i].title + '</div>'
+ '<div class="tl-time">' + nodes[i].time + '</div>'
+ '</div>'
+ '</div>';
}
var timelineWrap = document.querySelector('#cleanExtra .timeline');
if (timelineWrap) timelineWrap.innerHTML = html;
}
</script>
</body>
@@ -70,7 +70,7 @@
<div class="items">低脂鸡胸肉沙拉 ×1、杂粮饭 ×1、紫菜蛋花汤 ×1</div>
<div class="card-bottom"><span class="total">¥ 28.5</span><span class="time">今天 11:32</span></div>
</a>
<a class="order-card" href="me-order-detail.html?type=clean">
<a class="order-card" href="me-order-detail.html?type=clean&status=delivering">
<div class="card-top">
<div class="shop-row"><span class="shop">🥬 阳光食堂 · 净菜包订单</span><span class="type-tag clean">净菜包</span></div>
<span class="status shipping">待配送</span>
@@ -87,7 +87,7 @@
<div class="items">全麦三明治 ×1、鲜榨橙汁 ×1</div>
<div class="card-bottom"><span class="total">¥ 15.0</span><span class="time">今天 07:45</span></div>
</a>
<a class="order-card" href="me-order-detail.html?type=clean">
<a class="order-card" href="me-order-detail.html?type=clean&status=done">
<div class="card-top">
<div class="shop-row"><span class="shop">🥬 阳光食堂 · 净菜包订单</span><span class="type-tag clean">净菜包</span></div>
<span class="status">已完成</span>
+427 -39
View File
@@ -13,6 +13,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
--radius-sm:4px;--radius-md:8px;--radius-lg:12px;
--shadow-sm:0 1px 4px rgba(0,0,0,0.08);--shadow-md:0 4px 12px rgba(0,0,0,0.12);
}
/* ===== 顶部导航 ===== */
.top-bar{display:flex;align-items:center;height:56px;background:#001529;padding:0 24px;position:fixed;top:0;left:0;right:0;z-index:100;}
.top-bar__logo{display:flex;align-items:center;gap:8px;color:#fff;font-size:var(--font-lg);font-weight:600;white-space:nowrap;width:240px;flex-shrink:0;}
.top-bar__logo svg{width:28px;height:28px;}
@@ -24,6 +25,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.top-bar__right .btn-back{color:rgba(255,255,255,0.65);cursor:pointer;display:flex;align-items:center;gap:4px;text-decoration:none;}
.top-bar__right .btn-back:hover{color:#fff;}
.avatar{width:32px;height:32px;border-radius:50%;background:#1890FF;display:flex;align-items:center;justify-content:center;color:#fff;font-size:var(--font-sm);cursor:pointer;}
/* ===== 侧边栏 ===== */
.sidebar{position:fixed;top:56px;left:0;bottom:0;width:240px;background:#001529;overflow-y:auto;z-index:90;padding-top:8px;}
.sidebar-menu__title{color:rgba(255,255,255,0.35);font-size:var(--font-xs);padding:16px 24px 8px;text-transform:uppercase;letter-spacing:.5px;}
.sidebar-item{display:flex;align-items:center;gap:8px;padding:10px 24px;color:rgba(255,255,255,0.65);font-size:var(--font-sm);cursor:pointer;transition:all .15s;text-decoration:none;}
@@ -36,15 +38,21 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.sidebar-subgroup.open .sidebar-subgroup__header .arrow{transform:rotate(90deg);}
.sidebar-subgroup__body{display:none;padding-left:12px;}
.sidebar-subgroup.open .sidebar-subgroup__body{display:block;}
/* ===== 主内容 ===== */
.main{margin-left:240px;margin-top:56px;padding:24px;min-height:calc(100vh - 56px);}
.breadcrumb{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);color:var(--text-secondary);margin-bottom:16px;}
.breadcrumb a{color:var(--primary);text-decoration:none;}.breadcrumb a:hover{text-decoration:underline;}
.breadcrumb__sep{color:var(--text-placeholder);}
/* ===== 表单卡片 ===== */
.form-card{background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);padding:24px;margin-bottom:16px;}
.form-card__title{font-size:var(--font-base);font-weight:600;margin-bottom:20px;padding-bottom:12px;border-bottom:1px solid var(--border);}
.form-card__title{font-size:var(--font-base);font-weight:600;margin-bottom:20px;padding-bottom:12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;}
.form-card__title .card-icon{font-size:18px;}
.form-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px 24px;}
.form-grid.col3{grid-template-columns:repeat(3,1fr);}
.form-grid.col4{grid-template-columns:repeat(4,1fr);}
.form-group{display:flex;flex-direction:column;gap:6px;}
.form-group.full{grid-column:1/-1;}
.form-group.span2{grid-column:span 2;}
.form-label{font-size:var(--font-sm);color:var(--text-secondary);}
.form-label .required{color:var(--danger);margin-left:2px;}
.form-input,.form-select{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;width:100%;}
@@ -52,16 +60,22 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.form-input:disabled{background:#F5F5F5;color:var(--text-placeholder);cursor:not-allowed;}
.form-select{appearance:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;padding-right:28px;}
.form-hint{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:2px;}
.form-textarea{min-height:80px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;font-size:var(--font-sm);color:var(--text-primary);outline:none;background:#fff;width:100%;resize:vertical;font-family:inherit;}
.form-textarea:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.1);}
/* ===== 按钮 ===== */
.btn{height:36px;padding:0 16px;border-radius:var(--radius-sm);font-size:var(--font-sm);cursor:pointer;border:none;transition:all .2s;display:inline-flex;align-items:center;gap:6px;text-decoration:none;}
.btn-primary{background:var(--primary);color:#fff;}.btn-primary:hover{background:#40a9ff;}
.btn-default{background:#fff;color:var(--text-primary);border:1px solid var(--border);}.btn-default:hover{color:var(--primary);border-color:var(--primary);}
.btn-sm{height:28px;padding:0 10px;font-size:var(--font-xs);}
.btn-text{background:none;border:none;color:var(--primary);cursor:pointer;font-size:var(--font-sm);padding:0;}.btn-text:hover{color:#40a9ff;}
.btn-text--danger{color:var(--danger);}.btn-text--danger:hover{color:#ff7875;}
.form-footer{display:flex;align-items:center;gap:12px;padding:16px 24px;background:#fff;border-radius:var(--radius-md);box-shadow:var(--shadow-sm);}
/* 地图区域 */
.map-container{width:100%;height:320px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#E8ECF0;position:relative;overflow:hidden;}
.map-placeholder{width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-placeholder);gap:8px;background:linear-gradient(135deg,#e8ecf0 25%,#dde2e8 25%,#dde2e8 50%,#e8ecf0 50%,#e8ecf0 75%,#dde2e8 75%);background-size:20px 20px;}
.map-placeholder__icon{font-size:40px;opacity:0.5;}
.map-placeholder__text{font-size:var(--font-sm);}
/* 模拟地图内容 */
/* ===== 单选按钮组 ===== */
.radio-group{display:flex;gap:20px;height:36px;align-items:center;}
.radio-item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio-item input[type="radio"]{width:16px;height:16px;accent-color:var(--primary);}
/* ===== 地图区域 ===== */
.map-container{width:100%;height:280px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#E8ECF0;position:relative;overflow:hidden;}
.map-mock{width:100%;height:100%;position:relative;background:#e8edf2;}
.map-mock__grid{position:absolute;inset:0;background-image:linear-gradient(rgba(0,0,0,0.05) 1px,transparent 1px),linear-gradient(90deg,rgba(0,0,0,0.05) 1px,transparent 1px);background-size:40px 40px;}
.map-mock__roads{position:absolute;inset:0;}
@@ -76,14 +90,61 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.map-mock__search{position:absolute;left:12px;top:12px;display:flex;gap:6px;z-index:5;}
.map-mock__search input{width:220px;height:32px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-xs);background:#fff;box-shadow:var(--shadow-sm);}
.map-mock__search button{height:32px;padding:0 12px;background:var(--primary);color:#fff;border:none;border-radius:var(--radius-sm);font-size:var(--font-xs);cursor:pointer;}
/* 单选按钮组 */
.radio-group{display:flex;gap:20px;height:36px;align-items:center;}
.radio-item{display:flex;align-items:center;gap:6px;font-size:var(--font-sm);cursor:pointer;}
.radio-item input[type="radio"]{width:16px;height:16px;accent-color:var(--primary);}
/* ===== 营业时段 ===== */
.biz-hours-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.biz-hours-table th{text-align:left;padding:8px 10px;font-weight:500;color:var(--text-secondary);font-size:var(--font-xs);border-bottom:1px solid var(--border);white-space:nowrap;}
.biz-hours-table th .required{color:var(--danger);}
.biz-hours-table td{padding:8px 10px;border-bottom:1px solid #f5f5f5;vertical-align:middle;}
.biz-hours-table .col-meal{width:100px;}
.biz-hours-table .col-weekday{min-width:260px;}
.biz-hours-table .col-time{width:110px;}
.biz-hours-table .col-action{width:50px;text-align:center;}
.biz-hours-table .form-input,.biz-hours-table .form-select{height:30px;font-size:var(--font-xs);}
.biz-hours-table input[type="time"]{height:30px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 8px;font-size:var(--font-xs);color:var(--text-primary);outline:none;width:108px;}
.biz-hours-table input[type="time"]:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.1);}
/* 星期切换按钮 */
.weekday-toggles{display:flex;gap:4px;flex-wrap:wrap;}
.weekday-toggle{width:30px;height:28px;border-radius:var(--radius-sm);border:1px solid var(--border);background:#fff;font-size:var(--font-xs);color:var(--text-secondary);cursor:pointer;transition:all .15s;display:flex;align-items:center;justify-content:center;user-select:none;}
.weekday-toggle:hover{border-color:var(--primary);color:var(--primary);}
.weekday-toggle.active{background:var(--primary);color:#fff;border-color:var(--primary);}
.weekday-toggle-all{width:auto;padding:0 8px;font-size:var(--font-xs);color:var(--primary);cursor:pointer;user-select:none;height:28px;display:flex;align-items:center;}
.biz-hours-add{margin-top:12px;}
/* ===== 图片上传区 ===== */
.upload-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;}
.upload-zone{border:2px dashed var(--border);border-radius:var(--radius-md);padding:32px 16px;text-align:center;cursor:pointer;transition:all .2s;background:#FAFAFA;min-height:180px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;position:relative;overflow:hidden;}
.upload-zone:hover{border-color:var(--primary);background:var(--primary-light);}
.upload-zone.has-image{border-style:solid;border-color:var(--success);background:#F6FFED;padding:0;}
.upload-zone__icon{width:48px;height:48px;border-radius:50%;background:#E8E8E8;display:flex;align-items:center;justify-content:center;font-size:22px;color:var(--text-placeholder);}
.upload-zone:hover .upload-zone__icon{background:var(--primary-light);color:var(--primary);}
.upload-zone__text{font-size:var(--font-sm);color:var(--text-secondary);}
.upload-zone__hint{font-size:var(--font-xs);color:var(--text-placeholder);}
.upload-zone__label{position:absolute;top:0;left:0;background:var(--danger);color:#fff;font-size:10px;padding:3px 10px;border-radius:0 0 var(--radius-sm) 0;font-weight:500;}
.upload-zone__preview{width:100%;height:100%;object-fit:cover;display:block;}
.upload-zone__overlay{position:absolute;inset:0;background:rgba(0,0,0,0.45);display:flex;align-items:center;justify-content:center;gap:8px;opacity:0;transition:opacity .2s;}
.upload-zone.has-image:hover .upload-zone__overlay{opacity:1;}
.upload-zone__overlay .btn{background:#fff;color:var(--text-primary);height:28px;padding:0 12px;font-size:var(--font-xs);}
/* ===== 资质信息 ===== */
.qual-section{margin-bottom:20px;padding-bottom:20px;border-bottom:1px dashed var(--border);}
.qual-section:last-child{margin-bottom:0;padding-bottom:0;border-bottom:none;}
.qual-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:14px;display:flex;align-items:center;gap:6px;}
.qual-section__title .dot{width:6px;height:6px;border-radius:50%;background:var(--primary);}
.qual-section__title .tag-optional{font-size:var(--font-xs);color:var(--text-placeholder);font-weight:400;margin-left:4px;}
.qual-img-row{display:flex;gap:16px;margin-top:12px;}
.qual-img-item{flex:1;max-width:240px;}
.qual-img-item .upload-zone-small{border:2px dashed var(--border);border-radius:var(--radius-sm);min-height:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;cursor:pointer;background:#FAFAFA;transition:all .2s;}
.qual-img-item .upload-zone-small:hover{border-color:var(--primary);background:var(--primary-light);}
.qual-img-item .upload-zone-small__icon{font-size:28px;color:var(--text-placeholder);}
.qual-img-item .upload-zone-small__text{font-size:var(--font-xs);color:var(--text-placeholder);}
.qual-img-item .upload-zone-small__label{font-size:var(--font-xs);color:var(--text-secondary);margin-bottom:2px;}
/* ===== 日期范围 ===== */
.date-range{display:flex;align-items:center;gap:8px;}
.date-range input[type="date"]{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);color:var(--text-primary);outline:none;width:150px;}
.date-range input[type="date"]:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.1);}
.date-range__sep{color:var(--text-placeholder);font-size:var(--font-sm);}
</style>
</head>
<body>
<!-- 顶部导航 -->
<!-- ===== 顶部导航 ===== -->
<header class="top-bar">
<div class="top-bar__logo">
<svg viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="6" fill="#1890FF"/><path d="M7 14h14M14 7v14" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/></svg>
@@ -105,7 +166,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</div>
</header>
<!-- 侧边栏 -->
<!-- ===== 侧边栏 ===== -->
<aside class="sidebar">
<div class="sidebar-menu__title">营养用餐</div>
<a href="din-canteen.html" class="sidebar-item active"><span class="mi">🏢</span>场所管理</a>
@@ -145,18 +206,18 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<a href="din-innovation.html" class="sidebar-item"><span class="mi">🌟</span>味养创新</a>
</aside>
<!-- 主内容 -->
<!-- ===== 主内容 ===== -->
<main class="main">
<!-- 面包屑 -->
<div class="breadcrumb">
<a href="din-canteen.html">场所管理</a>
<span class="breadcrumb__sep">/</span>
<span>新增场所</span>
<span id="pageTitle">新增场所</span>
</div>
<!-- 基本信息表单 -->
<!-- 卡片1基本信息 -->
<div class="form-card">
<div class="form-card__title">基本信息</div>
<div class="form-card__title"><span class="card-icon">📋</span>基本信息</div>
<div class="form-grid">
<div class="form-group">
<label class="form-label">场所名称<span class="required">*</span></label>
@@ -165,7 +226,6 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<div class="form-group">
<label class="form-label">场所编号</label>
<input class="form-input" value="系统自动生成" disabled>
</div>
<div class="form-group">
<label class="form-label">场所类型<span class="required">*</span></label>
@@ -188,11 +248,11 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</select>
</div>
<div class="form-group">
<label class="form-label">负责人</label>
<label class="form-label">负责人<span class="required">*</span></label>
<input class="form-input" placeholder="请输入负责人姓名">
</div>
<div class="form-group">
<label class="form-label">负责人电话</label>
<label class="form-label">负责人电话<span class="required">*</span></label>
<input class="form-input" placeholder="请输入联系电话">
</div>
<div class="form-group">
@@ -205,15 +265,12 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</div>
</div>
<!-- 地址信息 -->
<!-- 卡片2地址信息 -->
<div class="form-card">
<div class="form-card__title">地址信息</div>
<!-- 地图选点 -->
<div class="map-container">
<div class="form-card__title"><span class="card-icon">📍</span>地址信息</div>
<div class="map-container" style="margin-bottom:12px;">
<div class="map-mock">
<!-- 网格背景 -->
<div class="map-mock__grid"></div>
<!-- 模拟道路 -->
<div class="map-mock__roads">
<div class="map-mock__road-h" style="top:35%"></div>
<div class="map-mock__road-h" style="top:55%"></div>
@@ -222,17 +279,14 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<div class="map-mock__road-v" style="left:50%"></div>
<div class="map-mock__road-v" style="left:72%"></div>
</div>
<!-- 搜索框 -->
<div class="map-mock__search">
<input placeholder="搜索地址或地名">
<button>搜索</button>
</div>
<!-- 缩放控件 -->
<div class="map-mock__controls">
<div class="map-mock__ctrl-btn">+</div>
<div class="map-mock__ctrl-btn"></div>
</div>
<!-- 标记点 -->
<div class="map-mock__pin">
<svg viewBox="0 0 36 48" fill="none">
<path d="M18 0C8.06 0 0 8.06 0 18c0 13.5 18 30 18 30s18-16.5 18-30C36 8.06 27.94 0 18 0z" fill="#FF4D4F"/>
@@ -243,11 +297,155 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
<div class="map-mock__label">📍 点击地图选择位置</div>
</div>
</div>
<div style="margin-top:12px;">
<div class="form-group">
<label class="form-label">详细地址</label>
<input class="form-input" id="addressInput" placeholder="地图选点后自动填入,也可手动输入详细地址">
<span class="form-hint">点击地图选点后,地址信息将自动写入此输入框</span>
<div class="form-group">
<label class="form-label">详细地址<span class="required">*</span></label>
<input class="form-input" id="addressInput" placeholder="地图选点后自动填入,也可手动输入详细地址">
<span class="form-hint">点击地图选点后,地址信息将自动写入此输入框</span>
</div>
</div>
<!-- 卡片3:营业时段 -->
<div class="form-card">
<div class="form-card__title"><span class="card-icon">🕐</span>营业时段</div>
<div style="overflow-x:auto;">
<table class="biz-hours-table" id="bizHoursTable">
<thead>
<tr>
<th class="col-meal">餐次<span class="required">*</span></th>
<th class="col-weekday">营业日<span class="required">*</span></th>
<th class="col-time">开始时间<span class="required">*</span></th>
<th class="col-time">结束时间<span class="required">*</span></th>
<th class="col-action">操作</th>
</tr>
</thead>
<tbody id="bizHoursBody">
</tbody>
</table>
</div>
<div class="biz-hours-add">
<button class="btn btn-default btn-sm" onclick="addBizHourRow()">+ 添加时段</button>
</div>
</div>
<!-- 卡片4:场所图片 -->
<div class="form-card">
<div class="form-card__title"><span class="card-icon">🖼️</span>场所图片</div>
<div class="upload-grid">
<!-- 场所Logo -->
<div class="upload-zone" id="uploadLogo" onclick="simulateUpload('uploadLogo')">
<span class="upload-zone__label">必填</span>
<div class="upload-zone__icon">🏢</div>
<div class="upload-zone__text">场所Logo</div>
<div class="upload-zone__hint">建议尺寸 200×200,支持 jpg/png</div>
<div class="upload-zone__overlay">
<span class="btn" onclick="event.stopPropagation();simulateUpload('uploadLogo')">重新上传</span>
</div>
</div>
<!-- 经营场所门头照 -->
<div class="upload-zone" id="uploadStorefront" onclick="simulateUpload('uploadStorefront')">
<span class="upload-zone__label">必填</span>
<div class="upload-zone__icon">📷</div>
<div class="upload-zone__text">经营场所门头照</div>
<div class="upload-zone__hint">建议尺寸 800×600,支持 jpg/png</div>
<div class="upload-zone__overlay">
<span class="btn" onclick="event.stopPropagation();simulateUpload('uploadStorefront')">重新上传</span>
</div>
</div>
<!-- 场所环境照 -->
<div class="upload-zone" id="uploadEnvironment" onclick="simulateUpload('uploadEnvironment')">
<span class="upload-zone__label">必填</span>
<div class="upload-zone__icon">🖼️</div>
<div class="upload-zone__text">场所环境照</div>
<div class="upload-zone__hint">建议尺寸 800×600,支持 jpg/png</div>
<div class="upload-zone__overlay">
<span class="btn" onclick="event.stopPropagation();simulateUpload('uploadEnvironment')">重新上传</span>
</div>
</div>
</div>
</div>
<!-- 卡片5:资质信息 -->
<div class="form-card">
<div class="form-card__title"><span class="card-icon">📜</span>资质信息</div>
<!-- 营业执照 -->
<div class="qual-section">
<div class="qual-section__title"><span class="dot"></span>营业执照<span class="required" style="color:var(--danger);">*</span></div>
<div class="form-grid">
<div class="form-group">
<label class="form-label">营业执照照片<span class="required">*</span></label>
<div class="qual-img-row">
<div class="qual-img-item">
<div class="upload-zone-small" id="uploadBizLicense" onclick="simulateUpload('uploadBizLicense')">
<div class="upload-zone-small__icon">📄</div>
<div class="upload-zone-small__text">点击上传营业执照</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">统一社会信用代码<span class="required">*</span></label>
<input class="form-input" placeholder="请输入统一社会信用代码">
</div>
<div class="form-group">
<label class="form-label">有效期<span class="required">*</span></label>
<div class="date-range">
<input type="date" value="2023-01-01">
<span class="date-range__sep"></span>
<input type="date" value="2033-01-01">
</div>
</div>
</div>
</div>
<!-- 食品经营许可证 -->
<div class="qual-section">
<div class="qual-section__title"><span class="dot"></span>食品经营许可证<span class="required" style="color:var(--danger);">*</span></div>
<div class="form-grid">
<div class="form-group">
<label class="form-label">许可证照片<span class="required">*</span></label>
<div class="qual-img-row">
<div class="qual-img-item">
<div class="upload-zone-small" id="uploadFoodPermit" onclick="simulateUpload('uploadFoodPermit')">
<div class="upload-zone-small__icon">📄</div>
<div class="upload-zone-small__text">点击上传食品经营许可证</div>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">许可证编号<span class="required">*</span></label>
<input class="form-input" placeholder="请输入许可证编号">
</div>
<div class="form-group">
<label class="form-label">有效期<span class="required">*</span></label>
<div class="date-range">
<input type="date" value="2023-06-01">
<span class="date-range__sep"></span>
<input type="date" value="2028-06-01">
</div>
</div>
</div>
</div>
<!-- 法人身份证 -->
<div class="qual-section">
<div class="qual-section__title"><span class="dot"></span>法人身份证<span class="tag-optional">(非必填)</span></div>
<div class="qual-img-row" style="margin-top:8px;">
<div class="qual-img-item">
<div class="upload-zone-small__label">身份证正面(人像面)</div>
<div class="upload-zone-small" id="uploadIdFront" onclick="simulateUpload('uploadIdFront')">
<div class="upload-zone-small__icon">🪪</div>
<div class="upload-zone-small__text">点击上传身份证正面</div>
</div>
</div>
<div class="qual-img-item">
<div class="upload-zone-small__label">身份证反面(国徽面)</div>
<div class="upload-zone-small" id="uploadIdBack" onclick="simulateUpload('uploadIdBack')">
<div class="upload-zone-small__icon">🪪</div>
<div class="upload-zone-small__text">点击上传身份证反面</div>
</div>
</div>
</div>
</div>
</div>
@@ -260,15 +458,23 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</main>
<script>
/* ===== 侧边栏 ===== */
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
(function(){var active=document.querySelector('.sidebar-item.active');if(!active)return;var sub=active.closest('.sidebar-subgroup');if(sub)sub.classList.add('open');})();
/* 模拟地图选点交互 */
/* ===== 编辑模式检测 ===== */
(function(){
var params=new URLSearchParams(window.location.search);
if(params.get('id')){
document.getElementById('pageTitle').textContent='编辑场所';
}
})();
/* ===== 地图选点 ===== */
document.querySelector('.map-mock').addEventListener('click',function(e){
var rect=this.getBoundingClientRect();
var x=e.clientX-rect.left;
var y=e.clientY-rect.top;
/* 移动标记点 */
var pin=this.querySelector('.map-mock__pin');
var label=this.querySelector('.map-mock__label');
pin.style.left=x+'px';
@@ -276,13 +482,195 @@ document.querySelector('.map-mock').addEventListener('click',function(e){
label.style.left=x+'px';
label.style.top=(y+6)+'px';
label.textContent='📍 已选择位置';
/* 模拟填入地址 */
document.getElementById('addressInput').value='陕西省西安市未央区凤城八路CQ能源总部基地';
});
/* ===== 营业时段 ===== */
var mealOptions=['早餐','午餐','晚餐','加餐'];
/* 星期切换 */
function toggleWeekday(el){
el.classList.toggle('active');
}
/* 全选/取消全选 */
function toggleAllWeekday(container){
var toggles=container.querySelectorAll('.weekday-toggle');
var allActive=true;
toggles.forEach(function(t){if(!t.classList.contains('active'))allActive=false;});
toggles.forEach(function(t){
if(allActive){t.classList.remove('active');}
else{t.classList.add('active');}
});
}
/* 创建星期按钮组 */
function createWeekdayToggles(){
var days=['一','二','三','四','五','六','日'];
var html='<div class="weekday-toggles">';
html+='<span class="weekday-toggle-all" onclick="toggleAllWeekday(this.parentElement)">全选</span>';
days.forEach(function(d){
html+='<span class="weekday-toggle active" onclick="toggleWeekday(this)">'+d+'</span>';
});
html+='</div>';
return html;
}
/* 创建营业时段行 */
function createBizHourRow(meal,start,end){
var tr=document.createElement('tr');
var mealSelect='<select class="form-select" style="width:90px;">';
mealSelect+='<option value="">请选择</option>';
mealOptions.forEach(function(m){
mealSelect+='<option value="'+m+'"'+(m===meal?' selected':'')+'>'+m+'</option>';
});
mealSelect+='</select>';
tr.innerHTML=
'<td>'+mealSelect+'</td>'+
'<td>'+createWeekdayToggles()+'</td>'+
'<td><input type="time" value="'+(start||'07:00')+'"></td>'+
'<td><input type="time" value="'+(end||'09:00')+'"></td>'+
'<td style="text-align:center;"><button class="btn btn-text btn-text--danger btn-sm" onclick="deleteBizHourRow(this)">删除</button></td>';
return tr;
}
/* 添加时段行 */
function addBizHourRow(meal,start,end){
var tbody=document.getElementById('bizHoursBody');
tbody.appendChild(createBizHourRow(meal,start,end));
}
/* 删除时段行 */
function deleteBizHourRow(btn){
var tbody=document.getElementById('bizHoursBody');
if(tbody.children.length<=1){
alert('至少保留一个营业时段');
return;
}
btn.closest('tr').remove();
}
/* 初始化默认时段 */
(function(){
addBizHourRow('早餐','07:00','09:00');
addBizHourRow('午餐','11:30','13:00');
addBizHourRow('晚餐','17:30','19:00');
})();
/* ===== 模拟图片上传 ===== */
function simulateUpload(zoneId){
var zone=document.getElementById(zoneId);
var label=zone.querySelector('.upload-zone__label');
var icon=zone.querySelector('.upload-zone__icon');
var text=zone.querySelector('.upload-zone__text');
var hint=zone.querySelector('.upload-zone__hint');
/* 模拟:已有图片则提示重新上传 */
if(zone.classList.contains('has-image')){
zone.classList.remove('has-image');
if(icon)icon.style.display='';
if(text)text.style.display='';
if(hint)hint.style.display='';
if(label)label.style.display='';
var existingImg=zone.querySelector('.upload-zone__preview');
if(existingImg)existingImg.remove();
return;
}
/* 模拟上传成功 */
zone.classList.add('has-image');
if(icon)icon.style.display='none';
if(text)text.style.display='none';
if(hint)hint.style.display='none';
if(label)label.style.display='none';
/* 生成模拟预览占位图(SVG 内联) */
var svgNS='http://www.w3.org/2000/svg';
var colors={uploadLogo:'#1890FF',uploadStorefront:'#52C41A',uploadEnvironment:'#FAAD14',
uploadBizLicense:'#722ED1',uploadFoodPermit:'#EB2F96',uploadIdFront:'#13C2C2',uploadIdBack:'#FA541C'};
var labels={uploadLogo:'场所Logo',uploadStorefront:'门头照',uploadEnvironment:'环境照',
uploadBizLicense:'营业执照',uploadFoodPermit:'食品经营许可证',uploadIdFront:'身份证正面',uploadIdBack:'身份证反面'};
var color=colors[zoneId]||'#1890FF';
var labelText=labels[zoneId]||'图片';
var img=document.createElement('div');
img.className='upload-zone__preview';
img.style.cssText='width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:'+color+'15;';
img.innerHTML='<div style="text-align:center;color:'+color+';">'+
'<div style="font-size:40px;margin-bottom:6px;">📷</div>'+
'<div style="font-size:12px;">'+labelText+'(模拟已上传)</div>'+
'</div>';
zone.appendChild(img);
}
/* ===== 保存 ===== */
function handleSave(){
alert('【交互说明】点击保存后,校验必填项,通过后保存并返回场所列表页');
/* 校验必填项 */
var placeName=document.querySelector('.form-card .form-grid .form-input[placeholder="请输入场所名称"]');
var leader=document.querySelector('.form-card .form-grid .form-input[placeholder="请输入负责人姓名"]');
var phone=document.querySelector('.form-card .form-grid .form-input[placeholder="请输入联系电话"]');
var address=document.getElementById('addressInput');
if(!placeName.value.trim()){
alert('请输入场所名称');placeName.focus();return;
}
if(!leader.value.trim()){
alert('请输入负责人');leader.focus();return;
}
if(!phone.value.trim()){
alert('请输入负责人电话');phone.focus();return;
}
if(!/^1\d{10}$/.test(phone.value.trim())){
alert('请输入正确的手机号码');phone.focus();return;
}
if(!address.value.trim()){
alert('请输入详细地址');address.focus();return;
}
/* 校验营业时段必填 */
var rows=document.getElementById('bizHoursBody').querySelectorAll('tr');
for(var i=0;i<rows.length;i++){
var cells=rows[i].querySelectorAll('td');
var mealSelect=cells[0].querySelector('select');
var timeStart=cells[2].querySelector('input');
var timeEnd=cells[3].querySelector('input');
var weekdays=cells[1].querySelectorAll('.weekday-toggle.active');
if(!mealSelect.value){
alert('请选择第'+(i+1)+'行餐次名称');mealSelect.focus();return;
}
if(weekdays.length===0){
alert('请选择第'+(i+1)+'行营业日');return;
}
if(!timeStart.value){
alert('请填写第'+(i+1)+'行开始时间');timeStart.focus();return;
}
if(!timeEnd.value){
alert('请填写第'+(i+1)+'行结束时间');timeEnd.focus();return;
}
}
/* 校验场所图片 */
if(!document.getElementById('uploadLogo').classList.contains('has-image')){
alert('请上传场所Logo');return;
}
if(!document.getElementById('uploadStorefront').classList.contains('has-image')){
alert('请上传经营场所门头照');return;
}
if(!document.getElementById('uploadEnvironment').classList.contains('has-image')){
alert('请上传场所环境照');return;
}
/* 校验资质信息 */
if(!document.getElementById('uploadBizLicense').querySelector('.upload-zone-small').classList.contains('has-image')){
alert('请上传营业执照照片');return;
}
if(!document.getElementById('uploadFoodPermit').querySelector('.upload-zone-small').classList.contains('has-image')){
alert('请上传食品经营许可证照片');return;
}
alert('【交互说明】校验通过,保存场所信息并返回列表页');
}
</script>
</body>
</html>
</html>
+132 -13
View File
@@ -79,6 +79,28 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
.detail-row{display:flex;margin-bottom:18px;}
.detail-label{width:110px;flex-shrink:0;font-size:var(--font-sm);color:var(--text-secondary);}
.detail-value{font-size:var(--font-sm);color:var(--text-primary);flex:1;}
/* 详情-分区标题 */
.detail-section{border-top:1px dashed var(--border);padding-top:18px;margin-top:18px;}
.detail-section:first-child{border-top:none;padding-top:0;margin-top:0;}
.detail-section__title{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);margin-bottom:14px;display:flex;align-items:center;gap:6px;}
.detail-section__title .dot{width:6px;height:6px;border-radius:50%;background:var(--primary);}
/* 详情-营业时段 */
.detail-biz-item{display:flex;align-items:center;gap:10px;padding:6px 10px;background:#FAFAFA;border-radius:var(--radius-sm);margin-bottom:8px;font-size:var(--font-xs);}
.detail-biz-item .biz-meal{font-weight:600;color:var(--text-primary);min-width:56px;}
.detail-biz-item .biz-weekdays{display:flex;gap:2px;}
.detail-biz-item .biz-wd{width:22px;height:20px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:10px;background:#E8E8E8;color:var(--text-placeholder);}
.detail-biz-item .biz-wd.active{background:var(--primary);color:#fff;}
.detail-biz-item .biz-time{color:var(--primary);white-space:nowrap;}
/* 详情-图片缩略图 */
.detail-img-row{display:flex;gap:12px;flex-wrap:wrap;}
.detail-img-item{width:100px;text-align:center;}
.detail-img-item .thumb{width:100px;height:75px;border-radius:var(--radius-sm);background:#E8ECF0;display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--text-placeholder);border:1px solid var(--border);}
.detail-img-item .thumb-label{font-size:10px;color:var(--text-secondary);margin-top:4px;}
/* 详情-资质卡片 */
.detail-qual-card{background:#FAFAFA;border-radius:var(--radius-sm);padding:12px;margin-bottom:10px;}
.detail-qual-card__title{font-size:var(--font-xs);font-weight:600;color:var(--text-primary);margin-bottom:8px;}
.detail-qual-card__row{display:flex;font-size:var(--font-xs);margin-bottom:4px;}
.detail-qual-card__label{color:var(--text-secondary);width:90px;flex-shrink:0;}
</style>
</head>
<body>
@@ -179,10 +201,10 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</div>
<div class="table-card__content">
<table>
<thead><tr><th>场所编号</th><th>场所名称</th><th>场所类型</th><th>所属单位</th><th>详细地址</th><th>操作</th></tr></thead>
<thead><tr><th>场所编号</th><th>场所名称</th><th>场所类型</th><th>所属单位</th><th>负责人</th><th>详细地址</th><th>操作</th></tr></thead>
<tbody>
<tr>
<td>ST-2026-001</td><td>总部一号食堂</td><td><span class="tag tag-blue">食堂</span></td><td>CQ能源总部</td><td>陕西省西安市未央区凤城八路总部基地A区</td>
<td>ST-2026-001</td><td>总部一号食堂</td><td><span class="tag tag-blue">食堂</span></td><td>CQ能源总部</td><td>张建国</td><td>陕西省西安市未央区凤城八路总部基地A区</td>
<td class="td-actions">
<span class="link" onclick="openDrawer(0)">详情</span>
<a class="link" href="din-canteen-line.html?id=1&name=总部一号食堂">餐线管理</a>
@@ -191,7 +213,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</td>
</tr>
<tr>
<td>ST-2026-002</td><td>总部二号食堂</td><td><span class="tag tag-blue">食堂</span></td><td>CQ能源总部</td><td>陕西省西安市未央区凤城八路总部基地B区</td>
<td>ST-2026-002</td><td>总部二号食堂</td><td><span class="tag tag-blue">食堂</span></td><td>CQ能源总部</td><td>李红梅</td><td>陕西省西安市未央区凤城八路总部基地B区</td>
<td class="td-actions">
<span class="link" onclick="openDrawer(1)">详情</span>
<a class="link" href="din-canteen-line.html?id=2&name=总部二号食堂">餐线管理</a>
@@ -200,7 +222,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</td>
</tr>
<tr>
<td>ST-2026-003</td><td>采油一厂职工食堂</td><td><span class="tag tag-blue">食堂</span></td><td>采油一厂</td><td>陕西省延安市安塞区采油一厂生活区</td>
<td>ST-2026-003</td><td>采油一厂职工食堂</td><td><span class="tag tag-blue">食堂</span></td><td>采油一厂</td><td>王志强</td><td>陕西省延安市安塞区采油一厂生活区</td>
<td class="td-actions">
<span class="link" onclick="openDrawer(2)">详情</span>
<a class="link" href="din-canteen-line.html?id=3&name=采油一厂职工食堂">餐线管理</a>
@@ -209,7 +231,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</td>
</tr>
<tr>
<td>ST-2026-004</td><td>采油二厂综合食堂</td><td><span class="tag tag-blue">食堂</span></td><td>采油二厂</td><td>甘肃省庆阳市庆城县采油二厂驻地</td>
<td>ST-2026-004</td><td>采油二厂综合食堂</td><td><span class="tag tag-blue">食堂</span></td><td>采油二厂</td><td>刘文静</td><td>甘肃省庆阳市庆城县采油二厂驻地</td>
<td class="td-actions">
<span class="link" onclick="openDrawer(3)">详情</span>
<a class="link" href="din-canteen-line.html?id=4&name=采油二厂综合食堂">餐线管理</a>
@@ -218,7 +240,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
</td>
</tr>
<tr>
<td>ST-2026-005</td><td>采气一厂营养餐厅</td><td><span class="tag tag-green">餐厅</span></td><td>采气一厂</td><td>陕西省榆林市靖边县采气一厂作业区</td>
<td>ST-2026-005</td><td>采气一厂营养餐厅</td><td><span class="tag tag-green">餐厅</span></td><td>采气一厂</td><td>陈伟</td><td>陕西省榆林市靖边县采气一厂作业区</td>
<td class="td-actions">
<span class="link" onclick="openDrawer(4)">详情</span>
<a class="link" href="din-canteen-line.html?id=5&name=采气一厂营养餐厅">餐线管理</a>
@@ -249,24 +271,121 @@ function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
/* 详情数据 */
var canteens=[
{name:'总部一号食堂',code:'ST-2026-001',type:'食堂',unit:'CQ能源总部',address:'陕西省西安市未央区凤城八路总部基地A区',leader:'张建国',phone:'138****1001',terminal:'是'},
{name:'总部二号食堂',code:'ST-2026-002',type:'食堂',unit:'CQ能源总部',address:'陕西省西安市未央区凤城八路总部基地B区',leader:'李红梅',phone:'139****2002',terminal:''},
{name:'采油一厂职工食堂',code:'ST-2026-003',type:'食堂',unit:'采油一厂',address:'陕西省延安市安塞区采油一厂生活区',leader:'王志强',phone:'137****3003',terminal:'否'},
{name:'采油二厂综合食堂',code:'ST-2026-004',type:'食堂',unit:'采油二厂',address:'甘肃省庆阳市庆城县采油二厂驻地',leader:'刘文静',phone:'136****4004',terminal:'是'},
{name:'采气一厂营养餐厅',code:'ST-2026-005',type:'餐厅',unit:'采气一厂',address:'陕西省榆林市靖边县采气一厂作业区',leader:'陈伟',phone:'135****5005',terminal:'否'}
{name:'总部一号食堂',code:'ST-2026-001',type:'食堂',unit:'CQ能源总部',address:'陕西省西安市未央区凤城八路总部基地A区',leader:'张建国',phone:'138****1001',terminal:'是',
bizHours:[{meal:'早餐',weekdays:[1,1,1,1,1,0,0],start:'07:00',end:'09:00'},{meal:'午餐',weekdays:[1,1,1,1,1,1,0],start:'11:30',end:'13:00'},{meal:'晚餐',weekdays:[1,1,1,1,1,1,1],start:'17:30',end:'19:00'}],
images:{logo:true,storefront:true,environment:true},
qual:{bizLicense:{code:'91610132MA6TXBXXXX',validFrom:'2023-01-01',validTo:'2033-01-01'},foodPermit:{code:'JY16101234567890',validFrom:'2023-06-01',validTo:'2028-06-01'},idCard:{front:true,back:true}}
},
{name:'总部二号食堂',code:'ST-2026-002',type:'食堂',unit:'CQ能源总部',address:'陕西省西安市未央区凤城八路总部基地B区',leader:'李红梅',phone:'139****2002',terminal:'是',
bizHours:[{meal:'早餐',weekdays:[1,1,1,1,1,0,0],start:'06:30',end:'08:30'},{meal:'午餐',weekdays:[1,1,1,1,1,1,0],start:'11:00',end:'13:30'},{meal:'晚餐',weekdays:[1,1,1,1,1,1,1],start:'17:00',end:'20:00'},{meal:'加餐',weekdays:[1,1,1,1,1,1,1],start:'21:00',end:'23:00'}],
images:{logo:true,storefront:true,environment:true},
qual:{bizLicense:{code:'91610132MA6TYCYYYY',validFrom:'2022-06-01',validTo:'2032-06-01'},foodPermit:{code:'JY16109876543210',validFrom:'2022-08-01',validTo:'2027-08-01'},idCard:{front:true,back:true}}
},
{name:'采油一厂职工食堂',code:'ST-2026-003',type:'食堂',unit:'采油一厂',address:'陕西省延安市安塞区采油一厂生活区',leader:'王志强',phone:'137****3003',terminal:'否',
bizHours:[{meal:'早餐',weekdays:[1,1,1,1,1,0,0],start:'07:00',end:'09:00'},{meal:'午餐',weekdays:[1,1,1,1,1,0,0],start:'11:30',end:'13:00'},{meal:'晚餐',weekdays:[1,1,1,1,1,0,0],start:'17:30',end:'19:00'}],
images:{logo:true,storefront:true,environment:false},
qual:{bizLicense:{code:'91610624MA6TXZZZZZ',validFrom:'2023-03-01',validTo:'2033-03-01'},foodPermit:{code:'JY16105555666',validFrom:'2023-03-15',validTo:'2028-03-15'},idCard:{front:false,back:false}}
},
{name:'采油二厂综合食堂',code:'ST-2026-004',type:'食堂',unit:'采油二厂',address:'甘肃省庆阳市庆城县采油二厂驻地',leader:'刘文静',phone:'136****4004',terminal:'是',
bizHours:[{meal:'早餐',weekdays:[1,1,1,1,1,0,0],start:'07:00',end:'09:00'},{meal:'午餐',weekdays:[1,1,1,1,1,0,0],start:'12:00',end:'14:00'},{meal:'晚餐',weekdays:[1,1,1,1,1,0,0],start:'18:00',end:'20:00'}],
images:{logo:true,storefront:true,environment:true},
qual:{bizLicense:{code:'91621021MA6TXAAAAA',validFrom:'2024-01-01',validTo:'2034-01-01'},foodPermit:{code:'JY16106666777',validFrom:'2024-02-01',validTo:'2029-02-01'},idCard:{front:true,back:false}}
},
{name:'采气一厂营养餐厅',code:'ST-2026-005',type:'餐厅',unit:'采气一厂',address:'陕西省榆林市靖边县采气一厂作业区',leader:'陈伟',phone:'135****5005',terminal:'否',
bizHours:[{meal:'午餐',weekdays:[1,1,1,1,1,1,0],start:'11:30',end:'13:30'},{meal:'晚餐',weekdays:[1,1,1,1,1,1,1],start:'17:30',end:'20:00'}],
images:{logo:true,storefront:false,environment:true},
qual:{bizLicense:{code:'91610824MA6TXBBBBB',validFrom:'2023-09-01',validTo:'2033-09-01'},foodPermit:{code:'JY16107777888',validFrom:'2023-10-01',validTo:'2028-10-01'},idCard:{front:true,back:true}}
}
];
function openDrawer(idx){
var d=canteens[idx];
var tagClass=d.type==='食堂'?'tag-blue':'tag-green';
var terminalTag=d.terminal==='是'?'tag-green':'tag-gray';
/* 构建营业时段 HTML */
var bizHtml='';
d.bizHours.forEach(function(bh){
var wdLabels=['一','二','三','四','五','六','日'];
var wdHtml='';
bh.weekdays.forEach(function(active,i){
wdHtml+='<span class="biz-wd'+(active?' active':'')+'">'+wdLabels[i]+'</span>';
});
bizHtml+='<div class="detail-biz-item">'+
'<span class="biz-meal">'+bh.meal+'</span>'+
'<span class="biz-weekdays">'+wdHtml+'</span>'+
'<span class="biz-time">'+bh.start+' - '+bh.end+'</span>'+
'</div>';
});
/* 构建场所图片 HTML */
var imgLabels={logo:'场所Logo',storefront:'门头照',environment:'环境照'};
var imgIcons={logo:'🏢',storefront:'📷',environment:'🖼️'};
var imgHtml='<div class="detail-img-row">';
['logo','storefront','environment'].forEach(function(key){
var hasImg=d.images[key];
imgHtml+='<div class="detail-img-item">'+
'<div class="thumb" style="'+(hasImg?'background:#E6F7FF;border-color:#91D5FF;':'')+'">'+
(hasImg?'📷':'—')+'</div>'+
'<div class="thumb-label">'+imgLabels[key]+(hasImg?' ✅':'')+'</div>'+
'</div>';
});
imgHtml+='</div>';
/* 构建资质信息 HTML */
var qualHtml='';
/* 营业执照 */
qualHtml+='<div class="detail-qual-card">'+
'<div class="detail-qual-card__title">📄 营业执照</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">照片:</span>已上传</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">信用代码:</span>'+d.qual.bizLicense.code+'</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">有效期:</span>'+d.qual.bizLicense.validFrom+' 至 '+d.qual.bizLicense.validTo+'</div>'+
'</div>';
/* 食品经营许可证 */
qualHtml+='<div class="detail-qual-card">'+
'<div class="detail-qual-card__title">📋 食品经营许可证</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">照片:</span>已上传</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">许可证编号:</span>'+d.qual.foodPermit.code+'</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">有效期:</span>'+d.qual.foodPermit.validFrom+' 至 '+d.qual.foodPermit.validTo+'</div>'+
'</div>';
/* 法人身份证 */
var idFront=d.qual.idCard.front?'已上传':'未上传';
var idBack=d.qual.idCard.back?'已上传':'未上传';
qualHtml+='<div class="detail-qual-card">'+
'<div class="detail-qual-card__title">🪪 法人身份证<span style="font-weight:400;color:var(--text-placeholder);">(非必填)</span></div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">正面(人像面):</span>'+idFront+'</div>'+
'<div class="detail-qual-card__row"><span class="detail-qual-card__label">反面(国徽面):</span>'+idBack+'</div>'+
'</div>';
document.getElementById('drawerBody').innerHTML=
/* 基本信息 */
'<div class="detail-section">'+
'<div class="detail-section__title"><span class="dot"></span>基本信息</div>'+
'<div class="detail-row"><div class="detail-label">场所编号</div><div class="detail-value">'+d.code+'</div></div>'+
'<div class="detail-row"><div class="detail-label">场所名称</div><div class="detail-value">'+d.name+'</div></div>'+
'<div class="detail-row"><div class="detail-label">场所类型</div><div class="detail-value"><span class="tag '+(d.type==='食堂'?'tag-blue':'tag-green')+'">'+d.type+'</span></div></div>'+
'<div class="detail-row"><div class="detail-label">场所类型</div><div class="detail-value"><span class="tag '+tagClass+'">'+d.type+'</span></div></div>'+
'<div class="detail-row"><div class="detail-label">所属单位</div><div class="detail-value">'+d.unit+'</div></div>'+
'<div class="detail-row"><div class="detail-label">详细地址</div><div class="detail-value">'+d.address+'</div></div>'+
'<div class="detail-row"><div class="detail-label">负责人</div><div class="detail-value">'+d.leader+'</div></div>'+
'<div class="detail-row"><div class="detail-label">负责人电话</div><div class="detail-value">'+d.phone+'</div></div>'+
'<div class="detail-row"><div class="detail-label">使用三减终端</div><div class="detail-value"><span class="tag '+(d.terminal==='是'?'tag-green':'tag-gray')+'">'+d.terminal+'</span></div></div>';
'<div class="detail-row"><div class="detail-label">使用三减终端</div><div class="detail-value"><span class="tag '+terminalTag+'">'+d.terminal+'</span></div></div>'+
'</div>'+
/* 营业时段 */
'<div class="detail-section">'+
'<div class="detail-section__title"><span class="dot"></span>营业时段</div>'+
bizHtml+
'</div>'+
/* 场所图片 */
'<div class="detail-section">'+
'<div class="detail-section__title"><span class="dot"></span>场所图片</div>'+
imgHtml+
'</div>'+
/* 资质信息 */
'<div class="detail-section">'+
'<div class="detail-section__title"><span class="dot"></span>资质信息</div>'+
qualHtml+
'</div>';
document.getElementById('drawerMask').classList.add('show');
document.getElementById('drawer').classList.add('show');
}
+184
View File
@@ -153,6 +153,26 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
thead th:last-child,tbody td.td-actions{position:sticky;right:0;background:#fff;box-shadow:-4px 0 8px rgba(0,0,0,0.04);z-index:2;white-space:nowrap;}
thead th:last-child{background:#FAFAFA;}
tbody tr:hover td.td-actions{background:#F8FBFF;}
/* 分类管理表格 */
.cat-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.cat-table th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
.cat-table td{padding:8px 12px;border-bottom:1px solid #F5F5F5;color:var(--text-primary);}
.cat-table tbody tr:hover{background:#F8FBFF;}
.cat-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border:1px solid var(--border);border-radius:3px;cursor:pointer;margin-right:6px;font-size:12px;line-height:1;background:#fff;color:var(--text-secondary);flex-shrink:0;vertical-align:middle;}
.cat-toggle:hover{border-color:var(--primary);color:var(--primary);}
.cat-toggle.leaf{visibility:hidden;}
.cat-table td.td-actions{white-space:nowrap;}
.cat-table td.td-actions .link{margin:0 4px;}
/* 状态胶囊开关 */
.status-toggle{display:inline-flex;align-items:center;gap:6px;cursor:pointer;user-select:none;}
.status-toggle__switch{width:36px;height:20px;background:#D9D9D9;border-radius:10px;position:relative;transition:background .2s;flex-shrink:0;}
.status-toggle__switch::after{content:'';position:absolute;left:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px rgba(0,0,0,0.15);}
.status-toggle.active .status-toggle__switch{background:var(--success);}
.status-toggle.active .status-toggle__switch::after{transform:translateX(16px);}
.status-toggle__label{font-size:var(--font-xs);color:var(--text-placeholder);}
.status-toggle.active .status-toggle__label{color:var(--success);}
</style>
</head>
<body>
@@ -246,6 +266,7 @@ tbody tr:hover td.td-actions{background:#F8FBFF;}
<div class="panel-left__actions">
<a href="din-dish-add.html" class="btn btn-primary"> 新增餐品</a>
<button class="btn btn-default">📤 批量导入</button>
<button class="btn btn-default" onclick="openDishCategoryDialog()">📂 餐品分类管理</button>
</div>
<div class="food-list" id="dishList"></div>
<div class="panel-left__footer">
@@ -510,6 +531,123 @@ function toggleCleanSale(el){
// 初始化
renderDishList();
renderDetail();
/* ========== 餐品分类管理 ========== */
var nextDishCatId = 100;
var dishCategories = [
{id:1, name:'主食', code:'01', level:1, sort:1, status:'启用', parentId:null},
{id:2, name:'荤菜', code:'02', level:1, sort:2, status:'启用', parentId:null},
{id:3, name:'素菜', code:'03', level:1, sort:3, status:'启用', parentId:null},
{id:4, name:'汤品', code:'04', level:1, sort:4, status:'启用', parentId:null},
{id:5, name:'小吃', code:'05', level:1, sort:5, status:'启用', parentId:null},
{id:6, name:'饮品', code:'06', level:1, sort:6, status:'启用', parentId:null},
{id:7, name:'甜品', code:'07', level:1, sort:7, status:'启用', parentId:null},
{id:8, name:'套餐', code:'08', level:1, sort:8, status:'禁用', parentId:null}
];
var expandedDishCats = {};
var editingDishCatId = null;
var editingDishParentId = null;
var MAX_DISH_LEVEL = 2;
function openDishCategoryDialog(){
document.getElementById('dishCategoryDialog').classList.add('open');
renderDishCatTable();
}
function closeDishCategoryDialog(){
document.getElementById('dishCategoryDialog').classList.remove('open');
}
function toggleDishCatStatus(id){
var cat = dishCategories.find(function(c){return c.id===id;});
if(!cat) return;
cat.status = cat.status==='启用'?'禁用':'启用';
renderDishCatTable();
}
function toggleDishCatExpand(id){
expandedDishCats[id] = !expandedDishCats[id];
renderDishCatTable();
}
function getDishCatChildren(parentId){
return dishCategories.filter(function(c){return c.parentId===parentId;}).sort(function(a,b){return a.sort-b.sort;});
}
function renderDishCatTable(){
var tops = dishCategories.filter(function(c){return c.parentId===null;}).sort(function(a,b){return a.sort-b.sort;});
var html = '';
tops.forEach(function(cat){
html += renderDishCatRow(cat);
if(expandedDishCats[cat.id]){
getDishCatChildren(cat.id).forEach(function(child){
html += renderDishCatRow(child, true);
});
}
});
document.getElementById('dishCatTableBody').innerHTML = html || '<tr><td colspan="6" style="text-align:center;color:var(--text-placeholder);padding:40px;">暂无分类数据</td></tr>';
}
function renderDishCatRow(cat, isChild){
var hasChildren = getDishCatChildren(cat.id).length > 0;
var isExpanded = expandedDishCats[cat.id];
var toggleHtml = hasChildren
? '<span class="cat-toggle" onclick="event.stopPropagation();toggleDishCatExpand('+cat.id+')">'+(isExpanded?'':'+')+'</span>'
: '<span class="cat-toggle leaf">+</span>';
var rowClass = isChild ? 'cat-row-l2' : '';
var activeClass = cat.status==='启用'?' active':'';
var statusLabel = cat.status==='启用'?'启用':'禁用';
var statusHtml = '<div class="status-toggle'+activeClass+'" onclick="event.stopPropagation();toggleDishCatStatus('+cat.id+')"><span class="status-toggle__switch"></span><span class="status-toggle__label">'+statusLabel+'</span></div>';
var canAddChild = cat.level < MAX_DISH_LEVEL;
var addChildBtn = canAddChild ? '<span class="link" onclick="openAddDishCat('+cat.id+')">新增下级</span>' : '';
return '<tr class="'+rowClass+'"><td class="cat-name">'+toggleHtml+cat.name+'</td><td>'+cat.code+'</td><td>'+cat.level+'</td><td>'+cat.sort+'</td><td>'+statusHtml+'</td><td class="td-actions"><span class="link" onclick="openEditDishCat('+cat.id+')">编辑</span><span class="link" style="color:var(--danger);" onclick="deleteDishCat('+cat.id+')">删除</span>'+addChildBtn+'</td></tr>';
}
function openAddDishCat(parentId){
editingDishCatId = null;
editingDishParentId = parentId || null;
document.getElementById('dishCatFormDialogTitle').textContent = parentId ? '新增下级分类' : '新增分类';
document.getElementById('dishCatFormName').value = '';
document.getElementById('dishCatFormCode').value = '';
document.getElementById('dishCatFormSort').value = '1';
document.getElementById('dishCatFormStatus').value = '启用';
document.getElementById('dishCatFormDialog').classList.add('open');
}
function openEditDishCat(id){
var cat = dishCategories.find(function(c){return c.id===id;});
if(!cat) return;
editingDishCatId = id;
editingDishParentId = cat.parentId;
document.getElementById('dishCatFormDialogTitle').textContent = '编辑分类';
document.getElementById('dishCatFormName').value = cat.name;
document.getElementById('dishCatFormCode').value = cat.code;
document.getElementById('dishCatFormSort').value = cat.sort;
document.getElementById('dishCatFormStatus').value = cat.status;
document.getElementById('dishCatFormDialog').classList.add('open');
}
function closeDishCatFormDialog(){
document.getElementById('dishCatFormDialog').classList.remove('open');
editingDishCatId = null;
editingDishParentId = null;
}
function saveDishCatForm(){
var name = document.getElementById('dishCatFormName').value.trim();
var code = document.getElementById('dishCatFormCode').value.trim();
var sort = parseInt(document.getElementById('dishCatFormSort').value) || 1;
var status = document.getElementById('dishCatFormStatus').value;
if(!name||!code){alert('请填写分类名称和分类编码');return;}
if(editingDishCatId){
var cat = dishCategories.find(function(c){return c.id===editingDishCatId;});
if(cat){cat.name=name;cat.code=code;cat.sort=sort;cat.status=status;}
} else {
var level = editingDishParentId ? 2 : 1;
dishCategories.push({id:nextDishCatId++,name:name,code:code,level:level,sort:sort,status:status,parentId:editingDishParentId});
if(editingDishParentId) expandedDishCats[editingDishParentId] = true;
}
closeDishCatFormDialog();
renderDishCatTable();
}
function deleteDishCat(id){
var cat = dishCategories.find(function(c){return c.id===id;});
if(!cat) return;
if(getDishCatChildren(id).length > 0){alert('该分类下存在子级分类,请先删除子级分类后再删除。');return;}
if(!confirm('确定要删除分类「'+cat.name+'」吗?删除后不可恢复。')) return;
dishCategories = dishCategories.filter(function(c){return c.id!==id;});
renderDishCatTable();
}
</script>
<!-- 新增食材弹窗 -->
@@ -547,5 +685,51 @@ renderDetail();
function openIngredientDialog(){document.getElementById('ingredientDialog').classList.add('open');}
function closeIngredientDialog(){document.getElementById('ingredientDialog').classList.remove('open');}
</script>
<!-- 餐品分类管理弹窗 -->
<div class="dialog-mask" id="dishCategoryDialog">
<div class="dialog" style="width:820px;">
<div class="dialog__header">
<div class="dialog__title">餐品分类管理</div>
<button class="dialog__close" onclick="closeDishCategoryDialog()"></button>
</div>
<div class="dialog__body">
<!-- 工具栏 -->
<div style="margin-bottom:12px;">
<button class="btn btn-primary" onclick="openAddDishCat()" style="height:30px;font-size:var(--font-xs);"> 新增一级分类</button>
</div>
<!-- 分类表格 -->
<div style="overflow-x:auto;">
<table class="cat-table">
<thead><tr><th style="width:180px;">分类名称</th><th style="width:100px;">分类编码</th><th style="width:60px;">层级</th><th style="width:60px;">排序</th><th style="width:80px;">状态</th><th style="width:180px;">操作</th></tr></thead>
<tbody id="dishCatTableBody"></tbody>
</table>
</div>
</div>
<div class="dialog__footer">
<button class="btn btn-default" onclick="closeDishCategoryDialog()">关闭</button>
</div>
</div>
</div>
<!-- 餐品分类表单子弹窗 -->
<div class="dialog-mask" id="dishCatFormDialog">
<div class="dialog" style="width:480px;">
<div class="dialog__header">
<div class="dialog__title" id="dishCatFormDialogTitle">新增分类</div>
<button class="dialog__close" onclick="closeDishCatFormDialog()"></button>
</div>
<div class="dialog__body">
<div class="form-group"><label>分类名称<span class="required">*</span></label><input class="form-input" id="dishCatFormName" placeholder="请输入分类名称"></div>
<div class="form-row">
<div class="form-group"><label>分类编码<span class="required">*</span></label><input class="form-input" id="dishCatFormCode" placeholder="请输入分类编码"></div>
<div class="form-group"><label>排序</label><input class="form-input" id="dishCatFormSort" type="number" value="1"></div>
</div>
<div class="form-group"><label>状态</label><select class="form-select" id="dishCatFormStatus"><option>启用</option><option>禁用</option></select></div>
</div>
<div class="dialog__footer">
<button class="btn btn-default" onclick="closeDishCatFormDialog()">取消</button>
<button class="btn btn-primary" onclick="saveDishCatForm()">保存</button>
</div>
</div>
</div>
</body>
</html>
+219 -1
View File
@@ -111,6 +111,29 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.dynamic-row__del:hover{color:var(--danger);background:#FFF1F0;}
.dynamic-row-add{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border:1px dashed var(--primary);color:var(--primary);border-radius:var(--radius-sm);font-size:var(--font-xs);cursor:pointer;background:none;margin-top:8px;transition:all .2s;}
.dynamic-row-add:hover{background:var(--primary-light);}
/* 复制食材弹窗 */
.copy-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:300;align-items:center;justify-content:center;}
.copy-overlay.show{display:flex;}
.copy-dialog{background:#fff;border-radius:var(--radius-lg);width:560px;max-width:90vw;box-shadow:var(--shadow-md);display:flex;flex-direction:column;max-height:80vh;}
.copy-dialog__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--border);flex-shrink:0;}
.copy-dialog__title{font-size:var(--font-lg);font-weight:600;}
.copy-dialog__close{width:32px;height:32px;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);color:var(--text-placeholder);font-size:18px;}
.copy-dialog__close:hover{background:#F5F5F5;color:var(--text-primary);}
.copy-dialog__search{padding:12px 24px;border-bottom:1px solid #F0F0F0;flex-shrink:0;}
.copy-dialog__search input{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;}
.copy-dialog__search input:focus{border-color:var(--primary);}
.copy-dialog__body{flex:1;overflow-y:auto;padding:0;}
.copy-dialog__empty{padding:40px 24px;text-align:center;color:var(--text-placeholder);font-size:var(--font-sm);}
.copy-item{display:flex;align-items:center;gap:12px;padding:12px 24px;cursor:pointer;transition:background .15s;border-bottom:1px solid #F5F5F5;}
.copy-item:hover{background:#F8FBFF;}
.copy-item.active{background:var(--primary-light);}
.copy-item__icon{width:36px;height:36px;border-radius:var(--radius-sm);background:#F5F7FA;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}
.copy-item__info{flex:1;min-width:0;}
.copy-item__name{font-size:var(--font-sm);font-weight:500;color:var(--text-primary);}
.copy-item__meta{font-size:var(--font-xs);color:var(--text-placeholder);margin-top:2px;display:flex;gap:8px;}
.copy-item__check{display:none;width:20px;height:20px;border-radius:50%;background:var(--primary);color:#fff;font-size:12px;align-items:center;justify-content:center;flex-shrink:0;}
.copy-item.active .copy-item__check{display:flex;}
.copy-dialog__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 24px;border-top:1px solid var(--border);flex-shrink:0;}
</style>
</head>
<body>
@@ -208,6 +231,26 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</div>
</main>
<!-- 复制食材弹窗 -->
<div class="copy-overlay" id="copyOverlay" onclick="if(event.target===this)closeCopyDialog()">
<div class="copy-dialog">
<div class="copy-dialog__header">
<div class="copy-dialog__title">选择要复制的食材</div>
<button class="copy-dialog__close" onclick="closeCopyDialog()">×</button>
</div>
<div class="copy-dialog__search">
<input type="text" id="copySearchInput" placeholder="搜索食物名称、编码或分类..." oninput="filterCopyList()">
</div>
<div class="copy-dialog__body" id="copyListBody">
<!-- JS 动态渲染 -->
</div>
<div class="copy-dialog__footer">
<button class="btn btn-default" onclick="closeCopyDialog()">取消</button>
<button class="btn btn-primary" id="btnConfirmCopy" disabled onclick="confirmCopy()">确认复制</button>
</div>
</div>
</div>
<script>
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
@@ -216,7 +259,7 @@ var totalSteps = 5;
var STEP_HTML = [
// Step 0: 基本信息
`<div class="form-card__title">📋 基本信息</div>
`<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;"><div class="form-card__title" style="margin-bottom:0;padding-bottom:0;border-bottom:none;flex-shrink:0;">📋 基本信息</div><button type="button" class="btn btn-default" onclick="openCopyDialog()" style="height:32px;font-size:var(--font-xs);">📋 从已有食材复制</button></div>
<div class="form-grid">
<div class="form-group"><label class="form-label">食物编码<span class="required">*</span></label><input class="form-input" id="foodCode" placeholder="系统自动生成(分类+自编码)" readonly style="background:#f5f5f5;cursor:not-allowed;"></div>
<div class="form-group"><label class="form-label">食物名称<span class="required">*</span></label><input class="form-input" id="foodName" placeholder="请输入食物名称"></div>
@@ -745,6 +788,181 @@ function initEditMode(){
}
}
/* === 复制已有食材 === */
var copySelectedCode = '';
var copyFoodList = [];
/* 构建可选食材列表(从 EDIT_FOODS 提取为数组,排除当前编辑的食材本身) */
function buildCopyFoodList(){
copyFoodList = [];
var isEdit = (pageMode === 'edit' && editCode);
for(var code in EDIT_FOODS){
// 编辑模式下排除自身
if(isEdit && code === editCode) continue;
var f = EDIT_FOODS[code];
copyFoodList.push({
code: f.code,
name: f.name,
firstClass: f.firstClass,
secondClass: f.secondClass,
calorie: f.nutri ? f.nutri.calorie : 0
});
}
}
/* 渲染弹窗食材列表 */
function renderCopyList(filterText){
var body = document.getElementById('copyListBody');
if(!body) return;
var kw = (filterText || '').toLowerCase();
var filtered = copyFoodList;
if(kw){
filtered = copyFoodList.filter(function(item){
return item.name.toLowerCase().indexOf(kw) !== -1
|| item.code.toLowerCase().indexOf(kw) !== -1
|| item.firstClass.toLowerCase().indexOf(kw) !== -1
|| item.secondClass.toLowerCase().indexOf(kw) !== -1;
});
}
if(filtered.length === 0){
body.innerHTML = '<div class="copy-dialog__empty">未找到匹配的食材</div>';
return;
}
var html = '';
filtered.forEach(function(item){
var activeClass = (copySelectedCode === item.code) ? ' active' : '';
html += '<div class="copy-item' + activeClass + '" onclick="selectCopyItem(\'' + item.code + '\')">';
html += '<div class="copy-item__icon">🧪</div>';
html += '<div class="copy-item__info"><div class="copy-item__name">' + item.name + '</div>';
html += '<div class="copy-item__meta"><span>' + item.code + '</span><span>' + item.firstClass + ' / ' + item.secondClass + '</span><span>' + item.calorie + ' kcal</span></div></div>';
html += '<div class="copy-item__check">✓</div>';
html += '</div>';
});
body.innerHTML = html;
}
/* 搜索过滤 */
function filterCopyList(){
var input = document.getElementById('copySearchInput');
var kw = input ? input.value : '';
copySelectedCode = '';
updateCopyConfirmBtn();
renderCopyList(kw);
}
/* 选中食材 */
function selectCopyItem(code){
copySelectedCode = code;
renderCopyList(document.getElementById('copySearchInput').value);
updateCopyConfirmBtn();
}
/* 更新确认按钮状态 */
function updateCopyConfirmBtn(){
var btn = document.getElementById('btnConfirmCopy');
if(btn) btn.disabled = !copySelectedCode;
}
/* 打开弹窗 */
function openCopyDialog(){
buildCopyFoodList();
copySelectedCode = '';
var overlay = document.getElementById('copyOverlay');
var input = document.getElementById('copySearchInput');
if(input) input.value = '';
if(overlay) overlay.classList.add('show');
renderCopyList('');
updateCopyConfirmBtn();
}
/* 关闭弹窗 */
function closeCopyDialog(){
var overlay = document.getElementById('copyOverlay');
if(overlay) overlay.classList.remove('show');
}
/* 确认复制:填充 formData(排除食物名称、食物分类、别名、食材图片) */
function confirmCopy(){
if(!copySelectedCode) return;
var food = EDIT_FOODS[copySelectedCode];
if(!food) return;
// 保存当前食物名称、别名、分类和图片,复制后还原
var savedName = formData['foodName'] || '';
var savedAlias = formData['foodAlias'] || '';
var savedCatL1 = selectedCategory.l1;
var savedCatL2 = selectedCategory.l2;
var savedImage = imageData;
// 基本信息(排除名称、别名、分类、图片)
formData['edible'] = food.edible || '';
formData['waterRate'] = food.waterRate || '';
formData['dataSource'] = food.dataSource === 0 ? '0' : String(food.dataSource || '0');
// 净菜类型
vegTypes = food.vegTypes ? food.vegTypes.slice() : [];
// 适配人群
crowdList = food.crowd ? food.crowd.map(function(c){ return {name:c.name, level:c.level}; }) : [];
// 营养成分
if(food.nutri){
var n = food.nutri;
for(var key in n){
if(key === 'mg'){
formData['mg_el'] = String(n[key]);
} else {
formData[key] = String(n[key]);
}
}
}
// 味道档案
if(food.taste){
formData['tasteMain'] = food.taste.main || '';
formData['tasteSub'] = food.taste.sub || '';
formData['tasteIntensity'] = food.taste.intensity || '';
formData['tasteDesc'] = food.taste.desc || '';
}
// 口感档案
if(food.texture){
formData['textureMain'] = food.texture.main || '';
formData['chewDiff'] = food.texture.chew || '';
formData['digestDiff'] = food.texture.digest || '';
formData['textureDesc'] = food.texture.desc || '';
formData['cookAdvice'] = food.texture.cook || '';
}
// 色泽档案
if(food.color){
formData['colorMain'] = food.color.main || '';
formData['colorGrade'] = food.color.grade || '';
formData['colorDesc'] = food.color.desc || '';
formData['colorCause'] = food.color.cause || '';
}
// 还原名称、别名、分类、图片
formData['foodName'] = savedName;
formData['foodAlias'] = savedAlias;
selectedCategory.l1 = savedCatL1;
selectedCategory.l2 = savedCatL2;
imageData = savedImage;
// 刷新当前步骤的显示
renderStep();
// 刷新级联选择器回显
if(selectedCategory.l1){
var trig = document.getElementById('addCascadeTrigger');
if(trig){
trig.textContent = selectedCategory.l1 + (selectedCategory.l2 ? ' / ' + selectedCategory.l2 : '');
trig.classList.add('has-value');
}
}
closeCopyDialog();
}
// 初始化
initEditMode();
renderStep();
+226 -1
View File
@@ -142,6 +142,41 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
.food-item__source{font-size:10px;padding:1px 6px;border-radius:8px;flex-shrink:0;white-space:nowrap;}
.food-item__source--sys{background:var(--primary-light);color:var(--primary);}
.food-item__source--manual{background:#FFF7E6;color:var(--warning);}
/* 弹窗 */
.dialog-mask{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.45);z-index:200;align-items:center;justify-content:center;}
.dialog-mask.open{display:flex;}
.dialog{background:#fff;border-radius:var(--radius-lg);box-shadow:var(--shadow-md);max-height:80vh;display:flex;flex-direction:column;}
.dialog__header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px 16px;border-bottom:1px solid var(--border);flex-shrink:0;}
.dialog__title{font-size:var(--font-lg);font-weight:600;}
.dialog__close{width:32px;height:32px;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:var(--radius-sm);color:var(--text-placeholder);font-size:16px;}
.dialog__close:hover{background:#F5F5F5;color:var(--text-primary);}
.dialog__body{flex:1;overflow-y:auto;padding:16px 24px;}
.dialog__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 24px;border-top:1px solid var(--border);flex-shrink:0;}
.form-input,.form-select{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);outline:none;background:#fff;}
.form-input:focus,.form-select:focus{border-color:var(--primary);}
.form-select{appearance:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 10px center;padding-right:28px;cursor:pointer;}
/* 分类管理表格 */
.cat-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
.cat-table th{background:#FAFAFA;padding:10px 12px;text-align:left;color:var(--text-secondary);font-weight:500;border-bottom:1px solid var(--border);white-space:nowrap;}
.cat-table td{padding:8px 12px;border-bottom:1px solid #F5F5F5;color:var(--text-primary);}
.cat-table tbody tr:hover{background:#F8FBFF;}
.cat-toggle{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border:1px solid var(--border);border-radius:3px;cursor:pointer;margin-right:6px;font-size:12px;line-height:1;background:#fff;color:var(--text-secondary);flex-shrink:0;vertical-align:middle;}
.cat-toggle:hover{border-color:var(--primary);color:var(--primary);}
.cat-toggle.leaf{visibility:hidden;}
.cat-row-l2 .cat-name{padding-left:28px!important;}
.cat-table td.td-actions{white-space:nowrap;}
.cat-table td.td-actions .link{margin:0 4px;}
/* 状态胶囊开关 */
.status-toggle{display:inline-flex;align-items:center;gap:6px;cursor:pointer;user-select:none;}
.status-toggle__switch{width:36px;height:20px;background:#D9D9D9;border-radius:10px;position:relative;transition:background .2s;flex-shrink:0;}
.status-toggle__switch::after{content:'';position:absolute;left:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px rgba(0,0,0,0.15);}
.status-toggle.active .status-toggle__switch{background:var(--success);}
.status-toggle.active .status-toggle__switch::after{transform:translateX(16px);}
.status-toggle__label{font-size:var(--font-xs);color:var(--text-placeholder);}
.status-toggle.active .status-toggle__label{color:var(--success);}
</style>
</head>
<body>
@@ -240,7 +275,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei
</div>
<div class="panel-left__actions">
<a href="din-ingredient-add.html" class="btn btn-primary"> 新增食物</a>
<a class="btn btn-primary">🖥 分类管理</a>
<button class="btn btn-default" onclick="openCategoryDialog()">📂 食材分类管理</button>
</div>
<div class="food-list">
<div class="food-item active" onclick="selectFood(0)">
@@ -585,6 +620,196 @@ renderDetail();
// 侧边栏子组展开/收起
function toggleSubgroup(header){header.parentElement.classList.toggle('open');}
/* ========== 食材分类管理 ========== */
var nextCatId = 100;
var ingredientCats = [
{id:1, name:'谷薯类', code:'01', level:1, sort:1, status:'启用', parentId:null},
{id:2, name:'稻米', code:'0101', level:2, sort:1, status:'启用', parentId:1},
{id:3, name:'小麦', code:'0102', level:2, sort:2, status:'启用', parentId:1},
{id:4, name:'玉米', code:'0103', level:2, sort:3, status:'启用', parentId:1},
{id:5, name:'薯类', code:'0104', level:2, sort:4, status:'启用', parentId:1},
{id:6, name:'杂粮', code:'0105', level:2, sort:5, status:'启用', parentId:1},
{id:7, name:'畜禽肉类', code:'02', level:1, sort:2, status:'启用', parentId:null},
{id:8, name:'猪肉', code:'0201', level:2, sort:1, status:'启用', parentId:7},
{id:9, name:'牛肉', code:'0202', level:2, sort:2, status:'启用', parentId:7},
{id:10, name:'羊肉', code:'0203', level:2, sort:3, status:'启用', parentId:7},
{id:11, name:'鸡肉', code:'0204', level:2, sort:4, status:'启用', parentId:7},
{id:12, name:'鸭肉', code:'0205', level:2, sort:5, status:'启用', parentId:7},
{id:13, name:'水产类', code:'03', level:1, sort:3, status:'启用', parentId:null},
{id:14, name:'淡水鱼', code:'0301', level:2, sort:1, status:'启用', parentId:13},
{id:15, name:'海水鱼', code:'0302', level:2, sort:2, status:'启用', parentId:13},
{id:16, name:'虾蟹', code:'0303', level:2, sort:3, status:'启用', parentId:13},
{id:17, name:'贝类', code:'0304', level:2, sort:4, status:'启用', parentId:13},
{id:18, name:'蛋类', code:'04', level:1, sort:4, status:'启用', parentId:null},
{id:19, name:'鸡蛋', code:'0401', level:2, sort:1, status:'启用', parentId:18},
{id:20, name:'鸭蛋', code:'0402', level:2, sort:2, status:'启用', parentId:18},
{id:21, name:'奶及奶制品', code:'05', level:1, sort:5, status:'启用', parentId:null},
{id:22, name:'牛奶', code:'0501', level:2, sort:1, status:'启用', parentId:21},
{id:23, name:'酸奶', code:'0502', level:2, sort:2, status:'启用', parentId:21},
{id:24, name:'豆及豆制品', code:'06', level:1, sort:6, status:'启用', parentId:null},
{id:25, name:'大豆', code:'0601', level:2, sort:1, status:'启用', parentId:24},
{id:26, name:'豆制品', code:'0602', level:2, sort:2, status:'启用', parentId:24},
{id:27, name:'蔬菜类', code:'07', level:1, sort:7, status:'启用', parentId:null},
{id:28, name:'叶菜类', code:'0701', level:2, sort:1, status:'启用', parentId:27},
{id:29, name:'根茎类', code:'0702', level:2, sort:2, status:'启用', parentId:27},
{id:30, name:'瓜果类', code:'0703', level:2, sort:3, status:'启用', parentId:27},
{id:31, name:'菌菇类', code:'0704', level:2, sort:4, status:'启用', parentId:27},
{id:32, name:'水果类', code:'08', level:1, sort:8, status:'启用', parentId:null},
{id:33, name:'坚果种子类', code:'09', level:1, sort:9, status:'启用', parentId:null},
{id:34, name:'油脂类', code:'10', level:1, sort:10, status:'启用', parentId:null},
{id:35, name:'调味品类', code:'11', level:1, sort:11, status:'启用', parentId:null}
];
var expandedCats = {};
var editingCatId = null;
var editingParentId = null;
var MAX_INGREDIENT_LEVEL = 2;
function openCategoryDialog(){
document.getElementById('categoryDialog').classList.add('open');
renderCatTable();
}
function closeCategoryDialog(){
document.getElementById('categoryDialog').classList.remove('open');
}
function toggleCatExpand(id){
expandedCats[id] = !expandedCats[id];
renderCatTable();
}
function getCatChildren(parentId){
return ingredientCats.filter(function(c){return c.parentId===parentId;}).sort(function(a,b){return a.sort-b.sort;});
}
function renderCatTable(){
var tops = ingredientCats.filter(function(c){return c.parentId===null;}).sort(function(a,b){return a.sort-b.sort;});
var html = '';
tops.forEach(function(cat){
html += renderCatRow(cat);
if(expandedCats[cat.id]){
getCatChildren(cat.id).forEach(function(child){
html += renderCatRow(child, true);
});
}
});
document.getElementById('catTableBody').innerHTML = html || '<tr><td colspan="6" style="text-align:center;color:var(--text-placeholder);padding:40px;">暂无分类数据</td></tr>';
}
function renderCatRow(cat, isChild){
var hasChildren = getCatChildren(cat.id).length > 0;
var isExpanded = expandedCats[cat.id];
var toggleHtml = hasChildren
? '<span class="cat-toggle" onclick="event.stopPropagation();toggleCatExpand('+cat.id+')">'+(isExpanded?'':'+')+'</span>'
: '<span class="cat-toggle leaf">+</span>';
var rowClass = isChild ? 'cat-row-l2' : '';
var activeClass = cat.status==='启用'?' active':'';
var statusLabel = cat.status==='启用'?'启用':'禁用';
var statusHtml = '<div class="status-toggle'+activeClass+'" onclick="event.stopPropagation();toggleCatStatus('+cat.id+')"><span class="status-toggle__switch"></span><span class="status-toggle__label">'+statusLabel+'</span></div>';
var canAddChild = cat.level < MAX_INGREDIENT_LEVEL;
var addChildBtn = canAddChild ? '<span class="link" onclick="openAddCat('+cat.id+')">新增下级</span>' : '';
return '<tr class="'+rowClass+'"><td class="cat-name">'+toggleHtml+cat.name+'</td><td>'+cat.code+'</td><td>'+cat.level+'</td><td>'+cat.sort+'</td><td>'+statusHtml+'</td><td class="td-actions"><span class="link" onclick="openEditCat('+cat.id+')">编辑</span><span class="link" style="color:var(--danger);" onclick="deleteCat('+cat.id+')">删除</span>'+addChildBtn+'</td></tr>';
}
function toggleCatStatus(id){
var cat = ingredientCats.find(function(c){return c.id===id;});
if(!cat) return;
cat.status = cat.status==='启用'?'禁用':'启用';
renderCatTable();
}
function openAddCat(parentId){
editingCatId = null;
editingParentId = parentId;
document.getElementById('catFormDialogTitle').textContent = parentId ? '新增下级分类' : '新增一级分类';
document.getElementById('catFormName').value = '';
document.getElementById('catFormCode').value = '';
document.getElementById('catFormSort').value = '1';
document.getElementById('catFormStatus').value = '启用';
document.getElementById('catFormDialog').classList.add('open');
}
function openEditCat(id){
var cat = ingredientCats.find(function(c){return c.id===id;});
if(!cat) return;
editingCatId = id;
editingParentId = cat.parentId;
document.getElementById('catFormDialogTitle').textContent = '编辑分类';
document.getElementById('catFormName').value = cat.name;
document.getElementById('catFormCode').value = cat.code;
document.getElementById('catFormSort').value = cat.sort;
document.getElementById('catFormStatus').value = cat.status;
document.getElementById('catFormDialog').classList.add('open');
}
function closeCatFormDialog(){
document.getElementById('catFormDialog').classList.remove('open');
editingCatId = null;
editingParentId = null;
}
function saveCatForm(){
var name = document.getElementById('catFormName').value.trim();
var code = document.getElementById('catFormCode').value.trim();
var sort = parseInt(document.getElementById('catFormSort').value) || 1;
var status = document.getElementById('catFormStatus').value;
if(!name||!code){alert('请填写分类名称和分类编码');return;}
if(editingCatId){
var cat = ingredientCats.find(function(c){return c.id===editingCatId;});
if(cat){cat.name=name;cat.code=code;cat.sort=sort;cat.status=status;}
} else {
var level = editingParentId ? 2 : 1;
ingredientCats.push({id:nextCatId++,name:name,code:code,level:level,sort:sort,status:status,parentId:editingParentId});
if(editingParentId) expandedCats[editingParentId] = true;
}
closeCatFormDialog();
renderCatTable();
}
function deleteCat(id){
var cat = ingredientCats.find(function(c){return c.id===id;});
if(!cat) return;
if(getCatChildren(id).length > 0){alert('该分类下存在子级分类,请先删除子级分类后再删除。');return;}
if(!confirm('确定要删除分类「'+cat.name+'」吗?删除后不可恢复。')) return;
ingredientCats = ingredientCats.filter(function(c){return c.id!==id;});
renderCatTable();
}
</script>
<!-- 食材分类管理弹窗 -->
<div class="dialog-mask" id="categoryDialog">
<div class="dialog" style="width:820px;">
<div class="dialog__header">
<div class="dialog__title">食材分类管理</div>
<button class="dialog__close" onclick="closeCategoryDialog()"></button>
</div>
<div class="dialog__body">
<!-- 工具栏 -->
<div style="margin-bottom:12px;">
<button class="btn btn-primary" onclick="openAddCat(null)" style="height:30px;font-size:var(--font-xs);"> 新增一级分类</button>
</div>
<!-- 分类表格 -->
<div style="overflow-x:auto;">
<table class="cat-table">
<thead><tr><th style="width:180px;">分类名称</th><th style="width:100px;">分类编码</th><th style="width:60px;">层级</th><th style="width:60px;">排序</th><th style="width:80px;">状态</th><th style="width:180px;">操作</th></tr></thead>
<tbody id="catTableBody"></tbody>
</table>
</div>
</div>
<div class="dialog__footer">
<button class="btn btn-default" onclick="closeCategoryDialog()">关闭</button>
</div>
</div>
</div>
<!-- 分类表单子弹窗 -->
<div class="dialog-mask" id="catFormDialog">
<div class="dialog" style="width:480px;">
<div class="dialog__header">
<div class="dialog__title" id="catFormDialogTitle">新增分类</div>
<button class="dialog__close" onclick="closeCatFormDialog()"></button>
</div>
<div class="dialog__body">
<div class="form-group"><label>分类名称<span class="required">*</span></label><input class="form-input" id="catFormName" placeholder="请输入分类名称"></div>
<div class="form-row">
<div class="form-group"><label>分类编码<span class="required">*</span></label><input class="form-input" id="catFormCode" placeholder="请输入分类编码"></div>
<div class="form-group"><label>排序</label><input class="form-input" id="catFormSort" type="number" value="1"></div>
</div>
<div class="form-group"><label>状态</label><select class="form-select" id="catFormStatus"><option>启用</option><option>禁用</option></select></div>
</div>
<div class="dialog__footer">
<button class="btn btn-default" onclick="closeCatFormDialog()">取消</button>
<button class="btn btn-primary" onclick="saveCatForm()">保存</button>
</div>
</div>
</div>
</body>
</html>
+20
View File
@@ -1,5 +1,25 @@
# 营养用餐管理端 CHANGELOG
## [2026-07-23]
### 后台营养管理
- **`din-canteen-add.html`**(场所管理 · 新增/编辑场所):页面重构为 5 个卡片区(基本信息 / 地址信息 / 营业时段 / 场所图片 / 资质信息)。新增餐次时段配置(早餐/午餐/晚餐/夜宵,每时段可独立设置营业日及起止时间),新增场所图片上传区(Logo/门头照/环境照)与资质证照管理(营业执照/食品经营许可证/法人身份证),完善全字段必填校验。
- **`din-canteen.html`**(场所管理 · 场所列表):列表新增「负责人」「营业时段」列;详情抽屉重构为 4 个分区(基本信息 / 营业时段 / 场所图片 / 资质信息),Mock 数据补齐营业时段及证照信息。
- **`din-ingredient.html`**(食物成分):新增「食材分类管理」弹窗,支持两级树形分类的增删改查,含子级删除校验及 11 个一级分类示例数据。
- **`din-dish-all.html`**(餐品总库):新增「餐品分类管理」弹窗,支持一级分类的增删改查及 8 个分类示例数据。
- **`din-ingredient-add.html`**(食物成分 · 新增/编辑食材):基本信息区新增「从已有食材复制」按钮,弹窗搜索选择已有食材后自动填充全部表单(排除名称/分类/别名/图片),覆盖营养成分/味道/口感/色泽档案全部步骤,编辑模式下排除当前食材自身。
### 营养管理小程序
- **`eat-order.html`**(订餐页):净菜包菜品名称去掉「净菜包」后缀;菜品图标、名称、描述区域点击可跳转至菜品详情页。
- **`eat-order-confirm.html`**(订单确认页 · 净菜包):期望配送时间由 chip 单选改为日期+时段联动下拉面板,左侧日期卡片右侧时段列表;取货方式切换时时间标签文案联动(期望配送时间 ↔ 自提时间)。
- **`me-health.html`**(健康档案):移除「营养目标」分区及其编辑弹窗。
- **`me-home.html`**(我的主页):菜单列表新增「收货地址」入口,指向新增的 `me-address.html`
- **`me-address.html`**(收货地址 · 新增页面):支持地址卡片列表展示、编辑/删除/设为默认操作,含内联编辑表单。
- **`me-order-detail.html`**(订单详情 · 净菜包):按订单状态(待配送/配送中/已完成)差异化渲染——头部徽标文案与配色联动,待配送状态仅展示食材明细+主辅材角色标签,已发货/已完成展示食材来源及溯源按钮;订单进度时间线改为动态渲染,按状态高亮对应节点。
- **`me-orders.html`**(我的订单):净菜包订单卡片跳转链接补充 status 参数,支持按状态进入对应详情。
## [2026-07-14] feat: 净菜包购买链路补齐与健康档案重构
### 后台营养管理