feat: 贴标秤新增发货功能 + 营养秤设置页新增闭餐功能

- 贴标秤:待发货/已发货Tab改造,发货弹窗支持三方配送/自主配送/商家自取
- 营养秤设置页:新增闭餐卡片与处理方式选择弹窗(捐赠/员工福利/废弃/加工再利用)
- 营养秤导航页:修复营养秤卡片缺图标和名称的样式问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhangyan
2026-07-27 11:31:44 +08:00
co-authored by Claude Opus 4.6
parent 91621bc48b
commit 8108f5520e
5 changed files with 487 additions and 23 deletions
@@ -61,11 +61,14 @@
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: flex-start;
height: calc(100% - 44px);
gap: 24px;
padding: 24px;
overflow-y: auto;
scrollbar-width: none;
}
.main-content::-webkit-scrollbar { display: none; }
/* ---- 按钮卡片 ---- */
.settings-card {
@@ -177,6 +180,36 @@
color: #fff;
}
/* ===== 闭餐弹窗 — 处理方式选择 ===== */
.disposal-option-group { display: flex; flex-direction: column; gap: 10px; }
.disposal-option {
display: flex; align-items: center; gap: 12px;
padding: 12px 16px; border-radius: 8px;
border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03);
cursor: pointer; transition: all 0.2s;
}
.disposal-option:hover { border-color: rgba(74,144,217,0.3); background: rgba(74,144,217,0.05); }
.disposal-option.active { border-color: #4a90d9; background: rgba(74,144,217,0.1); }
.disposal-option__radio { width: 16px; height: 16px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.25); flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.disposal-option.active .disposal-option__radio { border-color: #4a90d9; }
.disposal-option.active .disposal-option__radio::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: #4a90d9; }
.disposal-option__icon { font-size: 22px; flex-shrink: 0; }
.disposal-option__label { font-size: 14px; color: rgba(255,255,255,0.85); font-weight: 500; }
.disposal-option__hint { font-size: 11px; color: rgba(255,255,255,0.35); }
/* 确认/取消按钮 */
.modal-actions { display: flex; gap: 12px; margin-top: 0; }
.btn-confirm-danger {
flex: 1; padding: 12px; background: rgba(255,77,79,0.8); color: #fff;
border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s;
}
.btn-confirm-danger:hover { background: #ff4d4f; }
/* 系统状态标识 */
.status-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; margin-right: 4px; }
.status-dot--open { background: #52c41a; }
.status-dot--closed { background: rgba(255,255,255,0.3); }
/* ===== 减重弹窗 — 秤体重量显示 ===== */
.tare-weight-display {
background: rgba(255, 255, 255, 0.03);
@@ -318,6 +351,16 @@
</div>
<span class="settings-card__arrow">&#x203A;</span>
</div>
<!-- 闭餐卡片 -->
<div class="settings-card" id="closeMealCard" onclick="handleCloseMeal()">
<span class="settings-card__icon">&#x1F6D1;</span>
<div class="settings-card__info">
<div class="settings-card__title">闭餐</div>
<div class="settings-card__desc" id="closeMealStatusDesc"><span class="status-dot status-dot--open"></span>当前状态:开餐中</div>
</div>
<span class="settings-card__arrow">&#x203A;</span>
</div>
</div>
<!-- ===== 容器减重弹窗(一次性) ===== -->
@@ -364,6 +407,63 @@
</div>
</div>
<!-- ===== 闭餐弹窗 ===== -->
<div class="modal-overlay" id="closeMealModal">
<div class="modal-container">
<div class="modal-title">&#x1F6D1; 闭餐 - 选择处理方式</div>
<div style="font-size: 14px; color: rgba(255,255,255,0.45); margin-bottom: 20px;">
请选择剩余餐品的处理方式:
</div>
<div class="disposal-option-group" id="disposalGroup">
<div class="disposal-option active" data-disposal="donate" onclick="selectDisposal('donate', this)">
<div class="disposal-option__radio"></div>
<div class="disposal-option__icon">&#x1F91D;</div>
<div>
<div class="disposal-option__label">捐赠</div>
<div class="disposal-option__hint">将剩余餐品捐赠给慈善机构或社区</div>
</div>
</div>
<div class="disposal-option" data-disposal="welfare" onclick="selectDisposal('welfare', this)">
<div class="disposal-option__radio"></div>
<div class="disposal-option__icon">&#x1F465;</div>
<div>
<div class="disposal-option__label">员工福利</div>
<div class="disposal-option__hint">作为员工福利免费发放</div>
</div>
</div>
<div class="disposal-option" data-disposal="discard" onclick="selectDisposal('discard', this)">
<div class="disposal-option__radio"></div>
<div class="disposal-option__icon">&#x1F5D1;&#xFE0F;</div>
<div>
<div class="disposal-option__label">废弃</div>
<div class="disposal-option__hint">按食品安全规范进行废弃处理</div>
</div>
</div>
<div class="disposal-option" data-disposal="reprocess" onclick="selectDisposal('reprocess', this)">
<div class="disposal-option__radio"></div>
<div class="disposal-option__icon">&#x267B;&#xFE0F;</div>
<div>
<div class="disposal-option__label">加工再利用</div>
<div class="disposal-option__hint">回收加工为其他用途的原料</div>
</div>
</div>
</div>
<!-- 备注 -->
<div style="margin-top: 18px;">
<label style="font-size: 13px; color: rgba(255,255,255,0.45); display: block; margin-bottom: 8px;">备注</label>
<textarea id="closeMealRemark" placeholder="请输入备注信息(选填)" style="width: 100%; height: 72px; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.04); color: #fff; font-size: 13px; font-family: inherit; outline: none; resize: vertical; transition: border-color 0.2s;" onfocus="this.style.borderColor='#4a90d9'" onblur="this.style.borderColor='rgba(255,255,255,0.1)'"></textarea>
</div>
<div class="modal-actions" style="margin-top: 24px;">
<button class="btn-cancel" onclick="closeCloseMealModal()" style="flex: 1;">取消</button>
<button class="btn-confirm-danger" onclick="confirmCloseMeal()">确认闭餐</button>
</div>
</div>
</div>
<!-- ===== Toast 提示 ===== -->
<div class="toast" id="toast"></div>
@@ -521,6 +621,81 @@
spoonTareModal.addEventListener('click', function(e) {
if (e.target === spoonTareModal) closeSpoonTareModal();
});
/* ===== 闭餐逻辑 ===== */
var isMealOpen = true;
var selectedDisposal = 'donate';
var closeMealModal = document.getElementById('closeMealModal');
function updateCloseMealStatus() {
var descEl = document.getElementById('closeMealStatusDesc');
if (isMealOpen) {
descEl.innerHTML = '<span class="status-dot status-dot--open"></span>当前状态:开餐中';
} else {
descEl.innerHTML = '<span class="status-dot status-dot--closed"></span>当前状态:已闭餐';
}
}
function handleCloseMeal() {
if (!isMealOpen) {
showToast('未开餐设备不能执行闭餐操作');
return;
}
selectedDisposal = 'donate';
document.querySelectorAll('#disposalGroup .disposal-option').forEach(function(opt) { opt.classList.remove('active'); });
var firstOpt = document.querySelector('#disposalGroup .disposal-option[data-disposal="donate"]');
if (firstOpt) firstOpt.classList.add('active');
closeMealModal.classList.add('active');
}
function closeCloseMealModal() {
closeMealModal.classList.remove('active');
document.getElementById('closeMealRemark').value = '';
}
function selectDisposal(method, el) {
selectedDisposal = method;
document.querySelectorAll('#disposalGroup .disposal-option').forEach(function(opt) { opt.classList.remove('active'); });
el.classList.add('active');
}
function confirmCloseMeal() {
var disposalLabels = { donate: '捐赠', welfare: '员工福利', discard: '废弃', reprocess: '加工再利用' };
var label = disposalLabels[selectedDisposal] || selectedDisposal;
var remark = document.getElementById('closeMealRemark').value.trim();
isMealOpen = false;
updateCloseMealStatus();
closeCloseMealModal();
var msg = '闭餐成功 - 处理方式:' + label;
if (remark) msg += ',备注:' + remark;
showToast(msg);
}
closeMealModal.addEventListener('click', function(e) {
if (e.target === closeMealModal) closeCloseMealModal();
});
/* 开始开餐时恢复开餐状态 */
(function() {
var startCard = document.querySelector('.settings-card[href="01-meal-start.html"]');
if (startCard) {
startCard.addEventListener('click', function() {
isMealOpen = true;
updateCloseMealStatus();
/* 用 localStorage 跨页面传递状态 */
localStorage.setItem('mealOpen', 'true');
});
}
})();
/* 页面加载时从 localStorage 恢复状态 */
(function() {
var saved = localStorage.getItem('mealOpen');
if (saved === 'false') {
isMealOpen = false;
updateCloseMealStatus();
}
})();
</script>
</body>
@@ -1,6 +1,17 @@
# 餐线营养秤 - 变更记录
## [2026-06-17] refactor: 全局返回路径改为三级优先级机制
## [2026-07-27] feat: 设置页新增闭餐功能
### 新增
- **闭餐卡片**`04-settings.html`):设置页底部新增「闭餐」卡片,显示当前开餐/闭餐状态
- **闭餐弹窗**:点击闭餐卡片弹出处理方式选择弹窗,支持四种方式:捐赠、员工福利、废弃、加工再利用,支持备注文本框
- **状态联动**:点击"开始开餐"卡片自动恢复开餐状态,状态通过 localStorage 持久化
- **滚动支持**:设置页 `main-content` 新增 `overflow-y: auto`,卡片过多时可滚动
### 调整
- `04-settings.html``main-content``justify-content``center` 改为 `flex-start`
--- [2026-06-17] refactor: 全局返回路径改为三级优先级机制
### 调整
- **PRD 1.4 节**:返回按钮实现由 `history.back()` 改为三级优先级机制:
@@ -97,6 +97,44 @@
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
.toast--warn { background: rgba(250,173,22,0.92); }
/* 弹窗遮罩 */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 999; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.25s, visibility 0.25s; }
.modal-overlay.show { opacity: 1; visibility: visible; }
.modal-overlay.hidden { display: none; }
/* 弹窗 */
.modal { background: #1a2b4a; border-radius: 12px; border: 1px solid rgba(255,255,255,0.08); width: 480px; max-height: 560px; overflow-y: auto; box-shadow: 0 16px 48px rgba(0,0,0,0.5); }
.modal__header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.06); }
.modal__title { font-size: 15px; font-weight: 600; color: #fff; }
.modal__close { width: 28px; height: 28px; border-radius: 6px; background: rgba(255,255,255,0.06); border: none; color: rgba(255,255,255,0.5); cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.modal__close:hover { background: rgba(255,77,79,0.15); color: #ff4d4f; }
.modal__body { padding: 20px; }
.modal__footer { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 20px; border-top: 1px solid rgba(255,255,255,0.06); }
/* 配送方式选择 */
.ship-method-group { display: flex; gap: 10px; margin-bottom: 18px; }
.ship-method-option { flex: 1; padding: 12px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.03); text-align: center; cursor: pointer; transition: all 0.2s; }
.ship-method-option:hover { border-color: rgba(74,144,217,0.3); background: rgba(74,144,217,0.05); }
.ship-method-option.active { border-color: #4a90d9; background: rgba(74,144,217,0.1); }
.ship-method-option__icon { font-size: 24px; margin-bottom: 6px; }
.ship-method-option__label { font-size: 12px; color: rgba(255,255,255,0.7); font-weight: 500; }
.ship-method-option.active .ship-method-option__label { color: #4a90d9; }
/* 表单字段 */
.form-group { margin-bottom: 14px; }
.form-group__label { font-size: 12px; color: rgba(255,255,255,0.5); margin-bottom: 6px; display: block; }
.form-group__select, .form-group__input { width: 100%; padding: 9px 12px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: rgba(255,255,255,0.04); color: #fff; font-size: 13px; font-family: inherit; outline: none; transition: border-color 0.2s; }
.form-group__select:focus, .form-group__input:focus { border-color: #4a90d9; }
.form-group__select option { background: #1a2b4a; color: #fff; }
/* 按钮 */
.btn { padding: 8px 20px; border-radius: 6px; font-size: 13px; font-weight: 500; border: none; cursor: pointer; transition: all 0.2s; font-family: inherit; }
.btn--primary { background: #4a90d9; color: #fff; }
.btn--primary:hover { background: #5ba0e9; }
.btn--cancel { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6); border: 1px solid rgba(255,255,255,0.1); }
.btn--cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
</style>
</head>
<body>
@@ -115,7 +153,8 @@
<!-- Tab 切换 -->
<div class="tabs">
<div class="tab active" data-filter="pending" onclick="switchTab('pending', this)">待包装 <span class="tab__count" id="pendingCount">0</span></div>
<div class="tab" data-filter="done" onclick="switchTab('done', this)">已包装 <span class="tab__count" id="doneCount">0</span></div>
<div class="tab" data-filter="done" onclick="switchTab('done', this)">待发货 <span class="tab__count" id="doneCount">0</span></div>
<div class="tab" data-filter="shipped" onclick="switchTab('shipped', this)">已发货 <span class="tab__count" id="shippedCount">0</span></div>
</div>
<!-- 任务列表 -->
<div class="task-list-container" id="taskListContainer"></div>
@@ -124,6 +163,70 @@
<div class="toast" id="toast"></div>
</div>
<!-- 发货弹窗 -->
<div class="modal-overlay hidden" id="shipModalOverlay">
<div class="modal">
<div class="modal__header">
<span class="modal__title" id="shipModalTitle">选择配送方式</span>
<button class="modal__close" onclick="closeShipModal()">&times;</button>
</div>
<div class="modal__body">
<!-- 配送方式选择 -->
<div class="ship-method-group" id="shipMethodGroup">
<div class="ship-method-option active" data-method="thirdParty" onclick="selectShipMethod('thirdParty', this)">
<div class="ship-method-option__icon">&#x1F69A;</div>
<div class="ship-method-option__label">三方配送</div>
</div>
<div class="ship-method-option" data-method="selfDelivery" onclick="selectShipMethod('selfDelivery', this)">
<div class="ship-method-option__icon">&#x1F6CE;</div>
<div class="ship-method-option__label">自主配送</div>
</div>
<div class="ship-method-option" data-method="pickup" onclick="selectShipMethod('pickup', this)">
<div class="ship-method-option__icon">&#x1F3EA;</div>
<div class="ship-method-option__label">商家自取</div>
</div>
</div>
<!-- 三方配送字段 -->
<div id="thirdPartyFields">
<div class="form-group">
<label class="form-group__label">配送公司名称</label>
<select class="form-group__select" id="shipCompany">
<option value="">请选择配送公司</option>
<option value="顺丰速运">顺丰速运</option>
<option value="京东物流">京东物流</option>
<option value="中通快递">中通快递</option>
<option value="圆通速递">圆通速递</option>
<option value="韵达快递">韵达快递</option>
</select>
</div>
<div class="form-group">
<label class="form-group__label">配送单号</label>
<input class="form-group__input" type="text" id="shipTrackingNo" placeholder="请输入配送单号" />
</div>
</div>
<!-- 自主配送字段 -->
<div id="selfDeliveryFields" style="display:none;">
<div class="form-group">
<label class="form-group__label">配送员名称</label>
<select class="form-group__select" id="shipPerson">
<option value="">请选择配送员</option>
<option value="张达">张达</option>
<option value="李强">李强</option>
<option value="王峰">王峰</option>
<option value="赵明">赵明</option>
</select>
</div>
</div>
</div>
<div class="modal__footer">
<button class="btn btn--cancel" onclick="closeShipModal()">取消</button>
<button class="btn btn--primary" id="confirmShipBtn" onclick="confirmShip()">确定发货</button>
</div>
</div>
</div>
<script>
/* 获取 URL 参数 */
function getUrlParam(name) {
@@ -179,7 +282,7 @@
}
];
/* 已包装订单(包装历史 */
/* 待发货订单(包装完成,等待发货 */
var mockDoneData = [
{
orderNo: 'PO-20260624-001',
@@ -189,6 +292,7 @@
labeled: true,
labeledTime: '2026-06-24 10:15',
operator: '刘九',
shipped: false,
mealPacks: [
{ type: '净菜包', packName: '黄瓜', packSize: 3, packSpec: '500g', ingredients: [{ name: '黄瓜', weightPerPack: 500, weighed: 1500 }] },
{ type: '餐品净菜包', packName: '凉拌黄瓜套餐', packSize: 2, packSpec: '盒装', ingredients: [{ name: '凉拌黄瓜', weightPerPack: 600, weighed: 1200 }] }
@@ -202,6 +306,7 @@
labeled: true,
labeledTime: '2026-06-24 13:45',
operator: '陈十',
shipped: false,
mealPacks: [
{ type: '净菜包', packName: '芹菜', packSize: 2, packSpec: '500g', ingredients: [{ name: '芹菜', weightPerPack: 500, weighed: 1000 }] },
{ type: '餐品净菜包', packName: '芹菜炒肉套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '芹菜炒肉', weightPerPack: 800, weighed: 2400 }] }
@@ -209,6 +314,28 @@
}
];
/* 已发货订单 */
var mockShippedData = [
{
orderNo: 'PO-20260623-001',
orderType: 'internal',
expectedArrival: '2026-06-23 09:00',
taskNo: 'L-S01',
labeled: true,
labeledTime: '2026-06-23 08:30',
operator: '王五',
shipped: true,
shippedTime: '2026-06-24 11:00',
shippingMethod: '三方配送',
shippingCompany: '顺丰速运',
shippingNo: 'SF1234567890',
mealPacks: [
{ type: '净菜包', packName: '西红柿', packSize: 5, packSpec: '500g', ingredients: [{ name: '西红柿', weightPerPack: 500, weighed: 2500 }] },
{ type: '餐品净菜包', packName: '番茄牛腩套餐', packSize: 3, packSpec: '大份800g', ingredients: [{ name: '番茄牛腩', weightPerPack: 800, weighed: 2400 }] }
]
}
];
/* 判断订单是否所有包都已完成 */
function isOrderDone(order) {
return order.labeled === true;
@@ -227,15 +354,20 @@
var container = document.getElementById('taskListContainer');
container.innerHTML = '';
var pendingCount = 0;
var doneCount = 0;
var dataList;
if (currentFilter === 'pending') {
dataList = mockData;
} else if (currentFilter === 'done') {
dataList = mockDoneData;
} else {
dataList = mockShippedData;
}
var dataList = currentFilter === 'pending' ? mockData : mockDoneData;
if (dataList.length === 0) {
container.innerHTML = '<div style="text-align:center;color:rgba(255,255,255,0.3);padding:60px 0;font-size:14px;">暂无数据</div>';
}
dataList.forEach(function(order) {
if (currentFilter === 'pending') pendingCount++;
else doneCount++;
var taskCard = document.createElement('div');
taskCard.className = 'task-card';
@@ -253,10 +385,27 @@
headerHtml += '<span class="task-card__arrival">&#x23F0; 任务截止时间:<span class="task-card__arrival__time">' + order.expectedArrival + '</span></span>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--pending">待包装</span></span>';
headerHtml += '<button class="task-card__label-btn" onclick="handleLabel(\'' + order.taskNo + '\',\'' + order.orderNo + '\')">去包装</button>';
} else {
headerHtml += '<span class="task-card__arrival">&#x23F0; 包装完成时间:<span class="task-card__arrival__time">' + order.labeledTime + '</span></span>';
} else if (currentFilter === 'done') {
headerHtml += '<span class="task-card__arrival">&#x1F4E6; 包装完成时间:<span class="task-card__arrival__time">' + order.labeledTime + '</span></span>';
headerHtml += '<span class="task-card__operator">&#x1F464; 操作人:<span class="task-card__operator__name">' + order.operator + '</span></span>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--done">已包装</span></span>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--pending">待发货</span></span>';
headerHtml += '<button class="task-card__label-btn" onclick="handleShip(\'' + order.orderNo + '\')">发货</button>';
} else {
headerHtml += '<span class="task-card__arrival">&#x1F4E6; 包装完成时间:<span class="task-card__arrival__time">' + order.labeledTime + '</span></span>';
headerHtml += '<span class="task-card__operator">&#x1F464; 操作人:<span class="task-card__operator__name">' + order.operator + '</span></span>';
headerHtml += '<span class="task-card__status"><span class="status-badge status-badge--done">已发货</span></span>';
headerHtml += '<span style="font-size:10px;color:rgba(255,255,255,0.3)">发货时间:' + order.shippedTime + '</span>';
/* 配送信息 */
if (order.shippingMethod) {
var shippingInfo = order.shippingMethod;
if (order.shippingMethod === '三方配送') {
shippingInfo += ' | ' + order.shippingCompany + ' | ' + order.shippingNo;
} else if (order.shippingMethod === '自主配送') {
shippingInfo += ' | ' + order.shippingPerson;
}
headerHtml += '<span style="font-size:10px;color:rgba(255,255,255,0.3);margin-left:8px;">' + shippingInfo + '</span>';
}
}
header.innerHTML = headerHtml;
taskCard.appendChild(header);
@@ -267,7 +416,7 @@
tableHead.innerHTML = '<div>名称</div><div style="text-align:center">净菜/餐品</div><div style="text-align:center">包装规格</div><div style="text-align:right">应称重量</div><div style="text-align:right">已称重量</div><div style="text-align:center">状态</div><div style="text-align:center">操作</div>';
taskCard.appendChild(tableHead);
/* 表格体:按单份拆分展示 */
/* 表格体 */
var tableBody = document.createElement('div');
tableBody.className = 'item-table__body';
@@ -276,20 +425,26 @@
? { text: '待包装', cls: 'status-tag--wait' }
: { text: '已打印', cls: 'status-tag--done' };
var ingWeight = pack.ingredients[0] ? pack.ingredients[0].weightPerPack : 0;
/* 从包装规格中提取应称重量(如"500g"→500),按整包计算 */
var specMatch = pack.packSpec && pack.packSpec.match(/(\d+)g/);
var perDue = specMatch ? parseInt(specMatch[1]) : ingWeight;
var totalDue = perDue * pack.packSize;
var totalWeighed = currentFilter === 'done' ? totalDue : (pack.weighed || 0);
var weightClass = currentFilter === 'done' ? 'parent-weight--done' : '';
var totalWeighed = (currentFilter === 'done' || currentFilter === 'shipped') ? totalDue : (pack.weighed || 0);
var weightClass = (currentFilter === 'done' || currentFilter === 'shipped') ? 'parent-weight--done' : '';
var parentRow = document.createElement('div');
parentRow.className = 'item-parent-row';
var statusCell = '<div style="text-align:center"><span class="status-tag ' + packStatus.cls + '">' + packStatus.text + '</span></div>';
var actionCell = currentFilter === 'done'
? '<div style="text-align:center"><button style="font-size:10px;padding:2px 8px;background:rgba(74,144,217,0.15);border:1px solid rgba(74,144,217,0.3);border-radius:4px;color:#4a90d9;cursor:pointer;font-family:inherit;" onclick="event.stopPropagation();handleReprint(\'' + order.orderNo + '\',\'' + pack.packName + '\')">重新打印</button></div>'
: '<div style="text-align:center;font-size:10px;color:rgba(255,255,255,0.2)">—</div>';
/* 操作列 */
var actionCell;
if (currentFilter === 'done') {
actionCell = '<div style="text-align:center"><button style="font-size:10px;padding:2px 8px;background:rgba(74,144,217,0.15);border:1px solid rgba(74,144,217,0.3);border-radius:4px;color:#4a90d9;cursor:pointer;font-family:inherit;" onclick="event.stopPropagation();handleReprint(\'' + order.orderNo + '\',\'' + pack.packName + '\')">重新打印</button></div>';
} else if (currentFilter === 'shipped') {
actionCell = '<div style="text-align:center;font-size:10px;color:rgba(255,255,255,0.2)">—</div>';
} else {
actionCell = '<div style="text-align:center;font-size:10px;color:rgba(255,255,255,0.2)">—</div>';
}
parentRow.innerHTML =
'<div class="parent-name"><div class="parent-icon">&#x1F4E6;</div><span class="parent-name__text">' + pack.packName + '</span></div>' +
'<div style="text-align:center;font-size:11px;color:' + (pack.type === '餐品净菜包' ? '#faad16' : '#4a90d9') + '">' + pack.type + '</div>' +
@@ -308,6 +463,7 @@
/* 更新 Tab 计数 */
document.getElementById('pendingCount').textContent = mockData.length;
document.getElementById('doneCount').textContent = mockDoneData.length;
document.getElementById('shippedCount').textContent = mockShippedData.length;
}
/* 切换 Tab */
@@ -330,6 +486,117 @@
showToast('重新打印: ' + orderNo + ' ' + packName, 2000, 'success');
}
/* ========== 发货相关 ========== */
var currentShipOrder = null;
var currentShipMethod = 'thirdParty';
/* 打开发货弹窗 */
function handleShip(orderNo) {
currentShipOrder = orderNo;
/* 重置为默认:三方配送 */
currentShipMethod = 'thirdParty';
resetShipMethodUI();
document.getElementById('shipModalOverlay').classList.remove('hidden');
requestAnimationFrame(function() {
document.getElementById('shipModalOverlay').classList.add('show');
});
}
/* 关闭弹窗 */
function closeShipModal() {
document.getElementById('shipModalOverlay').classList.remove('show');
setTimeout(function() {
document.getElementById('shipModalOverlay').classList.add('hidden');
/* 清空表单 */
document.getElementById('shipCompany').value = '';
document.getElementById('shipTrackingNo').value = '';
document.getElementById('shipPerson').value = '';
}, 250);
}
/* 选择配送方式 */
function selectShipMethod(method, el) {
currentShipMethod = method;
/* 更新选项激活状态 */
document.querySelectorAll('.ship-method-option').forEach(function(opt) { opt.classList.remove('active'); });
el.classList.add('active');
/* 切换字段区域 */
document.getElementById('thirdPartyFields').style.display = method === 'thirdParty' ? 'block' : 'none';
document.getElementById('selfDeliveryFields').style.display = method === 'selfDelivery' ? 'block' : 'none';
}
/* 重置配送方式 UI */
function resetShipMethodUI() {
document.querySelectorAll('.ship-method-option').forEach(function(opt) { opt.classList.remove('active'); });
var firstOpt = document.querySelector('.ship-method-option[data-method="thirdParty"]');
if (firstOpt) firstOpt.classList.add('active');
document.getElementById('thirdPartyFields').style.display = 'block';
document.getElementById('selfDeliveryFields').style.display = 'none';
}
/* 确认发货 */
function confirmShip() {
/* 找到订单在待发货列表中的索引 */
var orderIndex = -1;
for (var i = 0; i < mockDoneData.length; i++) {
if (mockDoneData[i].orderNo === currentShipOrder) {
orderIndex = i;
break;
}
}
if (orderIndex === -1) {
showToast('订单不存在', 2000, 'warn');
return;
}
var order = mockDoneData[orderIndex];
var now = new Date();
var shippedTime = now.getFullYear() + '-' +
String(now.getMonth() + 1).padStart(2, '0') + '-' +
String(now.getDate()).padStart(2, '0') + ' ' +
String(now.getHours()).padStart(2, '0') + ':' +
String(now.getMinutes()).padStart(2, '0');
/* 根据配送方式设置字段 */
if (currentShipMethod === 'thirdParty') {
var company = document.getElementById('shipCompany').value;
var trackingNo = document.getElementById('shipTrackingNo').value.trim();
if (!company) {
showToast('请选择配送公司', 2000, 'warn');
return;
}
if (!trackingNo) {
showToast('请输入配送单号', 2000, 'warn');
return;
}
order.shippingMethod = '三方配送';
order.shippingCompany = company;
order.shippingNo = trackingNo;
} else if (currentShipMethod === 'selfDelivery') {
var person = document.getElementById('shipPerson').value;
if (!person) {
showToast('请选择配送员', 2000, 'warn');
return;
}
order.shippingMethod = '自主配送';
order.shippingPerson = person;
} else {
order.shippingMethod = '商家自取';
}
order.shipped = true;
order.shippedTime = shippedTime;
/* 从待发货列表移除,添加到已发货列表 */
mockDoneData.splice(orderIndex, 1);
mockShippedData.unshift(order);
/* 刷新列表 */
renderTaskList();
closeShipModal();
showToast('发货成功', 2000);
}
/* 退出登录 */
function handleLogout() {
window.location.href = '00-login.html';
@@ -1,5 +1,16 @@
# 净菜包装贴标秤 CHANGELOG
## 2026-07-27 | 新增发货功能 + Tab 改造
### 新增
- `01-orders.html` — "已包装" Tab 改名为"待发货",新增"已发货" Tab
- `01-orders.html` — 待发货订单卡片头部新增「发货」按钮
- `01-orders.html` — 发货弹窗:支持三种配送方式(三方配送/自主配送/商家自取)
- 三方配送:选择配送公司 + 输入配送单号
- 自主配送:选择配送员
- 商家自取:无需额外字段
- `01-orders.html` — 已发货 Tab 纯展示订单信息及配送详情,无操作按钮
## 2026-07-06 | 移除份数列 + 打印按钮
### 修改
+2 -2
View File
@@ -253,8 +253,8 @@
</a>
<a class="card" href="canteen-nutrition-scale/index.html">
<span class="card__status card__status--done">已设计</span>
<div class="card__icon">&#x1F958;</div>
<div class="card__name">餐线营养秤</div>
<div class="card__icon">&#x2696;&#xFE0F;</div>
<div class="card__name">营养秤</div>
<div class="card__desc">开餐、补餐、取餐、餐品展示、设置、采样</div>
</a>
<a class="card" href="stall-nutrition-scale/index.html">