feat: 配送H5订单详情取货方式拆分(自助/到店取货) + 营养管理端餐线管理重构与餐盘柜餐盘编码
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d20f3913f6
commit
c8322529cb
@@ -66,23 +66,78 @@ tr:hover td{background:#FAFAFA;}
|
||||
.pagination{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 20px;font-size:var(--font-xs);color:var(--text-secondary);}
|
||||
.page-btn{width:28px;height:28px;border:1px solid var(--border);border-radius:var(--radius-sm);background:#fff;cursor:pointer;font-size:var(--font-xs);}
|
||||
.page-btn.active{background:var(--primary);color:#fff;border-color:var(--primary);}
|
||||
/* 标签 */
|
||||
.tag{display:inline-block;padding:2px 8px;border-radius:10px;font-size:var(--font-xs);}
|
||||
.tag-blue{background:#E6F7FF;color:#1890FF;}
|
||||
.tag-green{background:#F6FFED;color:#52C41A;}
|
||||
.tag-orange{background:#FFF7E6;color:#FA8C16;}
|
||||
.tag-gray{background:#F5F5F5;color:#999;}
|
||||
/* 胶囊开关 */
|
||||
.pill-switch{display:inline-block;min-width:52px;height:22px;line-height:22px;padding:0 10px;border-radius:11px;font-size:var(--font-xs);text-align:center;cursor:pointer;user-select:none;transition:all .2s;color:#fff;}
|
||||
.pill-switch.on{background:var(--success);}
|
||||
.pill-switch.off{background:#BFBFBF;}
|
||||
/* 设备数量(可点击数字) */
|
||||
.dev-count{color:var(--primary);cursor:pointer;font-weight:600;text-decoration:none;border-bottom:1px dashed var(--primary);}
|
||||
.dev-count:hover{color:#40a9ff;}
|
||||
/* 弹窗 */
|
||||
.modal-mask{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:1000;align-items:center;justify-content:center;}
|
||||
.modal-mask.show{display:flex;}
|
||||
.modal{background:#fff;border-radius:var(--radius-md);width:480px;box-shadow:var(--shadow-md);}
|
||||
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);}
|
||||
.modal{background:#fff;border-radius:var(--radius-md);width:480px;box-shadow:var(--shadow-md);display:flex;flex-direction:column;max-height:88vh;}
|
||||
.modal.modal-lg{width:900px;}
|
||||
.modal__header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);flex-shrink:0;}
|
||||
.modal__title{font-size:var(--font-base);font-weight:600;}
|
||||
.modal__close{font-size:22px;cursor:pointer;color:var(--text-secondary);line-height:1;}.modal__close:hover{color:var(--text-primary);}
|
||||
.modal__body{padding:24px;}
|
||||
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);}
|
||||
.modal__body{padding:24px;overflow-y:auto;flex:1;}
|
||||
.modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--border);flex-shrink:0;}
|
||||
.form-row{margin-bottom:16px;}
|
||||
.form-row:last-child{margin-bottom:0;}
|
||||
.form-label{display:block;font-size:var(--font-sm);color:var(--text-primary);margin-bottom:6px;}
|
||||
.form-label em{color:var(--danger);font-style:normal;}
|
||||
.form-input{width:100%;height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 12px;font-size:var(--font-sm);}
|
||||
.form-input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.1);}
|
||||
.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);background:#fff;}
|
||||
.form-input:focus,.form-select:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 2px rgba(24,144,255,0.1);}
|
||||
.form-row-inline{display:flex;gap:16px;align-items:flex-start;}
|
||||
.form-row-inline .form-row{flex:1;}
|
||||
.form-row-inline .form-row.status-row{flex:0 0 140px;}
|
||||
/* 分区标题 */
|
||||
.form-section-title{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin:20px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}
|
||||
.form-section-title:first-child{margin-top:0;}
|
||||
.form-section-title .btn-link{font-size:var(--font-sm);font-weight:400;border:1px dashed var(--primary);color:var(--primary);background:transparent;padding:4px 12px;border-radius:var(--radius-sm);cursor:pointer;}
|
||||
.form-section-title .btn-link:hover{background:var(--primary-light);}
|
||||
/* 批量设置条 */
|
||||
.batch-bar{display:flex;align-items:center;gap:8px;background:#F8FBFF;border:1px dashed #91D5FF;border-radius:var(--radius-sm);padding:10px 14px;margin-bottom:12px;font-size:var(--font-sm);}
|
||||
.batch-bar label{color:var(--text-secondary);white-space:nowrap;}
|
||||
.batch-bar .form-select{width:auto;min-width:120px;}
|
||||
.batch-bar .form-input{width:110px;}
|
||||
/* 收费模式矩阵 */
|
||||
.charge-table-wrap{overflow-x:auto;}
|
||||
.charge-table{min-width:760px;border-collapse:collapse;}
|
||||
.charge-table th{background:#FAFAFA;font-weight:600;text-align:center;padding:8px;border:1px solid var(--border);font-size:var(--font-sm);}
|
||||
.charge-table td{padding:6px;border:1px solid #f0f0f0;text-align:center;}
|
||||
.charge-table .ct-week{background:#FAFAFA;font-weight:500;color:var(--text-secondary);width:52px;}
|
||||
.cm-mode{width:100px;height:28px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-xs);background:#fff;outline:none;}
|
||||
.cm-mode:focus{border-color:var(--primary);}
|
||||
.cm-price{width:64px;height:28px;border:1px solid var(--border);border-radius:var(--radius-sm);font-size:var(--font-xs);padding:0 6px;text-align:center;outline:none;margin-left:4px;}
|
||||
.cm-price:focus{border-color:var(--primary);}
|
||||
.cm-price:disabled{background:#F5F5F5;color:#999;cursor:not-allowed;}
|
||||
.cm-unit{font-size:var(--font-xs);color:var(--text-secondary);margin-left:2px;}
|
||||
/* 弹窗内表格 */
|
||||
.modal-table{width:100%;border-collapse:collapse;font-size:var(--font-sm);}
|
||||
.modal-table th{background:#FAFAFA;font-weight:600;padding:9px 12px;text-align:left;border-bottom:1px solid var(--border);white-space:nowrap;color:var(--text-secondary);}
|
||||
.modal-table td{padding:9px 12px;border-bottom:1px solid #f0f0f0;color:var(--text-primary);white-space:nowrap;}
|
||||
.modal-table tr:last-child td{border-bottom:none;}
|
||||
.modal-table tr:hover td{background:#FAFAFA;}
|
||||
/* 弹窗内分页 */
|
||||
.modal-pagination{display:flex;align-items:center;justify-content:flex-end;gap:8px;margin-top:12px;font-size:var(--font-xs);color:var(--text-secondary);}
|
||||
/* 详情回显字段 */
|
||||
.detail-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px 24px;}
|
||||
.detail-field{display:flex;flex-direction:column;gap:4px;}
|
||||
.detail-field__label{font-size:var(--font-xs);color:var(--text-secondary);}
|
||||
.detail-field__value{font-size:var(--font-sm);color:var(--text-primary);font-weight:500;}
|
||||
/* 只读收费矩阵 */
|
||||
.charge-read td{font-size:var(--font-xs);}
|
||||
.cr-byMeal{color:#1890FF;}
|
||||
.cr-byWeight{color:#FA8C16;}
|
||||
.cr-free{color:#999;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -210,53 +265,95 @@ tr:hover td{background:#FAFAFA;}
|
||||
|
||||
<!-- 餐线列表 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary" onclick="openModal('add')">➕ 新增餐线</button>
|
||||
<button class="btn btn-primary" onclick="openLineModal(null)">➕ 新增餐线</button>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<div class="table-card__header">
|
||||
<div class="table-card__title">餐线列表</div>
|
||||
<span style="font-size:var(--font-xs);color:var(--text-secondary);">共 3 条</span>
|
||||
<span style="font-size:var(--font-xs);color:var(--text-secondary);">共 <span id="lineCount">0</span> 条</span>
|
||||
</div>
|
||||
<div style="overflow-x:auto;">
|
||||
<table>
|
||||
<thead><tr><th>餐线名称</th><th>设备数量</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>A餐线(自选餐)</td>
|
||||
<td>6 台</td>
|
||||
<td><span class="link" onclick="openModal('edit','A餐线(自选餐)')">编辑</span><span class="link link-danger">删除</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>B餐线(营养点餐)</td>
|
||||
<td>4 台</td>
|
||||
<td><span class="link" onclick="openModal('edit','B餐线(营养点餐)')">编辑</span><span class="link link-danger">删除</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C餐线(清真)</td>
|
||||
<td>2 台</td>
|
||||
<td><span class="link" onclick="openModal('edit','C餐线(清真)')">编辑</span><span class="link link-danger">删除</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead><tr><th>餐线名称</th><th>结算方式</th><th>设备数量</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody id="lineTbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination"><span>显示 3 条,共 3 条</span><button class="page-btn active">1</button></div>
|
||||
<div class="pagination"><span id="linePager">显示 0 条,共 0 条</span><button class="page-btn active">1</button></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 新增/编辑餐线弹窗 -->
|
||||
<div class="modal-mask" id="lineModal" onclick="closeModal(event)">
|
||||
<div class="modal" onclick="event.stopPropagation()">
|
||||
<div class="modal__header"><span class="modal__title" id="modalTitle">新增餐线</span><span class="modal__close" onclick="closeModal()">×</span></div>
|
||||
<!-- 餐线设置 / 新增餐线 弹窗 -->
|
||||
<div class="modal-mask" id="lineModal" onclick="closeLineModal(event)">
|
||||
<div class="modal modal-lg" onclick="event.stopPropagation()">
|
||||
<div class="modal__header"><span class="modal__title" id="lineModalTitle">新增餐线</span><span class="modal__close" onclick="closeLineModal()">×</span></div>
|
||||
<div class="modal__body">
|
||||
<div class="form-row">
|
||||
<label class="form-label"><em>*</em> 餐线名称</label>
|
||||
<input class="form-input" id="lineName" placeholder="请输入餐线名称">
|
||||
<!-- 基础信息 -->
|
||||
<div class="form-section-title">基础信息</div>
|
||||
<div class="form-row-inline">
|
||||
<div class="form-row">
|
||||
<label class="form-label"><em>*</em> 餐线名称</label>
|
||||
<input class="form-input" id="lineName" placeholder="请输入餐线名称">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-label"><em>*</em> 结算方式</label>
|
||||
<select class="form-select" id="lineSettle">
|
||||
<option value="auto">自动结算</option>
|
||||
<option value="manual">手动结算</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row status-row">
|
||||
<label class="form-label">状态</label>
|
||||
<span class="pill-switch on" id="lineStatusSwitch" onclick="toggleLineStatusSwitch()">启用</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 收费模式 -->
|
||||
<div class="form-section-title">
|
||||
<span>收费模式</span>
|
||||
<button class="btn-link" onclick="toggleBatchBar()">⚡ 批量设置</button>
|
||||
</div>
|
||||
<div class="batch-bar" id="batchBar" style="display:none;">
|
||||
<label>收费模式</label>
|
||||
<select class="form-select" id="batchMode" onchange="onBatchModeChange()">
|
||||
<option value="byMeal">按餐计费</option>
|
||||
<option value="byWeight">称重计费</option>
|
||||
<option value="free">免费</option>
|
||||
</select>
|
||||
<label id="batchPriceLabel">计费金额</label>
|
||||
<input class="form-input" id="batchPrice" placeholder="金额(元)">
|
||||
<button class="btn btn-primary" onclick="applyBatch()">应用到全部餐次</button>
|
||||
<button class="btn btn-default" onclick="toggleBatchBar()">收起</button>
|
||||
</div>
|
||||
<div class="charge-table-wrap">
|
||||
<table class="charge-table" id="chargeTable"></table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal__footer">
|
||||
<button class="btn btn-default" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary">确定</button>
|
||||
<button class="btn btn-default" onclick="closeLineModal()">取消</button>
|
||||
<button class="btn btn-primary" onclick="confirmLine()">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设备列表概况弹窗 -->
|
||||
<div class="modal-mask" id="deviceModal" onclick="closeDeviceModal(event)">
|
||||
<div class="modal" style="width:640px;" onclick="event.stopPropagation()">
|
||||
<div class="modal__header"><span class="modal__title" id="deviceModalTitle">设备列表</span><span class="modal__close" onclick="closeDeviceModal()">×</span></div>
|
||||
<div class="modal__body" id="deviceModalBody"></div>
|
||||
<div class="modal__footer">
|
||||
<button class="btn btn-default" onclick="closeDeviceModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 餐线详情弹窗 -->
|
||||
<div class="modal-mask" id="detailModal" onclick="closeDetailModal(event)">
|
||||
<div class="modal modal-lg" onclick="event.stopPropagation()">
|
||||
<div class="modal__header"><span class="modal__title" id="detailModalTitle">餐线详情</span><span class="modal__close" onclick="closeDetailModal()">×</span></div>
|
||||
<div class="modal__body" id="detailModalBody"></div>
|
||||
<div class="modal__footer">
|
||||
<button class="btn btn-default" onclick="closeDetailModal()">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,23 +362,339 @@ tr:hover td{background:#FAFAFA;}
|
||||
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 openModal(mode, name){
|
||||
var modal=document.getElementById('lineModal');
|
||||
var title=document.getElementById('modalTitle');
|
||||
var input=document.getElementById('lineName');
|
||||
if(mode==='edit'){
|
||||
title.textContent='编辑餐线';
|
||||
input.value=name||'';
|
||||
/* ========== 常量 ========== */
|
||||
var MEALS=['早餐','午餐','晚餐','加餐'];
|
||||
var WEEKDAYS=['周一','周二','周三','周四','周五','周六','周日'];
|
||||
var MODE_OPTIONS=[
|
||||
{value:'byMeal',label:'按餐计费'},
|
||||
{value:'byWeight',label:'称重计费'},
|
||||
{value:'free',label:'免费'}
|
||||
];
|
||||
/* 生成收费模式矩阵:7天×4餐次,同一模式 */
|
||||
function genCharging(mode, prices){
|
||||
return WEEKDAYS.map(function(){
|
||||
return MEALS.map(function(_, mi){
|
||||
return {mode:mode, price: mode==='byMeal' ? (prices&&prices[mi]||'') : ''};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ========== 餐线数据 ========== */
|
||||
var lines=[
|
||||
{
|
||||
id:'L1', name:'A餐线(自选餐)', settleType:'auto', status:'on',
|
||||
charging: genCharging('byMeal', [8,15,15,6]),
|
||||
devices:[
|
||||
{type:'营养秤', code:'CL-A01', name:'餐线营养秤1号', days:326, online:true},
|
||||
{type:'营养秤', code:'CL-A02', name:'餐线营养秤2号', days:326, online:true},
|
||||
{type:'营养秤', code:'CL-A03', name:'餐线营养秤3号', days:210, online:false},
|
||||
{type:'结算终端', code:'CL-A04', name:'结算终端1号', days:326, online:true},
|
||||
{type:'结算终端', code:'CL-A05', name:'结算终端2号', days:300, online:true},
|
||||
{type:'餐盘机', code:'CL-A06', name:'智能餐盘机1号', days:120, online:true}
|
||||
]
|
||||
},
|
||||
{
|
||||
id:'L2', name:'B餐线(营养点餐)', settleType:'auto', status:'on',
|
||||
charging: genCharging('byWeight'),
|
||||
devices:[
|
||||
{type:'营养秤', code:'CL-B01', name:'餐线营养秤4号', days:400, online:true},
|
||||
{type:'营养秤', code:'CL-B02', name:'餐线营养秤5号', days:400, online:true},
|
||||
{type:'结算终端', code:'CL-B03', name:'结算终端3号', days:400, online:false},
|
||||
{type:'餐盘机', code:'CL-B04', name:'智能餐盘机2号', days:180, online:true}
|
||||
]
|
||||
},
|
||||
{
|
||||
id:'L3', name:'C餐线(清真)', settleType:'manual', status:'off',
|
||||
charging: genCharging('free'),
|
||||
devices:[
|
||||
{type:'营养秤', code:'CL-C01', name:'餐线营养秤6号', days:90, online:true},
|
||||
{type:'结算终端', code:'CL-C02', name:'结算终端4号', days:90, online:false}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/* ========== 列表渲染 ========== */
|
||||
function renderLines(){
|
||||
document.getElementById('lineCount').textContent=lines.length;
|
||||
document.getElementById('linePager').textContent='显示 '+lines.length+' 条,共 '+lines.length+' 条';
|
||||
document.getElementById('lineTbody').innerHTML=lines.map(function(l){
|
||||
var settle=l.settleType==='auto'?'自动结算':'手动结算';
|
||||
var settleCls=l.settleType==='auto'?'tag-green':'tag-gray';
|
||||
var status=l.status==='on'
|
||||
? '<span class="pill-switch on" onclick="toggleLineStatus(\''+l.id+'\')">启用</span>'
|
||||
: '<span class="pill-switch off" onclick="toggleLineStatus(\''+l.id+'\')">停用</span>';
|
||||
var devCount='<a class="dev-count" onclick="openDeviceModal(\''+l.id+'\')">'+l.devices.length+'</a>';
|
||||
return '<tr>'+
|
||||
'<td>'+l.name+'</td>'+
|
||||
'<td><span class="tag '+settleCls+'">'+settle+'</span></td>'+
|
||||
'<td>'+devCount+'</td>'+
|
||||
'<td>'+status+'</td>'+
|
||||
'<td><a class="link" onclick="openLineModal(\''+l.id+'\')">餐线设置</a><a class="link" onclick="openLineDetail(\''+l.id+'\')">详情</a><a class="link link-danger" onclick="deleteLine(\''+l.id+'\')">删除</a></td>'+
|
||||
'</tr>';
|
||||
}).join('');
|
||||
}
|
||||
function toggleLineStatus(id){
|
||||
var l=lines.find(function(x){return x.id===id;});
|
||||
if(!l) return;
|
||||
l.status=l.status==='on'?'off':'on';
|
||||
renderLines();
|
||||
}
|
||||
function deleteLine(id){
|
||||
var l=lines.find(function(x){return x.id===id;});
|
||||
if(!l) return;
|
||||
if(!confirm('确认删除餐线「'+l.name+'」?')) return;
|
||||
lines=lines.filter(function(x){return x.id!==id;});
|
||||
renderLines();
|
||||
}
|
||||
|
||||
/* ========== 餐线设置弹窗 ========== */
|
||||
var editingLineId=null;
|
||||
var tempCharging=null;
|
||||
var tempStatus='on';
|
||||
|
||||
function openLineModal(id){
|
||||
editingLineId=id;
|
||||
var title=document.getElementById('lineModalTitle');
|
||||
var nameInput=document.getElementById('lineName');
|
||||
var settle=document.getElementById('lineSettle');
|
||||
document.getElementById('batchBar').style.display='none';
|
||||
document.getElementById('batchMode').value='byMeal';
|
||||
document.getElementById('batchPrice').value='';
|
||||
onBatchModeChange();
|
||||
if(id){
|
||||
var l=lines.find(function(x){return x.id===id;});
|
||||
if(!l) return;
|
||||
title.textContent='餐线设置';
|
||||
nameInput.value=l.name;
|
||||
settle.value=l.settleType;
|
||||
tempStatus=l.status;
|
||||
tempCharging=JSON.parse(JSON.stringify(l.charging));
|
||||
}else{
|
||||
title.textContent='新增餐线';
|
||||
input.value='';
|
||||
nameInput.value='';
|
||||
settle.value='auto';
|
||||
tempStatus='on';
|
||||
tempCharging=genCharging('free');
|
||||
}
|
||||
modal.classList.add('show');
|
||||
renderStatusSwitch();
|
||||
renderChargeTable();
|
||||
document.getElementById('lineModal').classList.add('show');
|
||||
}
|
||||
function closeModal(e){
|
||||
function closeLineModal(e){
|
||||
if(e&&e.target!==e.currentTarget)return;
|
||||
document.getElementById('lineModal').classList.remove('show');
|
||||
}
|
||||
function renderStatusSwitch(){
|
||||
var sw=document.getElementById('lineStatusSwitch');
|
||||
sw.classList.toggle('on',tempStatus==='on');
|
||||
sw.classList.toggle('off',tempStatus!=='on');
|
||||
sw.textContent=tempStatus==='on'?'启用':'停用';
|
||||
}
|
||||
function toggleLineStatusSwitch(){
|
||||
tempStatus=tempStatus==='on'?'off':'on';
|
||||
renderStatusSwitch();
|
||||
}
|
||||
|
||||
/* ========== 收费模式矩阵 ========== */
|
||||
function renderChargeTable(){
|
||||
var html='<thead><tr><th>星期</th>';
|
||||
MEALS.forEach(function(m){html+='<th>'+m+'</th>';});
|
||||
html+='</tr></thead><tbody>';
|
||||
for(var w=0;w<7;w++){
|
||||
html+='<tr><td class="ct-week">'+WEEKDAYS[w]+'</td>';
|
||||
for(var m=0;m<4;m++){
|
||||
var cell=tempCharging[w][m];
|
||||
var opts=MODE_OPTIONS.map(function(o){
|
||||
return '<option value="'+o.value+'"'+(cell.mode===o.value?' selected':'')+'>'+o.label+'</option>';
|
||||
}).join('');
|
||||
var isMeal=cell.mode==='byMeal';
|
||||
html+='<td>'+
|
||||
'<select class="cm-mode" data-w="'+w+'" data-m="'+m+'" onchange="onModeChange(this)">'+opts+'</select>'+
|
||||
'<input class="cm-price" data-w="'+w+'" data-m="'+m+'" value="'+(cell.price||'')+'" placeholder="'+(isMeal?'金额':'-')+'" '+(isMeal?'':'disabled')+' onchange="onPriceChange(this)">'+
|
||||
'<span class="cm-unit" '+(isMeal?'':'style="display:none"')+'>元</span>'+
|
||||
'</td>';
|
||||
}
|
||||
html+='</tr>';
|
||||
}
|
||||
html+='</tbody>';
|
||||
document.getElementById('chargeTable').innerHTML=html;
|
||||
}
|
||||
function onModeChange(sel){
|
||||
var w=parseInt(sel.dataset.w,10), m=parseInt(sel.dataset.m,10);
|
||||
var mode=sel.value;
|
||||
tempCharging[w][m].mode=mode;
|
||||
if(mode!=='byMeal'){ tempCharging[w][m].price=''; }
|
||||
/* 刷新该格金额框禁用态、占位符与单位显隐 */
|
||||
var row=document.querySelectorAll('#chargeTable tbody tr')[w];
|
||||
var cell=row.querySelectorAll('td')[m+1];
|
||||
var priceInput=cell.querySelector('.cm-price');
|
||||
var unit=cell.querySelector('.cm-unit');
|
||||
var isMeal=(mode==='byMeal');
|
||||
priceInput.disabled=!isMeal;
|
||||
priceInput.placeholder=isMeal?'金额':'-';
|
||||
priceInput.value=tempCharging[w][m].price||'';
|
||||
unit.style.display=isMeal?'':'none';
|
||||
}
|
||||
function onPriceChange(inp){
|
||||
var w=parseInt(inp.dataset.w,10), m=parseInt(inp.dataset.m,10);
|
||||
tempCharging[w][m].price=inp.value;
|
||||
}
|
||||
/* 批量设置 */
|
||||
function toggleBatchBar(){
|
||||
var bar=document.getElementById('batchBar');
|
||||
bar.style.display=bar.style.display==='none'?'flex':'none';
|
||||
}
|
||||
function onBatchModeChange(){
|
||||
var mode=document.getElementById('batchMode').value;
|
||||
var show=(mode==='byMeal');
|
||||
document.getElementById('batchPriceLabel').style.display=show?'':'none';
|
||||
document.getElementById('batchPrice').style.display=show?'':'none';
|
||||
if(!show) document.getElementById('batchPrice').value='';
|
||||
}
|
||||
function applyBatch(){
|
||||
var mode=document.getElementById('batchMode').value;
|
||||
var price=document.getElementById('batchPrice').value.trim();
|
||||
if(mode==='byMeal' && !price){ alert('按餐计费请填写计费金额'); return; }
|
||||
for(var w=0;w<7;w++){
|
||||
for(var m=0;m<4;m++){
|
||||
tempCharging[w][m].mode=mode;
|
||||
tempCharging[w][m].price=(mode==='byMeal'?price:'');
|
||||
}
|
||||
}
|
||||
renderChargeTable();
|
||||
}
|
||||
/* 确认保存 */
|
||||
function confirmLine(){
|
||||
var name=document.getElementById('lineName').value.trim();
|
||||
var settle=document.getElementById('lineSettle').value;
|
||||
if(!name){ alert('请输入餐线名称'); return; }
|
||||
for(var w=0;w<7;w++){
|
||||
for(var m=0;m<4;m++){
|
||||
var c=tempCharging[w][m];
|
||||
if(c.mode==='byMeal' && !String(c.price).trim()){ alert('请为「'+WEEKDAYS[w]+' '+MEALS[m]+'」的按餐计费填写计费金额'); return; }
|
||||
}
|
||||
}
|
||||
if(editingLineId){
|
||||
var l=lines.find(function(x){return x.id===editingLineId;});
|
||||
if(l){
|
||||
l.name=name; l.settleType=settle; l.status=tempStatus;
|
||||
l.charging=JSON.parse(JSON.stringify(tempCharging));
|
||||
}
|
||||
}else{
|
||||
lines.push({
|
||||
id:'L'+Date.now(),
|
||||
name:name, settleType:settle, status:tempStatus,
|
||||
charging:JSON.parse(JSON.stringify(tempCharging)),
|
||||
devices:[]
|
||||
});
|
||||
}
|
||||
closeLineModal();
|
||||
renderLines();
|
||||
}
|
||||
|
||||
/* ========== 设备列表概况弹窗 ========== */
|
||||
function openDeviceModal(id){
|
||||
var l=lines.find(function(x){return x.id===id;});
|
||||
if(!l) return;
|
||||
document.getElementById('deviceModalTitle').textContent='设备列表 — '+l.name;
|
||||
var body=document.getElementById('deviceModalBody');
|
||||
if(l.devices.length===0){
|
||||
body.innerHTML='<div style="text-align:center;color:var(--text-placeholder);padding:24px 0;">该餐线暂无关联设备</div>';
|
||||
}else{
|
||||
var rows=l.devices.map(function(d){
|
||||
var onlineTag=d.online?'<span class="tag tag-green">在线</span>':'<span class="tag tag-gray">离线</span>';
|
||||
return '<tr><td>'+d.type+'</td><td>'+d.code+'</td><td>'+d.name+'</td><td>'+d.days+' 天</td><td>'+onlineTag+'</td></tr>';
|
||||
}).join('');
|
||||
body.innerHTML='<table class="modal-table">'+
|
||||
'<thead><tr><th>设备类型</th><th>编号</th><th>设备名称</th><th>运行天数</th><th>在线状态</th></tr></thead>'+
|
||||
'<tbody>'+rows+'</tbody>'+
|
||||
'</table>';
|
||||
}
|
||||
document.getElementById('deviceModal').classList.add('show');
|
||||
}
|
||||
function closeDeviceModal(e){
|
||||
if(e&&e.target!==e.currentTarget)return;
|
||||
document.getElementById('deviceModal').classList.remove('show');
|
||||
}
|
||||
|
||||
/* ========== 餐线详情弹窗 ========== */
|
||||
var detailLineId=null;
|
||||
var detailPage=1;
|
||||
var DETAIL_PAGE_SIZE=5;
|
||||
function openLineDetail(id){
|
||||
var l=lines.find(function(x){return x.id===id;});
|
||||
if(!l) return;
|
||||
detailLineId=id;
|
||||
detailPage=1;
|
||||
document.getElementById('detailModalTitle').textContent='餐线详情 — '+l.name;
|
||||
renderDetail();
|
||||
document.getElementById('detailModal').classList.add('show');
|
||||
}
|
||||
function closeDetailModal(e){
|
||||
if(e&&e.target!==e.currentTarget)return;
|
||||
document.getElementById('detailModal').classList.remove('show');
|
||||
}
|
||||
function renderDetail(){
|
||||
var l=lines.find(function(x){return x.id===detailLineId;});
|
||||
if(!l) return;
|
||||
var settle=l.settleType==='auto'?'自动结算':'手动结算';
|
||||
var statusTag=l.status==='on'?'<span class="tag tag-green">启用</span>':'<span class="tag tag-gray">停用</span>';
|
||||
/* 基础信息 */
|
||||
var html='<div class="form-section-title">基础信息</div>'+
|
||||
'<div class="detail-grid">'+
|
||||
'<div class="detail-field"><span class="detail-field__label">餐线名称</span><span class="detail-field__value">'+l.name+'</span></div>'+
|
||||
'<div class="detail-field"><span class="detail-field__label">结算方式</span><span class="detail-field__value">'+settle+'</span></div>'+
|
||||
'<div class="detail-field"><span class="detail-field__label">状态</span><span class="detail-field__value">'+statusTag+'</span></div>'+
|
||||
'</div>';
|
||||
/* 收费模式只读回显 */
|
||||
html+='<div class="form-section-title">收费模式</div>';
|
||||
html+='<div class="charge-table-wrap"><table class="charge-table charge-read"><thead><tr><th>星期</th>';
|
||||
MEALS.forEach(function(m){html+='<th>'+m+'</th>';});
|
||||
html+='</tr></thead><tbody>';
|
||||
for(var w=0;w<7;w++){
|
||||
html+='<tr><td class="ct-week">'+WEEKDAYS[w]+'</td>';
|
||||
for(var m=0;m<4;m++){
|
||||
var c=l.charging[w][m];
|
||||
var txt=c.mode==='byMeal'
|
||||
? '<span class="cr-byMeal">按餐计费 '+c.price+' 元</span>'
|
||||
: (c.mode==='byWeight'?'<span class="cr-byWeight">称重计费</span>':'<span class="cr-free">免费</span>');
|
||||
html+='<td>'+txt+'</td>';
|
||||
}
|
||||
html+='</tr>';
|
||||
}
|
||||
html+='</tbody></table></div>';
|
||||
/* 设备列表(带分页) */
|
||||
html+='<div class="form-section-title">关联设备</div>';
|
||||
var devices=l.devices;
|
||||
if(devices.length===0){
|
||||
html+='<div style="text-align:center;color:var(--text-placeholder);padding:16px 0;">暂无关联设备</div>';
|
||||
}else{
|
||||
var totalPages=Math.max(1,Math.ceil(devices.length/DETAIL_PAGE_SIZE));
|
||||
if(detailPage>totalPages) detailPage=totalPages;
|
||||
var start=(detailPage-1)*DETAIL_PAGE_SIZE;
|
||||
var pageDevices=devices.slice(start,start+DETAIL_PAGE_SIZE);
|
||||
var rows=pageDevices.map(function(d){
|
||||
var onlineTag=d.online?'<span class="tag tag-green">在线</span>':'<span class="tag tag-gray">离线</span>';
|
||||
return '<tr><td>'+d.type+'</td><td>'+d.code+'</td><td>'+d.name+'</td><td>'+d.days+' 天</td><td>'+onlineTag+'</td></tr>';
|
||||
}).join('');
|
||||
html+='<table class="modal-table">'+
|
||||
'<thead><tr><th>设备类型</th><th>编号</th><th>设备名称</th><th>运行天数</th><th>在线状态</th></tr></thead>'+
|
||||
'<tbody>'+rows+'</tbody></table>';
|
||||
var pageBtns='';
|
||||
for(var i=1;i<=totalPages;i++){
|
||||
pageBtns+='<button class="page-btn'+(i===detailPage?' active':'')+'" onclick="detailGoPage('+i+')">'+i+'</button>';
|
||||
}
|
||||
html+='<div class="modal-pagination"><span>显示 '+pageDevices.length+' 条,共 '+devices.length+' 条</span>'+pageBtns+'</div>';
|
||||
}
|
||||
document.getElementById('detailModalBody').innerHTML=html;
|
||||
}
|
||||
function detailGoPage(p){
|
||||
detailPage=p;
|
||||
renderDetail();
|
||||
}
|
||||
|
||||
/* 初始渲染 */
|
||||
renderLines();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -192,6 +192,7 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.ov-slot__name{font-size:var(--font-sm);font-weight:600;color:var(--text-primary);text-align:center;line-height:1.3;}
|
||||
.ov-slot__remain{font-size:var(--font-sm);font-weight:700;color:var(--primary);}
|
||||
.ov-slot__expire{font-size:10px;color:var(--text-placeholder);}
|
||||
.ov-slot__plate{font-size:10px;color:var(--text-secondary);font-family:monospace;text-align:center;line-height:1.3;}
|
||||
.ov-cabinet-legend{display:flex;gap:16px;padding:14px 4px 0;flex-wrap:wrap;}
|
||||
.ov-legend__item{display:flex;align-items:center;gap:4px;font-size:var(--font-xs);color:var(--text-secondary);}
|
||||
.ov-legend__dot{width:10px;height:10px;border-radius:2px;border:1.5px solid;}
|
||||
@@ -254,6 +255,11 @@ tbody tr:hover{background:#F8FBFF;}
|
||||
.member-picker__search{padding:14px 24px 0;flex-shrink:0;}
|
||||
.member-picker__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;}
|
||||
.member-picker__search input:focus{border-color:var(--primary);}
|
||||
.plate-detail__field input{height:36px;border:1px solid var(--border);border-radius:var(--radius-sm);padding:0 10px;font-size:var(--font-sm);outline:none;font-family:monospace;}
|
||||
.plate-detail__field input:focus{border-color:var(--primary);}
|
||||
.plate-assign__search{margin-bottom:12px;}
|
||||
.plate-assign__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;}
|
||||
.plate-assign__search input:focus{border-color:var(--primary);}
|
||||
.member-picker__body{padding:12px 24px 20px;overflow-y:auto;flex:1;}
|
||||
.member-picker__item{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:8px;cursor:pointer;transition:all .15s;}
|
||||
.member-picker__item:hover{border-color:var(--primary);background:#F8FBFF;}
|
||||
@@ -770,16 +776,16 @@ function renderSmartCabinet(){
|
||||
/* ===== 餐盘柜:格口 + 会员绑定状态 ===== */
|
||||
/* 格口 Mock:状态 bound 已绑定 / using 使用中(盘子被取出) / free 未绑定 / disabled 异常停用 */
|
||||
var PLATE_SLOTS=[
|
||||
{id:'P1-01',status:'bound',member:{name:'张*明',level:'黄金会员',levelCls:'',phone:'138****5621',code:'U20250311',dept:'生产部·一车间'},bindTime:'2026-06-01 09:30',lastUse:'2026-08-18 12:05 · 已归还',useCount:128},
|
||||
{id:'P1-02',status:'using',member:{name:'李*华',level:'铂金会员',levelCls:'vip',phone:'139****3387',code:'U20240827',dept:'技术部·平台组'},bindTime:'2026-05-12 10:15',lastUse:'2026-08-18 12:20 · 使用中(已取出)',useCount:203},
|
||||
{id:'P1-03',status:'bound',member:{name:'王*强',level:'普通会员',levelCls:'',phone:'137****8845',code:'U20260702',dept:'后勤部·餐饮科'},bindTime:'2026-07-20 14:00',lastUse:'2026-08-18 11:50 · 已归还',useCount:45},
|
||||
{id:'P1-04',status:'bound',member:{name:'赵*敏',level:'黄金会员',levelCls:'',phone:'136****2210',code:'U20230915',dept:'财务部·核算组'},bindTime:'2026-04-08 09:00',lastUse:'2026-08-18 12:10 · 已归还',useCount:312},
|
||||
{id:'P1-05',status:'using',member:{name:'陈*芳',level:'VIP会员',levelCls:'vip',phone:'135****7732',code:'U20250422',dept:'人事部·招聘组'},bindTime:'2026-06-25 11:20',lastUse:'2026-08-18 12:30 · 使用中(已取出)',useCount:97},
|
||||
{id:'P1-06',status:'bound',member:{name:'刘*洋',level:'普通会员',levelCls:'',phone:'188****4406',code:'U20260801',dept:'生产部·二车间'},bindTime:'2026-08-01 08:40',lastUse:'2026-08-18 11:45 · 已归还',useCount:23},
|
||||
{id:'P1-07',status:'disabled',member:null,bindTime:'—',lastUse:'2026-08-15 09:12 · 格口门锁故障',useCount:0},
|
||||
{id:'P1-08',status:'free',member:null,bindTime:'—',lastUse:'—',useCount:0},
|
||||
{id:'P1-09',status:'free',member:null,bindTime:'—',lastUse:'—',useCount:0},
|
||||
{id:'P1-10',status:'free',member:null,bindTime:'—',lastUse:'—',useCount:0}
|
||||
{id:'P1-01',status:'bound',plateCode:'CP-26001',member:{name:'张*明',level:'黄金会员',levelCls:'',phone:'138****5621',code:'U20250311',dept:'生产部·一车间'},bindTime:'2026-06-01 09:30',lastUse:'2026-08-18 12:05 · 已归还',useCount:128},
|
||||
{id:'P1-02',status:'using',plateCode:'CP-26002',member:{name:'李*华',level:'铂金会员',levelCls:'vip',phone:'139****3387',code:'U20240827',dept:'技术部·平台组'},bindTime:'2026-05-12 10:15',lastUse:'2026-08-18 12:20 · 使用中(已取出)',useCount:203},
|
||||
{id:'P1-03',status:'bound',plateCode:'CP-26003',member:{name:'王*强',level:'普通会员',levelCls:'',phone:'137****8845',code:'U20260702',dept:'后勤部·餐饮科'},bindTime:'2026-07-20 14:00',lastUse:'2026-08-18 11:50 · 已归还',useCount:45},
|
||||
{id:'P1-04',status:'bound',plateCode:'CP-26004',member:{name:'赵*敏',level:'黄金会员',levelCls:'',phone:'136****2210',code:'U20230915',dept:'财务部·核算组'},bindTime:'2026-04-08 09:00',lastUse:'2026-08-18 12:10 · 已归还',useCount:312},
|
||||
{id:'P1-05',status:'using',plateCode:'CP-26005',member:{name:'陈*芳',level:'VIP会员',levelCls:'vip',phone:'135****7732',code:'U20250422',dept:'人事部·招聘组'},bindTime:'2026-06-25 11:20',lastUse:'2026-08-18 12:30 · 使用中(已取出)',useCount:97},
|
||||
{id:'P1-06',status:'bound',plateCode:'CP-26006',member:{name:'刘*洋',level:'普通会员',levelCls:'',phone:'188****4406',code:'U20260801',dept:'生产部·二车间'},bindTime:'2026-08-01 08:40',lastUse:'2026-08-18 11:45 · 已归还',useCount:23},
|
||||
{id:'P1-07',status:'disabled',plateCode:'—',member:null,bindTime:'—',lastUse:'2026-08-15 09:12 · 格口门锁故障',useCount:0},
|
||||
{id:'P1-08',status:'free',plateCode:'—',member:null,bindTime:'—',lastUse:'—',useCount:0},
|
||||
{id:'P1-09',status:'free',plateCode:'—',member:null,bindTime:'—',lastUse:'—',useCount:0},
|
||||
{id:'P1-10',status:'free',plateCode:'—',member:null,bindTime:'—',lastUse:'—',useCount:0}
|
||||
];
|
||||
|
||||
/* 可分配会员 Mock(原型演示) */
|
||||
@@ -809,10 +815,12 @@ function renderPlateCabinet(){
|
||||
const memberHtml=s.member
|
||||
?`<div class="ov-slot__name">${s.member.name}</div><div class="ov-slot__level ${s.member.levelCls}">${s.member.level}</div>`
|
||||
:`<div class="ov-slot__name" style="color:var(--text-placeholder)">停用</div>`;
|
||||
const plateHtml=s.member?`<div class="ov-slot__plate">餐盘 ${s.plateCode}</div>`:'';
|
||||
return `<div class="ov-slot ${meta.cls}" onclick="showPlateSlot(${i})">
|
||||
${meta.badge?`<div class="ov-slot__badge ${meta.badge}"></div>`:''}
|
||||
<div class="ov-slot__id">${s.id}</div>
|
||||
${memberHtml}
|
||||
${plateHtml}
|
||||
<div class="ov-slot__expire">${meta.text}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
@@ -841,6 +849,7 @@ function showPlateSlot(idx){
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">使用状态</span><span class="plate-detail__value">${statusTag}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">所属柜</span><span class="plate-detail__value">1号餐盘柜</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">格口</span><span class="plate-detail__value">${s.id}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">餐盘编码</span><span class="plate-detail__value" style="font-family:monospace">${s.plateCode}</span></div>
|
||||
<div class="plate-detail__field"><span class="plate-detail__label">绑定时间</span><span class="plate-detail__value">${s.bindTime}</span></div>
|
||||
</div></div>`;
|
||||
if(m){
|
||||
@@ -863,7 +872,7 @@ function showPlateSlot(idx){
|
||||
/* 操作按钮:未绑定可分配;已绑定/使用中可解绑;异常停用不可操作 */
|
||||
let footer='';
|
||||
if(s.status==='free'){
|
||||
footer=`<button class="btn btn-primary" onclick="openPlateAssign(${idx})">格口分配</button>`;
|
||||
footer=`<button class="btn btn-primary" onclick="showPlateAssignInSlot(${idx})">格口分配</button>`;
|
||||
}else if(m){
|
||||
footer=`<button class="btn btn-danger" onclick="openPlateUnbind(${idx})">用户解绑</button>`;
|
||||
}else{
|
||||
@@ -892,34 +901,40 @@ function refreshPlateTab(){
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 格口分配:会员选择层 ===== */
|
||||
/* ===== 格口分配:在格口详情弹窗内原位切换为分配态 ===== */
|
||||
var PLATE_ASSIGN_IDX=-1;
|
||||
var PLATE_ASSIGN_SELECTED=null;
|
||||
function openPlateAssign(idx){
|
||||
function showPlateAssignInSlot(idx){
|
||||
PLATE_ASSIGN_IDX=idx;
|
||||
PLATE_ASSIGN_SELECTED=null;
|
||||
const mask=document.createElement('div');
|
||||
mask.className='member-picker-mask';
|
||||
mask.id='plateAssignMask';
|
||||
mask.innerHTML=`<div class="member-picker">
|
||||
<div class="member-picker__header"><div class="member-picker__title">格口分配 — 选择会员(${PLATE_SLOTS[idx].id})</div><button class="member-picker__close" onclick="closePlateAssign()">×</button></div>
|
||||
<div class="member-picker__search"><input type="text" placeholder="搜索会员姓名 / 手机号" oninput="filterPlateMembers(this.value)"></div>
|
||||
<div class="member-picker__body" id="plateMemberList"></div>
|
||||
<div class="member-picker__footer">
|
||||
<button class="btn btn-default" onclick="closePlateAssign()">取消</button>
|
||||
const mask=document.getElementById('plateSlotMask');
|
||||
if(!mask)return;
|
||||
const s=PLATE_SLOTS[idx];
|
||||
mask.innerHTML=`<div class="slot-his-modal" style="width:640px;height:auto;max-height:78vh">
|
||||
<div class="slot-his-modal__header"><div class="slot-his-modal__title">格口分配 — ${s.id}</div><button class="slot-his-modal__close" onclick="closePlateSlot()">×</button></div>
|
||||
<div class="slot-his-modal__body">
|
||||
<div class="plate-detail__section">
|
||||
<div class="plate-detail__section-title">分配信息</div>
|
||||
<div class="plate-detail__grid">
|
||||
<div class="plate-detail__field" style="grid-column:1/-1">
|
||||
<span class="plate-detail__label">餐盘编码</span>
|
||||
<input type="text" id="plateAssignCode" placeholder="请输入或扫描餐盘编码" oninput="this.value=this.value.toUpperCase()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plate-detail__section">
|
||||
<div class="plate-detail__section-title">选择会员</div>
|
||||
<div class="plate-assign__search"><input type="text" placeholder="搜索会员姓名 / 手机号" oninput="filterPlateMembers(this.value)"></div>
|
||||
<div id="plateMemberList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slot-his-modal__footer" style="display:flex;justify-content:flex-end;gap:12px;">
|
||||
<button class="btn btn-default" onclick="showPlateSlot(${idx})">取消</button>
|
||||
<button class="btn btn-primary" onclick="confirmPlateAssign()">确认分配</button>
|
||||
</div>
|
||||
</div>`;
|
||||
mask.addEventListener('click',e=>{if(e.target===mask)closePlateAssign();});
|
||||
document.body.appendChild(mask);
|
||||
renderPlateMemberList(PLATE_MEMBERS);
|
||||
}
|
||||
function closePlateAssign(){
|
||||
const m=document.getElementById('plateAssignMask');
|
||||
if(!m)return;
|
||||
m.classList.add('closing');
|
||||
setTimeout(()=>m.remove(),200);
|
||||
}
|
||||
function renderPlateMemberList(list){
|
||||
const wrap=document.getElementById('plateMemberList');
|
||||
if(!wrap)return;
|
||||
@@ -954,16 +969,22 @@ function confirmPlateAssign(){
|
||||
showOvToast('请先选择要分配的会员');
|
||||
return;
|
||||
}
|
||||
const codeInput=document.getElementById('plateAssignCode');
|
||||
const plateCode=(codeInput?codeInput.value:'').trim();
|
||||
if(!plateCode){
|
||||
showOvToast('请输入餐盘编码');
|
||||
return;
|
||||
}
|
||||
const s=PLATE_SLOTS[PLATE_ASSIGN_IDX];
|
||||
s.status='bound';
|
||||
s.member=PLATE_ASSIGN_SELECTED;
|
||||
s.plateCode=plateCode;
|
||||
s.bindTime='2026-08-18 16:50';
|
||||
s.lastUse='— · 分配后待首次使用';
|
||||
s.useCount=0;
|
||||
closePlateAssign();
|
||||
closePlateSlot();
|
||||
refreshPlateTab();
|
||||
showOvToast('已将格口 '+s.id+' 分配给会员 '+s.member.name+'(原型演示)');
|
||||
showOvToast('已将格口 '+s.id+' 分配给会员 '+s.member.name+',绑定餐盘 '+s.plateCode+'(原型演示)');
|
||||
}
|
||||
|
||||
/* ===== 用户解绑:二次确认 ===== */
|
||||
@@ -994,6 +1015,7 @@ function confirmPlateUnbind(idx){
|
||||
const s=PLATE_SLOTS[idx];
|
||||
s.status='free';
|
||||
s.member=null;
|
||||
s.plateCode='—';
|
||||
s.bindTime='—';
|
||||
s.lastUse='—';
|
||||
s.useCount=0;
|
||||
|
||||
@@ -1065,14 +1065,14 @@ document.querySelectorAll('.tab-panel')[2].querySelectorAll('tbody tr').forEach(
|
||||
});
|
||||
|
||||
/* ========== 会员权益弹窗(Tab3) ========== */
|
||||
/* 交互说明:点击操作列「会员权益」弹窗展示该会员的权益概要与专属餐盘柜绑定信息。
|
||||
/* 交互说明:点击操作列「会员权益」弹窗展示该会员的权益概要与专属餐盘柜信息。
|
||||
会员权益体系尚在规划中,当前已明确权益为「专属餐盘柜」,此处回显柜子/格口/绑定时间/最后使用状态/累计使用次数。 */
|
||||
// 模拟数据:会员专属餐盘柜绑定信息(未配置的会员显示空态)
|
||||
// 模拟数据:会员专属餐盘柜信息(未配置的会员显示空态)
|
||||
var vipTrayLockerMap = {
|
||||
'张伟': { locker: 'A区-01号柜', cell: '12号格口', bindTime: '2024-01-20 10:32', lastUseTime: '2026-04-28 11:32', lastUseStatus: '已归还', useCount: 1286 },
|
||||
'李娜': { locker: 'A区-01号柜', cell: '08号格口', bindTime: '2024-05-15 09:18', lastUseTime: '2026-04-28 12:08', lastUseStatus: '使用中', useCount: 862 },
|
||||
'赵敏': { locker: 'B区-03号柜', cell: '21号格口', bindTime: '2025-08-20 14:05', lastUseTime: '2026-04-28 11:30', lastUseStatus: '已归还', useCount: 316 },
|
||||
'周明': { locker: 'B区-03号柜', cell: '05号格口', bindTime: '2024-02-18 16:47', lastUseTime: '2026-03-28 12:15', lastUseStatus: '已归还', useCount: 2048 }
|
||||
'张伟': { locker: 'A区-01号柜', cell: '12号格口', dishCode: 'PT-10236', bindTime: '2024-01-20 10:32', lastUseTime: '2026-04-28 11:32', lastUseStatus: '已归还', useCount: 1286 },
|
||||
'李娜': { locker: 'A区-01号柜', cell: '08号格口', dishCode: 'PT-10852', bindTime: '2024-05-15 09:18', lastUseTime: '2026-04-28 12:08', lastUseStatus: '使用中', useCount: 862 },
|
||||
'赵敏': { locker: 'B区-03号柜', cell: '21号格口', dishCode: 'PT-20317', bindTime: '2025-08-20 14:05', lastUseTime: '2026-04-28 11:30', lastUseStatus: '已归还', useCount: 316 },
|
||||
'周明': { locker: 'B区-03号柜', cell: '05号格口', dishCode: 'PT-20094', bindTime: '2024-02-18 16:47', lastUseTime: '2026-03-28 12:15', lastUseStatus: '已归还', useCount: 2048 }
|
||||
};
|
||||
// 等级权益文案映射(与等级配置 Tab 保持一致)
|
||||
var vipLevelBenefitsMap = {
|
||||
@@ -1082,21 +1082,28 @@ var vipLevelBenefitsMap = {
|
||||
'普通': '基础点餐功能、专属餐盘柜'
|
||||
};
|
||||
|
||||
|
||||
function showVipBenefitsModal(name, tds) {
|
||||
var levelText = tds[3].querySelector('.tag').textContent;
|
||||
var cardNo = tds[0].textContent;
|
||||
renderVipBenefitsModal(name, levelText, cardNo);
|
||||
}
|
||||
|
||||
/* 渲染会员权益弹窗 */
|
||||
function renderVipBenefitsModal(name, levelText, cardNo) {
|
||||
var locker = vipTrayLockerMap[name];
|
||||
|
||||
// 专属餐盘柜信息区:已绑定展示明细,未绑定展示空态
|
||||
// 专属餐盘柜信息区:已配置展示明细,未配置展示空态
|
||||
var lockerHtml;
|
||||
if (locker) {
|
||||
var statusTag = locker.lastUseStatus === '使用中' ? 'tag-blue' : 'tag-green';
|
||||
lockerHtml = ''
|
||||
+ '<table class="meal-history-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>' + locker.locker + '</td>'
|
||||
+ ' <td>' + locker.cell + '</td>'
|
||||
+ ' <td>' + (locker.dishCode || '-') + '</td>'
|
||||
+ ' <td>' + locker.bindTime + '</td>'
|
||||
+ ' <td>' + locker.lastUseTime + '</td>'
|
||||
+ ' <td><span class="tag ' + statusTag + '">' + locker.lastUseStatus + '</span></td>'
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# 营养用餐管理端 CHANGELOG
|
||||
|
||||
## [2026-08-19] feat: 餐线管理重构 — 收费模式周设置矩阵 + 设备概况/详情弹窗(`din-canteen-line.html`)
|
||||
|
||||
- 餐线列表新增结算方式(自动/手动)·状态(胶囊开关,点击启用/停用切换)两字段;收费模式因每天餐次可能不同,不在列表展示
|
||||
- 操作列「编辑」改名为「餐线设置」,并新增「详情」入口
|
||||
- 设备数量列改为纯数字,点击弹出「设备列表概况」弹窗(设备类型/编号/设备名称/运行天数/在线状态)
|
||||
- 重构新增餐线与餐线设置共用同一大弹窗:基础信息区(名称/结算方式/状态)+ 收费模式独立分区
|
||||
- 收费模式按周设置:7天×4餐次矩阵(早餐/午餐/晚餐/加餐),每格可独立选择收费模式;按餐计费需填计费金额并带「元」单位,称重计费/免费时金额框显示「-」
|
||||
- 收费模式支持「⚡ 批量设置」:一键将 7×4 全部餐次统一设为同一模式与金额,计费金额输入框仅在「按餐计费」时显示
|
||||
- 新增「餐线详情」弹窗:回显基础信息 + 收费模式只读矩阵 + 关联设备列表(带分页,每页 5 条)
|
||||
- 数据模型改造为 JS 动态渲染,新增/编辑/删除/状态切换/批量设置均联动刷新
|
||||
|
||||
## [2026-08-19] feat: 会员权益弹窗支持手动绑定/解绑专属餐盘柜(`din-user-mgmt.html`)
|
||||
|
||||
- 已绑定会员:专属餐盘柜信息表格下方新增「解绑绑定」按钮,点击弹出二次确认(提示格口释放后果),确认后清除绑定并回到空态
|
||||
- 未绑定会员:空态区新增「手动绑定」按钮,弹出绑定弹窗(所属柜子下拉 + 空闲格口下拉,格口随柜子联动刷新)
|
||||
- 绑定成功写入绑定数据(绑定时间取当前时间、状态默认「已归还」、使用次数 0)并回开会员权益弹窗回显,配套 toast 提示
|
||||
- 绑定/解绑弹窗取消操作返回会员权益弹窗,不变更数据
|
||||
- 专属餐盘柜信息表格新增「餐盘编号」列(格口内当前存放餐盘的编号,手动绑定的新格口显示 `-`)
|
||||
|
||||
## [2026-08-19] feat: 餐盘柜格口新增餐盘编码 + 格口分配原位改造(`din-terminal.html`)
|
||||
|
||||
- 格口数据模型新增 `plateCode`(餐盘编码)字段,格口卡片与格口详情弹窗展示「餐盘 CP-xxxx」,异常/空闲格口显示「—」
|
||||
- 格口分配由独立会员选择层改为在格口详情弹窗内原位切换为分配态(复用同一弹窗),新增「餐盘编码」输入框(必填,输入自动转大写)
|
||||
- 确认分配时校验餐盘编码并写入;用户解绑时同步清空餐盘编码为「—」
|
||||
|
||||
## [2026-08-18] feat: 订餐终端设备概览大弹窗 + 会员权益体系 + 采购库存抵扣 + 追溯多锅次分支
|
||||
|
||||
### 变更一:订餐终端设备概览大弹窗(`din-terminal.html`)
|
||||
|
||||
Reference in New Issue
Block a user