feat: 毛菜加工任务新增弹窗 + 调料位置设置交互优化

- 毛菜加工秤:新增毛菜加工任务弹窗(上游任务选择+食材复选表格),提交后直接入已完成分页
- 毛菜加工秤:所有重量统一为 g 单位,复选食材自动反显重量
- 调料位置设置:格子状态三态(未分配/已分配未称重黄色/已称重绿色),大格子参与一键分配
- 调料位置设置:今日需求 Chip 三态同步、超16个折叠展开
- 调料位置设置:去掉弹窗重量输入行,重量由硬件反显

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhangyan
2026-08-12 14:15:47 +08:00
co-authored by Claude Opus 4.6
parent 6dd8e3e03a
commit dbdc1891e1
2 changed files with 937 additions and 253 deletions
@@ -13,196 +13,272 @@
display: flex; align-items: center; justify-content: center;
padding: 20px;
}
/* PAD 设备外壳 */
.device-frame {
width: 720px; height: 960px;
background: #0e1326;
border-radius: 24px;
width: 720px; height: 960px; background: #0b1020;
border-radius: 24px; overflow: hidden;
box-shadow: 0 24px 64px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04);
overflow: hidden;
position: relative;
display: flex; flex-direction: column;
}
/* 顶部栏 */
.top-bar { display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px; }
.back-btn { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; transition: color 0.2s; }
.back-btn:hover { color: #fff; }
.top-bar__title { flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); }
.top-bar__user { font-size: 12px; color: rgba(255,255,255,0.5); }
/* 主内容 */
.main-content { flex: 1; padding: 20px 40px; display: flex; flex-direction: column; gap: 16px; overflow: hidden; }
/* ===== 顶栏 ===== */
.top-bar {
display: flex; align-items: center; height: 52px;
background: linear-gradient(180deg, #111d38 0%, #0e182c 100%);
padding: 0 20px; gap: 14px;
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.top-bar__back {
font-size: 14px; color: rgba(255,255,255,0.55); cursor: pointer;
text-decoration: none; transition: color 0.2s; flex-shrink: 0;
}
.top-bar__back:hover { color: #fff; }
.top-bar__title {
font-size: 15px; font-weight: 700; color: rgba(255,255,255,0.9);
letter-spacing: 0.5px;
}
.top-bar__spacer { flex: 1; }
.top-bar__btn {
padding: 5px 14px; border-radius: 6px; font-size: 12px; font-weight: 600;
cursor: pointer; border: none; font-family: inherit; transition: all 0.2s;
}
.top-bar__btn--reset {
background: rgba(255,255,255,0.05); color: rgba(255,255,255,0.5);
border: 1px solid rgba(255,255,255,0.08);
}
.top-bar__btn--reset:hover { background: rgba(255,255,255,0.1); color: #fff; }
.top-bar__btn--save {
background: #4a90d9; color: #fff;
}
.top-bar__btn--save:hover { background: #5ba0e9; }
.top-bar__btn--auto {
background: rgba(250,173,21,0.12); color: #faad14;
border: 1px solid rgba(250,173,21,0.2);
}
.top-bar__btn--auto:hover { background: rgba(250,173,21,0.22); }
.top-bar__btn:active { transform: scale(0.96); }
.top-bar__user {
font-size: 11px; color: rgba(255,255,255,0.35);
margin-left: 4px; flex-shrink: 0;
}
/* 说明栏 */
.info-bar {
display: flex; align-items: center; gap: 12px;
background: rgba(74,144,217,0.08); border: 1px solid rgba(74,144,217,0.2);
border-radius: 10px; padding: 10px 16px; font-size: 12px; color: rgba(255,255,255,0.6);
/* ===== 主内容 ===== */
.main-content {
flex: 1; padding: 12px 36px 16px;
display: flex; flex-direction: column; gap: 8px; overflow: hidden;
}
.info-bar__icon { font-size: 16px; }
.info-bar__text { flex: 1; line-height: 1.5; }
.info-bar__save-btn {
padding: 6px 16px; border-radius: 6px; border: none; cursor: pointer;
background: #4a90d9; color: #fff; font-size: 13px; font-weight: 600;
transition: all 0.2s; flex-shrink: 0;
}
.info-bar__save-btn:hover { background: #5ba0e9; }
.info-bar__save-btn:active { transform: scale(0.97); }
.info-bar__reset-btn {
padding: 6px 14px; border-radius: 6px; cursor: pointer;
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6);
border: 1px solid rgba(255,255,255,0.12); font-size: 13px; transition: all 0.2s; flex-shrink: 0;
}
.info-bar__reset-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
/* 调料秤网格 */
/* ===== 提示条 ===== */
.hint-bar {
font-size: 11px; color: rgba(255,255,255,0.28);
padding: 0 2px; line-height: 1;
}
/* ===== 需求 Chip 条 ===== */
.demand-strip {
display: flex; align-items: flex-start; gap: 8px;
padding: 8px 12px; border-radius: 10px;
background: rgba(250,173,21,0.03); border: 1px solid rgba(250,173,21,0.07);
}
.demand-strip__label {
font-size: 11px; font-weight: 600; color: #faad14;
flex-shrink: 0; padding-top: 3px; white-space: nowrap;
}
.demand-strip__chips {
display: flex; flex-wrap: wrap; gap: 5px; flex: 1;
}
.demand-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 9px; border-radius: 14px;
font-size: 11px; font-weight: 600;
background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.65);
border: 1px solid rgba(255,255,255,0.06);
transition: all 0.25s; white-space: nowrap;
}
.demand-chip__name { color: rgba(255,255,255,0.8); }
.demand-chip__qty { color: rgba(255,255,255,0.4); font-weight: 500; }
.demand-chip__status {
font-size: 10px; margin-left: 2px;
}
.demand-chip__status--pending { color: rgba(255,255,255,0.25); }
.demand-chip__status--done { color: #52c41a; }
.demand-chip--placed {
opacity: 0.45; border-color: rgba(82,196,26,0.2);
}
.demand-chip--warning {
border-color: rgba(250,173,21,0.3); background: rgba(250,173,21,0.04);
}
.demand-chip__status--warning { color: #faad14; }
.demand-chip--toggle {
cursor: pointer; color: #4a90d9; border-color: rgba(74,144,217,0.25);
background: rgba(74,144,217,0.06); font-weight: 500;
}
.demand-chip--toggle:hover {
background: rgba(74,144,217,0.14); border-color: rgba(74,144,217,0.45);
}
/* ===== 网格容器 ===== */
.grid-container {
flex: 1; display: flex; align-items: center; justify-content: center;
flex: 1; display: flex; align-items: flex-start; justify-content: center;
}
.seasoning-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(3, 100px);
gap: 5px;
gap: 8px; width: 100%;
}
/* 调料格基类 */
/* ===== 单元格基类 ===== */
.seasoning-cell {
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
border-radius: 8px; padding: 4px 6px;
aspect-ratio: 1;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.05);
border-radius: 12px; padding: 10px 12px;
display: flex; flex-direction: column; justify-content: center;
font-size: 10px; color: rgba(255,255,255,0.5);
transition: all 0.2s; position: relative;
}
/* 信息格:位置0(油),2列×2行大格 */
/* 信息格:油 (0) — 2×2 */
.seasoning-cell--info {
grid-column: 1 / span 2; grid-row: 1 / span 2;
background: rgba(250,173,21,0.08); border: 2px solid rgba(250,173,21,0.3);
padding: 8px 10px; gap: 4px; cursor: pointer;
background: linear-gradient(135deg, rgba(250,173,21,0.07) 0%, rgba(250,173,21,0.02) 100%);
border: 2px solid rgba(250,173,21,0.25); cursor: pointer; padding: 14px 16px; gap: 8px;
}
.seasoning-cell--info:hover { border-color: rgba(250,173,21,0.5); background: rgba(250,173,21,0.12); }
.seasoning-cell--info .sea-name {
font-size: 18px; font-weight: 700; color: #faad14; line-height: 1.2;
display: flex; align-items: center; gap: 4px; margin-bottom: 6px;
.seasoning-cell--info:hover {
border-color: rgba(250,173,21,0.45);
background: linear-gradient(135deg, rgba(250,173,21,0.1) 0%, rgba(250,173,21,0.03) 100%);
}
.seasoning-cell--info .sea-name-icon { font-size: 22px; }
.seasoning-cell--info .sea-fields {
display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px;
.seasoning-cell--info .sc-name {
font-size: 22px; font-weight: 700; color: #faad14;
display: flex; align-items: center; gap: 8px;
}
.seasoning-cell--info .sea-field {
display: flex; align-items: center; gap: 4px; font-size: 14px;
.seasoning-cell--info .sc-name em { font-style: normal; font-size: 26px; }
.seasoning-cell--info .sc-meta {
display: flex; gap: 24px; font-size: 15px;
}
.seasoning-cell--info .sea-field__label { color: rgba(255,255,255,0.35); }
.seasoning-cell--info .sea-field__value { color: rgba(255,255,255,0.75); font-weight: 500; }
/* 调料格(位置1-11 */
.seasoning-cell--slot { cursor: pointer; }
.seasoning-cell--slot:hover { border-color: rgba(74,144,217,0.3); background: rgba(74,144,217,0.06); }
.seasoning-cell--occupied { border-color: rgba(255,255,255,0.12); }
.sea-slot__name {
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.85);
line-height: 1.2; display: flex; align-items: center; gap: 4px;
margin-bottom: 4px;
}
.sea-slot__name-icon { font-size: 16px; }
.sea-slot__fields {
display: grid; grid-template-columns: 1fr 1fr; gap: 2px 8px;
}
.sea-slot__field {
display: flex; gap: 3px; font-size: 11px; line-height: 1.4;
}
.sea-slot__field-label { color: rgba(255,255,255,0.35); }
.sea-slot__field-value { color: rgba(255,255,255,0.65); font-weight: 500; }
.sea-slot__empty { font-size: 11px; color: rgba(255,255,255,0.2); text-align: center; }
.seasoning-cell--info .sc-meta span { color: rgba(255,255,255,0.5); }
.seasoning-cell--info .sc-meta strong { color: rgba(255,255,255,0.8); font-weight: 600; }
/* 下拉选择浮层 */
/* 调料格 (1-11) */
.seasoning-cell--slot { cursor: pointer; }
.seasoning-cell--slot:hover {
border-color: rgba(74,144,217,0.25); background: rgba(74,144,217,0.04);
}
.seasoning-cell--occupied {
border-color: rgba(255,255,255,0.1); background: rgba(255,255,255,0.03);
}
.sc-name {
font-size: 16px; font-weight: 600; color: rgba(255,255,255,0.85);
display: flex; align-items: center; gap: 6px; margin-bottom: 5px;
}
.sc-name em { font-style: normal; font-size: 18px; }
.sc-weight {
font-size: 13px; color: rgba(255,255,255,0.4); font-weight: 500;
}
/* 格子状态标识 */
.seasoning-cell--warning {
border-color: rgba(250,173,21,0.4) !important;
background: rgba(250,173,21,0.04) !important;
}
.seasoning-cell--warning .sc-weight { color: #faad14; }
.seasoning-cell--success {
border-color: rgba(82,196,26,0.3) !important;
background: rgba(82,196,26,0.03) !important;
}
.seasoning-cell--success .sc-weight { color: #52c41a; }
/* 信息格(油)的成功/警告状态 */
.seasoning-cell--info.seasoning-cell--warning {
border-color: rgba(250,173,21,0.45) !important;
}
.seasoning-cell--info.seasoning-cell--success {
border-color: rgba(82,196,26,0.35) !important;
}
.sc-empty {
text-align: center; color: rgba(255,255,255,0.12);
}
.sc-empty__icon { font-size: 22px; margin-bottom: 2px; }
.sc-empty__text { font-size: 10px; }
/* ===== 选择浮层 ===== */
.select-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 100;
display: none; align-items: center; justify-content: center;
position: fixed; inset: 0; background: rgba(0,0,0,0.55);
z-index: 100; display: none; align-items: center; justify-content: center;
}
.select-overlay.show { display: flex; }
.select-panel {
background: #1a2b4a; border-radius: 14px; border: 1px solid rgba(255,255,255,0.08);
padding: 20px 24px; width: 420px; display: flex; flex-direction: column; gap: 14px;
background: #141f38; border-radius: 16px; border: 1px solid rgba(255,255,255,0.08);
padding: 22px 26px; width: 420px; display: flex; flex-direction: column; gap: 14px;
box-shadow: 0 20px 48px rgba(0,0,0,0.5);
}
.select-panel__title {
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.85);
display: flex; align-items: center; gap: 8px;
font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
}
.select-panel__pos-tag {
font-size: 11px; padding: 2px 8px; border-radius: 4px;
background: rgba(74,144,217,0.15); color: #4a90d9; font-weight: 600;
}
/* 搜索框 */
.select-panel__search-wrap { position: relative; flex: 1; }
.select-panel__search-row { display: flex; gap: 8px; align-items: flex-start; }
.select-panel__btn--reset-inline {
padding: 10px 14px; border-radius: 8px; font-size: 13px; font-weight: 600;
cursor: pointer; transition: all 0.2s; border: none; font-family: inherit; flex-shrink: 0;
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.5);
border: 1px solid rgba(255,255,255,0.1);
}
.select-panel__btn--reset-inline:hover { background: rgba(255,255,255,0.1); color: #fff; }
.select-panel__search-wrap { position: relative; flex: 1; }
.select-panel__search-input {
width: 100%; padding: 10px 14px; background: #121832;
border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
color: #fff; font-size: 14px; outline: none; transition: border-color 0.2s;
font-family: inherit;
width: 100%; padding: 10px 14px; background: #0d1529;
border: 1px solid rgba(255,255,255,0.08); border-radius: 8px;
color: #fff; font-size: 14px; outline: none; font-family: inherit;
transition: border-color 0.2s;
}
.select-panel__search-input:focus { border-color: #4a90d9; }
/* 搜索结果列表 */
.select-panel__search-results {
position: absolute; top: 100%; left: 0; right: 0; z-index: 10;
background: #121832; border: 1px solid rgba(255,255,255,0.1);
border-radius: 0 0 8px 8px; max-height: 180px; overflow-y: auto;
display: none;
background: #0d1529; border: 1px solid rgba(255,255,255,0.08);
border-radius: 0 0 8px 8px; max-height: 180px; overflow-y: auto; display: none;
}
.select-panel__search-results.show { display: block; }
.select-panel__search-item {
padding: 10px 14px; cursor: pointer; font-size: 13px; color: rgba(255,255,255,0.8);
transition: background 0.15s; display: flex; align-items: center; gap: 8px;
padding: 10px 14px; cursor: pointer; font-size: 13px; color: rgba(255,255,255,0.75);
display: flex; align-items: center; gap: 8px; transition: background 0.15s;
}
.select-panel__search-item:hover { background: rgba(74,144,217,0.12); color: #fff; }
.select-panel__search-item--selected { background: rgba(74,144,217,0.18); color: #4a90d9; }
.select-panel__search-item--demand { background: rgba(250,173,21,0.05); }
.select-panel__search-item--demand:hover { background: rgba(250,173,21,0.12); }
.search-item__badge {
font-size: 10px; padding: 1px 6px; border-radius: 3px;
background: rgba(250,173,21,0.15); color: #faad14;
font-weight: 600; margin-left: auto; flex-shrink: 0;
}
.select-panel__search-divider {
padding: 5px 14px; font-size: 10px; color: rgba(255,255,255,0.2);
border-top: 1px solid rgba(255,255,255,0.05); pointer-events: none;
}
.select-panel__search-item:hover { background: rgba(74,144,217,0.15); color: #fff; }
.select-panel__search-item--selected { background: rgba(74,144,217,0.2); color: #4a90d9; }
.select-panel__search-empty {
padding: 16px; text-align: center; font-size: 12px; color: rgba(255,255,255,0.3);
display: none;
padding: 16px; text-align: center; font-size: 12px; color: rgba(255,255,255,0.25); display: none;
}
.select-panel__search-empty.show { display: block; }
/* 重量输入行 */
.select-panel__weight-row { display: flex; align-items: center; gap: 10px; }
.select-panel__weight-label { font-size: 13px; color: rgba(255,255,255,0.6); flex-shrink: 0; }
.select-panel__weight-input {
flex: 1; padding: 10px 14px; background: #121832;
border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
color: #fff; font-size: 14px; outline: none; transition: border-color 0.2s;
font-family: inherit; -moz-appearance: textfield;
.select-panel__btn--reset-inline {
padding: 10px 14px; border-radius: 8px; font-size: 13px; font-weight: 600;
cursor: pointer; border: none; font-family: inherit; flex-shrink: 0;
background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.4);
border: 1px solid rgba(255,255,255,0.08); transition: all 0.2s;
}
.select-panel__weight-input:focus { border-color: #faad14; }
.select-panel__weight-input::-webkit-outer-spin-button,
.select-panel__weight-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.select-panel__weight-unit { font-size: 13px; color: rgba(255,255,255,0.4); }
.select-panel__btn--reset-inline:hover { background: rgba(255,255,255,0.08); color: #fff; }
.select-panel__actions { display: flex; gap: 10px; }
.select-panel__btn {
flex: 1; padding: 10px 0; border-radius: 8px; font-size: 13px; font-weight: 600;
cursor: pointer; transition: all 0.2s; border: none; font-family: inherit;
cursor: pointer; border: none; font-family: inherit; transition: all 0.2s;
}
.select-panel__btn--clear {
background: rgba(255,77,79,0.08); color: rgba(255,77,79,0.7);
border: 1px solid rgba(255,77,79,0.2);
background: rgba(255,77,79,0.06); color: rgba(255,77,79,0.6);
border: 1px solid rgba(255,77,79,0.15);
}
.select-panel__btn--clear:hover { background: rgba(255,77,79,0.18); color: #ff4d4f; }
.select-panel__btn--clear:hover { background: rgba(255,77,79,0.15); color: #ff4d4f; }
.select-panel__btn--cancel {
background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.6);
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.5);
}
.select-panel__btn--cancel:hover { background: rgba(255,255,255,0.12); color: #fff; }
.select-panel__btn--cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
.select-panel__btn--confirm {
background: #4a90d9; color: #fff;
}
.select-panel__btn--confirm:hover { background: #5ba0e9; }
/* Toast */
/* ===== Toast ===== */
.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;
background: rgba(0,0,0,0.9); 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;
@@ -214,31 +290,35 @@
</head>
<body>
<div class="device-frame">
<!-- 顶栏 -->
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<a class="top-bar__back" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a>
<span class="top-bar__title">调料位置设置</span>
<span class="top-bar__spacer"></span>
<button class="top-bar__btn top-bar__btn--auto" onclick="autoAssignAll()">一键分配</button>
<button class="top-bar__btn top-bar__btn--reset" onclick="resetConfig()">重置</button>
<button class="top-bar__btn top-bar__btn--save" onclick="saveConfig()">保存</button>
<span class="top-bar__user" id="topUser">--</span>
</div>
<div class="main-content">
<!-- 说明栏 -->
<div class="info-bar">
<span class="info-bar__icon">&#x1F4A1;</span>
<span class="info-bar__text">点击格子配置调料名称和放置重量。配置完成后点击「保存」生效。</span>
<button class="info-bar__reset-btn" onclick="resetConfig()">重置</button>
<button class="info-bar__save-btn" onclick="saveConfig()">保存</button>
<!-- 提示条 -->
<div class="hint-bar">点击格子配置调料名称,重量由硬件自动反显,完成后点击「保存」生效</div>
<!-- 今日需求 Chip 条 -->
<div class="demand-strip" id="demandStrip">
<span class="demand-strip__label">&#x1F4CB; 今日需求</span>
<div class="demand-strip__chips" id="demandChips"></div>
</div>
<!-- 调料秤网格 -->
<div class="grid-container">
<div class="seasoning-grid" id="seasoningGrid">
<!-- 动态渲染 -->
</div>
<div class="seasoning-grid" id="seasoningGrid"></div>
</div>
</div>
</div>
<!-- 下拉选择浮层 -->
<!-- 选择浮层 -->
<div class="select-overlay" id="selectOverlay">
<div class="select-panel">
<div class="select-panel__title" id="selectTitle">选择调料</div>
@@ -248,12 +328,7 @@
<div class="select-panel__search-results" id="searchResults"></div>
<div class="select-panel__search-empty" id="searchEmpty">无匹配调料</div>
</div>
<button class="select-panel__btn--reset-inline" id="resetInlineBtn" onclick="resetSlot()">重置</button>
</div>
<div class="select-panel__weight-row">
<span class="select-panel__weight-label">放置重量:</span>
<input type="number" class="select-panel__weight-input" id="seasoningWeight" min="0" step="0.1" placeholder="0">
<span class="select-panel__weight-unit"></span>
<button class="select-panel__btn--reset-inline" onclick="resetSlot()">重置</button>
</div>
<div class="select-panel__actions">
<button class="select-panel__btn select-panel__btn--clear" onclick="clearSlot()">清空</button>
@@ -298,19 +373,14 @@
{ name: '姜末', icon: '&#x1F95C;' }
];
/* 存储键名 */
var STORAGE_KEY = 'seasoning_position_config_v2';
/* 当前编辑的位置(0=信息格油,1-11=调料格) */
var editingPos = -1;
/* 加载配置(兼容旧格式 {pos:'name'} → 新格式 {pos:{name,weight}} */
function loadConfig() {
var raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
try { return JSON.parse(raw); } catch(e) {}
}
/* 默认配置 */
return {
0: { name: '油', weight: 1000 },
1: { name: '盐', weight: 500 },
@@ -322,115 +392,104 @@
};
}
/* 保存配置 */
function saveConfigData(config) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
}
var config = loadConfig();
/* 渲染网格(位置0=信息格 2×2,位置1-11=调料格) */
/* 渲染网格 */
function renderGrid() {
var grid = document.getElementById('seasoningGrid');
grid.innerHTML = '';
/* 位置0:信息格(油),2列×2行大格 */
/* 位置0信息格 2×2 */
var infoCell = document.createElement('div');
infoCell.className = 'seasoning-cell seasoning-cell--info';
var oilCfg = config[0];
if (oilCfg && oilCfg.name) {
var hasWeight = (oilCfg.weight || 0) > 0;
infoCell.classList.add(hasWeight ? 'seasoning-cell--success' : 'seasoning-cell--warning');
var opt = SEASONING_OPTIONS.find(function(o) { return o.name === oilCfg.name; });
var icon = opt ? opt.icon : '&#x1F6E2;';
var weightDisplay = hasWeight ? (oilCfg.weight + 'g') : '0g';
infoCell.innerHTML =
'<div class="sea-name"><span class="sea-name-icon">' + icon + '</span>' + oilCfg.name + '</div>' +
'<div class="sea-fields">' +
'<div class="sea-field"><span class="sea-field__value">' + (oilCfg.weight || 0) + 'g</span></div>' +
'</div>';
'<div class="sc-name"><em>' + icon + '</em>' + oilCfg.name + '</div>' +
'<div class="sc-meta"><strong>' + weightDisplay + '</strong></div>';
} else {
infoCell.innerHTML = '<div class="sea-name" style="color:rgba(255,255,255,0.3)">点击配置</div>';
infoCell.innerHTML = '<div class="sc-empty"><div class="sc-empty__icon">+</div><div class="sc-empty__text">点击配置</div></div>';
}
infoCell.onclick = (function(p) { return function() { openSelect(p); }; })(0);
grid.appendChild(infoCell);
/* 位置1-11:普通调料格 */
/* 位置1-11 */
for (var pos = 1; pos <= 11; pos++) {
var cell = document.createElement('div');
cell.className = 'seasoning-cell seasoning-cell--slot';
var cfg = config[pos];
if (cfg && cfg.name) {
cell.classList.add('seasoning-cell--occupied');
var hasWeight = (cfg.weight || 0) > 0;
cell.classList.add(hasWeight ? 'seasoning-cell--success' : 'seasoning-cell--warning');
var opt2 = SEASONING_OPTIONS.find(function(o) { return o.name === cfg.name; });
var icon2 = opt2 ? opt2.icon : '&#x1F9C2;';
var weightDisplay = hasWeight ? (cfg.weight + 'g') : '0g';
cell.innerHTML =
'<div class="sea-slot__name"><span class="sea-slot__name-icon">' + icon2 + '</span>' + cfg.name + '</div>' +
'<div class="sea-slot__fields">' +
'<div class="sea-slot__field"><span class="sea-slot__field-value">' + (cfg.weight || 0) + 'g</span></div>' +
'</div>';
'<div class="sc-name"><em>' + icon2 + '</em>' + cfg.name + '</div>' +
'<div class="sc-weight">' + weightDisplay + '</div>';
} else {
cell.innerHTML = '<div class="sea-slot__empty">点击配置</div>';
cell.innerHTML = '<div class="sc-empty"><div class="sc-empty__icon">+</div><div class="sc-empty__text">点击配置</div></div>';
}
cell.onclick = (function(p) { return function() { openSelect(p); }; })(pos);
grid.appendChild(cell);
}
}
/* 打开选择弹窗(所有位置统一体验 */
/* 打开选择弹窗(重量由硬件反显,弹窗不设重量输入 */
function openSelect(pos) {
editingPos = pos;
document.getElementById('selectTitle').textContent = '选择调料';
var searchInput = document.getElementById('seasoningSearch');
searchInput.value = config[pos] && config[pos].name ? config[pos].name : '';
searchInput.disabled = false;
searchInput.style.opacity = '1';
renderSearchResults('');
/* 填充重量 */
var weightInput = document.getElementById('seasoningWeight');
weightInput.value = (config[pos] && config[pos].weight) ? config[pos].weight : '';
document.getElementById('selectOverlay').classList.add('show');
}
/* 确认选择 */
/* 确认选择(重量由硬件反显,此处仅配置调料名称) */
function confirmSelect() {
var name = document.getElementById('seasoningSearch').value.trim();
var weight = parseFloat(document.getElementById('seasoningWeight').value) || 0;
if (!name) {
showToast('请输入或选择调料名称', 1500, 'error');
return;
}
/* 检查是否在候选清单中 */
var found = SEASONING_OPTIONS.find(function(o) { return o.name === name; });
if (!found) {
showToast('调料"' + name + '"不在候选清单中', 1500, 'error');
return;
}
config[editingPos] = { name: name, weight: weight };
/* 保留已有重量或初始化为 0,等待硬件反显 */
var oldWeight = (config[editingPos] && config[editingPos].weight) ? config[editingPos].weight : 0;
config[editingPos] = { name: name, weight: oldWeight };
renderDemandChips();
renderGrid();
closeSelect();
showToast('位置 ' + editingPos + ' 已配置为 ' + name + ' (' + weight + 'g)', 1500, 'success');
showToast('位置 ' + editingPos + ' 已配置为 ' + name + ',等待称重', 1500, 'success');
}
/* 清空位置(重量归零,保留调料名称) */
/* 清空位置 */
function clearSlot() {
if (config[editingPos] && config[editingPos].name) {
config[editingPos].weight = 0;
} else {
config[editingPos] = { name: '', weight: 0 };
}
document.getElementById('seasoningWeight').value = '0';
config[editingPos] = { name: '', weight: 0 };
document.getElementById('seasoningSearch').value = '';
renderGrid();
showToast('位置 ' + editingPos + ' 重量已清零', 1500, 'success');
renderDemandChips();
showToast('位置 ' + editingPos + ' 已清空', 1500, 'success');
}
/* 重置调料选择(清除搜索框,清空重量输入) */
/* 重置选择 */
function resetSlot() {
var searchInput = document.getElementById('seasoningSearch');
var weightInput = document.getElementById('seasoningWeight');
searchInput.value = '';
weightInput.value = '';
document.getElementById('seasoningSearch').value = '';
document.getElementById('searchResults').classList.remove('show');
document.getElementById('searchEmpty').classList.remove('show');
showToast('位置 ' + editingPos + ' 调料选择已清除', 1500, 'success');
@@ -444,46 +503,7 @@
document.getElementById('searchEmpty').classList.remove('show');
}
/* 渲染搜索结果列表 */
function renderSearchResults(query) {
var resultsEl = document.getElementById('searchResults');
var emptyEl = document.getElementById('searchEmpty');
resultsEl.innerHTML = '';
resultsEl.classList.remove('show');
emptyEl.classList.remove('show');
var filtered = SEASONING_OPTIONS;
if (query) {
var q = query.toLowerCase();
filtered = SEASONING_OPTIONS.filter(function(o) {
return o.name.toLowerCase().indexOf(q) >= 0;
});
}
if (filtered.length === 0) {
emptyEl.classList.add('show');
return;
}
filtered.forEach(function(o) {
var item = document.createElement('div');
item.className = 'select-panel__search-item';
var currentName = document.getElementById('seasoningSearch').value;
if (o.name === currentName) {
item.classList.add('select-panel__search-item--selected');
}
item.innerHTML = o.icon + ' ' + o.name;
item.onclick = function() {
document.getElementById('seasoningSearch').value = o.name;
resultsEl.classList.remove('show');
emptyEl.classList.remove('show');
};
resultsEl.appendChild(item);
});
resultsEl.classList.add('show');
}
/* 保存配置 */
/* 保存 */
function saveConfig() {
saveConfigData(config);
showToast('调料位置配置已保存', 2000, 'success');
@@ -495,6 +515,7 @@
config = {};
saveConfigData(config);
renderGrid();
renderDemandChips();
showToast('配置已重置', 1500, 'success');
}
@@ -524,15 +545,13 @@
}
})();
/* 搜索框输入事件 */
/* 搜索框事件 */
document.getElementById('seasoningSearch').addEventListener('input', function() {
renderSearchResults(this.value);
});
/* 点击搜索框时显示全部候选 */
document.getElementById('seasoningSearch').addEventListener('focus', function() {
renderSearchResults(this.value);
});
/* 点击空白处关闭搜索结果 */
document.addEventListener('click', function(e) {
var wrap = document.querySelector('.select-panel__search-wrap');
if (wrap && !wrap.contains(e.target)) {
@@ -541,8 +560,194 @@
}
});
/* ==================== 今日调料需求 ==================== */
var MOCK_DEMAND = [
{ name: '酱油', qty: 2500 },
{ name: '盐', qty: 2000 },
{ name: '料酒', qty: 1800 },
{ name: '白糖', qty: 1500 },
{ name: '醋', qty: 1400 },
{ name: '蚝油', qty: 1200 },
{ name: '豆瓣酱', qty: 1000 },
{ name: '鸡精', qty: 900 },
{ name: '味精', qty: 800 },
{ name: '蒜蓉', qty: 750 },
{ name: '花椒', qty: 700 },
{ name: '胡椒粉', qty: 600 },
{ name: '甜面酱', qty: 550 },
{ name: '干辣椒', qty: 500 },
{ name: '桂皮', qty: 400 },
{ name: '八角', qty: 350 }
];
/* 查找需求调料在配置中的称重状态: -1=未分配, 0=已分配未称重, >0=已分配且已称重 */
function getDemandSlotWeight(name) {
for (var pos = 0; pos <= 11; pos++) {
if (config[pos] && config[pos].name === name) {
return config[pos].weight || 0;
}
}
return -1;
}
function isDemandItem(name) {
return MOCK_DEMAND.some(function(d) {
if (d.name !== name) return false;
return getDemandSlotWeight(name) <= 0; /* 未分配 或 已分配但未称重,仍算需求项 */
});
}
function getDemandWeight(name) {
var found = MOCK_DEMAND.find(function(d) { return d.name === name; });
return found ? found.qty : 0;
}
/* 渲染搜索结果 */
function renderSearchResults(query) {
var resultsEl = document.getElementById('searchResults');
var emptyEl = document.getElementById('searchEmpty');
resultsEl.innerHTML = '';
resultsEl.classList.remove('show');
emptyEl.classList.remove('show');
var filtered = SEASONING_OPTIONS;
if (query) {
var q = query.toLowerCase();
filtered = SEASONING_OPTIONS.filter(function(o) {
return o.name.toLowerCase().indexOf(q) >= 0;
});
}
if (filtered.length === 0) {
emptyEl.classList.add('show');
return;
}
var demandItems = filtered.filter(function(o) { return isDemandItem(o.name); });
var otherItems = filtered.filter(function(o) { return !isDemandItem(o.name); });
var currentName = document.getElementById('seasoningSearch').value.trim();
demandItems.forEach(function(o) {
resultsEl.appendChild(buildSearchItem(o, currentName, true));
});
if (demandItems.length > 0 && otherItems.length > 0) {
var divider = document.createElement('div');
divider.className = 'select-panel__search-divider';
divider.textContent = '其它调料';
resultsEl.appendChild(divider);
}
otherItems.forEach(function(o) {
resultsEl.appendChild(buildSearchItem(o, currentName, false));
});
resultsEl.classList.add('show');
}
function buildSearchItem(o, currentName, isDemand) {
var item = document.createElement('div');
item.className = 'select-panel__search-item';
if (o.name === currentName) item.classList.add('select-panel__search-item--selected');
if (isDemand) item.classList.add('select-panel__search-item--demand');
var label = o.icon + ' ' + o.name;
if (isDemand) {
label += '<span class="search-item__badge">&#x1F4CB; 今日需求</span>';
}
item.innerHTML = label;
item.onclick = function() {
document.getElementById('seasoningSearch').value = o.name;
document.getElementById('searchResults').classList.remove('show');
document.getElementById('searchEmpty').classList.remove('show');
};
return item;
}
/* 折叠展开控制 */
var demandExpanded = false;
var DEMAND_COLLAPSE_LIMIT = 16;
function toggleDemandExpand() {
demandExpanded = !demandExpanded;
renderDemandChips();
}
/* 渲染需求 Chips(三态:未分配 / 已分配未称重 / 已称重,超过16个折叠) */
function renderDemandChips() {
var container = document.getElementById('demandChips');
container.innerHTML = '';
var total = MOCK_DEMAND.length;
var needsCollapse = total > DEMAND_COLLAPSE_LIMIT;
var list = (!needsCollapse || demandExpanded) ? MOCK_DEMAND : MOCK_DEMAND.slice(0, DEMAND_COLLAPSE_LIMIT);
list.forEach(function(d) {
var chip = document.createElement('span');
chip.className = 'demand-chip';
var slotW = getDemandSlotWeight(d.name);
var statusClass, statusIcon, weightText;
if (slotW < 0) {
/* 未分配 → 显示需求量 */
statusClass = 'demand-chip__status--pending';
statusIcon = '&#x25CB;';
weightText = d.qty + 'g';
} else if (slotW === 0) {
/* 已分配但未称重 → 黄色圆点,重量显示为 0 */
chip.classList.add('demand-chip--warning');
statusClass = 'demand-chip__status--warning';
statusIcon = '&#x25CF;';
weightText = '0g';
} else {
/* 已分配且已称重 → 绿色对号,显示实际重量 */
chip.classList.add('demand-chip--placed');
statusClass = 'demand-chip__status--done';
statusIcon = '&#x2713;';
weightText = slotW + 'g';
}
chip.innerHTML =
'<span class="demand-chip__name">' + d.name + '</span>' +
'<span class="demand-chip__qty">' + weightText + '</span>' +
'<span class="demand-chip__status ' + statusClass + '">' + statusIcon + '</span>';
container.appendChild(chip);
});
/* 折叠/展开按钮 */
if (needsCollapse) {
var toggleChip = document.createElement('span');
toggleChip.className = 'demand-chip demand-chip--toggle';
toggleChip.textContent = demandExpanded ? '收起 ▲' : '展开全部 (' + total + ')';
toggleChip.onclick = toggleDemandExpand;
container.appendChild(toggleChip);
}
}
/* 一键分配:按需求量降序自动填充空闲格子 */
function autoAssignAll() {
if (!confirm('确定一键分配?将按需求量从大到小自动填充空闲格子。')) return;
var sorted = MOCK_DEMAND.slice().sort(function(a, b) { return b.qty - a.qty; });
var slotIndex = 0;
var assigned = 0;
for (var pos = 0; pos <= 11 && slotIndex < sorted.length; pos++) {
if (config[pos] && config[pos].name) continue;
var item = sorted[slotIndex];
config[pos] = { name: item.name, weight: 0 };
slotIndex++;
assigned++;
}
if (assigned === 0) {
showToast('没有空闲格子可分配', 2000, 'error');
return;
}
renderGrid();
renderDemandChips();
showToast('已自动分配 ' + assigned + ' 个调料到空闲格子', 2000, 'success');
}
/* 初始化 */
renderGrid();
renderDemandChips();
</script>
</body>
</html>
@@ -198,6 +198,15 @@
100% { border-color: rgba(255,255,255,0.06); box-shadow: none; background: #1a2b4a; }
}
/* 新增任务按钮 */
.add-task-btn {
padding: 8px 18px; border-radius: 8px; cursor: pointer;
background: #4a90d9; color: #fff; font-size: 13px; font-weight: 600;
border: none; transition: all 0.2s; font-family: inherit; flex-shrink: 0;
}
.add-task-btn:hover { background: #5ba0e9; }
.add-task-btn:active { transform: scale(0.96); }
/* 顶部操作区 */
.tabs-spacer { flex: 1; }
.logout-btn {
@@ -207,6 +216,185 @@
}
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
/* ── 新增任务弹窗 ── */
.dialog-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.55);
z-index: 100; display: none; align-items: center; justify-content: center;
}
.dialog-overlay.show { display: flex; }
.dialog-panel {
background: #0e1326; border-radius: 14px; border: 1px solid rgba(255,255,255,0.08);
padding: 0; width: 900px; height: 560px; display: flex; flex-direction: column; gap: 0;
box-shadow: 0 20px 48px rgba(0,0,0,0.5); overflow: hidden;
}
/* 弹窗顶栏 — 与 top-bar 一致 */
.dialog-panel__top {
display: flex; align-items: center; height: 44px;
background: #1a2b4a; padding: 0 20px; flex-shrink: 0;
}
.dialog-panel__title {
flex: 1; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
}
/* 弹窗主内容 — 仿 02 页 main-content 左右分栏 */
.dialog-body {
flex: 1; padding: 14px 20px; display: flex; gap: 16px; overflow: hidden;
}
/* ===== 左侧:任务号选择 ===== */
.dialog-left {
flex: 0 0 300px; display: flex; flex-direction: column; gap: 8px; overflow: hidden;
}
.dialog-section__label {
font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500;
}
.dialog-task-list {
flex: 1; display: flex; flex-direction: column; gap: 6px; overflow-y: auto;
scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent;
}
.dialog-task-list::-webkit-scrollbar { width: 4px; }
.dialog-task-list::-webkit-scrollbar-track { background: transparent; }
.dialog-task-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
.task-option {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-radius: 8px; cursor: pointer;
background: rgba(74,144,217,0.04); border: 1px solid rgba(74,144,217,0.08);
transition: all 0.2s; flex-shrink: 0;
}
.task-option:hover {
border-color: rgba(74,144,217,0.25); background: rgba(74,144,217,0.08);
}
.task-option--selected {
border-color: #4a90d9; background: rgba(74,144,217,0.1);
}
.task-option__radio {
width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0;
border: 2px solid rgba(255,255,255,0.2);
display: flex; align-items: center; justify-content: center; transition: all 0.2s;
}
.task-option--selected .task-option__radio {
border-color: #4a90d9; background: #4a90d9;
}
.task-option--selected .task-option__radio::after {
content: ''; width: 5px; height: 5px; border-radius: 50%; background: #fff;
}
.task-option__no {
font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.85);
font-family: "Courier New", monospace;
}
.task-option__desc {
font-size: 11px; color: rgba(255,255,255,0.35); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ===== 右侧:食材表格(仿 batch-panel ===== */
.dialog-right {
flex: 1; display: flex; flex-direction: column; gap: 10px; overflow: hidden;
}
/* 汇总 — 仿 batch-summary */
.dialog-summary {
background: #1a2b4a; border-radius: 12px; padding: 10px 16px;
display: flex; gap: 16px; flex-shrink: 0;
}
.dialog-stat { flex: 1; text-align: center; }
.dialog-stat__value { font-size: 20px; font-weight: 700; color: #fff; }
.dialog-stat__label { font-size: 11px; color: rgba(255,255,255,0.5); }
/* 表格区 — 仿 batch-list-section */
.ingredient-table-section {
flex: 1; background: #1a2b4a; border-radius: 12px;
padding: 12px 16px; display: flex; flex-direction: column; overflow: hidden;
}
.ingredient-table-section.hide {
display: none;
}
.ingredient-table-header {
display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
flex-shrink: 0;
}
.ingredient-table-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
.ingredient-table-count { font-size: 12px; color: rgba(255,255,255,0.4); }
/* 表头列 — 仿 batch-list__columns */
.ingredient-table-cols {
display: flex; align-items: center; padding: 0 12px 6px;
border-bottom: 1px solid rgba(255,255,255,0.08); flex-shrink: 0;
}
.ing-col {
font-size: 11px; color: rgba(255,255,255,0.4); white-space: nowrap;
}
.ing-col--name { flex: 1; }
.ing-col--actual { width: 72px; text-align: right; }
.ing-col--status { width: 64px; text-align: center; }
.ingredient-table-list {
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 6px;
scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent;
}
.ingredient-table-list::-webkit-scrollbar { width: 4px; }
.ingredient-table-list::-webkit-scrollbar-track { background: transparent; }
.ingredient-table-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
/* 食材行 — 仿 batch-item */
.ing-row {
display: flex; align-items: center; gap: 10px;
padding: 9px 12px; background: rgba(0,0,0,0.15); border-radius: 8px;
cursor: pointer; transition: all 0.2s;
}
.ing-row:hover { background: rgba(0,0,0,0.25); }
.ing-row--checked {
background: rgba(52,196,26,0.06); border: 1px solid rgba(82,196,26,0.18);
}
.ing-row__checkbox {
width: 16px; height: 16px; border-radius: 3px; flex-shrink: 0;
border: 2px solid rgba(255,255,255,0.2);
display: flex; align-items: center; justify-content: center; transition: all 0.2s;
font-size: 10px; color: transparent;
}
.ing-row--checked .ing-row__checkbox {
border-color: #52c41a; background: #52c41a; color: #fff;
}
.ing-row__icon {
width: 34px; height: 34px; border-radius: 8px; font-size: 18px;
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
background: rgba(74,144,217,0.15);
}
.ing-row__icon.icon--potato { background: rgba(250,173,22,0.15); }
.ing-row__icon.icon--tomato { background: rgba(255,77,79,0.15); }
.ing-row__icon.icon--cabbage { background: rgba(82,196,26,0.15); }
.ing-row__icon.icon--carrot { background: rgba(250,140,22,0.15); }
.ing-row__icon.icon--cucumber { background: rgba(20,184,166,0.15); }
.ing-row__name { flex: 1; font-size: 13px; font-weight: 500; color: #fff; }
.ing-row__actual {
font-size: 13px; color: rgba(255,255,255,0.25); width: 72px; text-align: right;
}
.ing-row__status {
font-size: 11px; font-weight: 500; width: 64px; text-align: center;
}
.ing-row__status--pending { color: rgba(255,255,255,0.3); }
.ing-row__status--checked { color: #52c41a; }
/* 弹窗底部 — 仿 submit-bar */
.dialog-panel__footer {
display: flex; align-items: center; gap: 12px;
padding: 10px 20px; background: #1a2b4a; flex-shrink: 0;
}
.dialog-hint {
flex: 1; font-size: 12px; color: rgba(255,255,255,0.4);
}
.dialog-btn {
padding: 10px 28px; border-radius: 8px; font-size: 14px; font-weight: 600;
cursor: pointer; border: none; font-family: inherit; transition: all 0.2s;
}
.dialog-btn--cancel {
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.5);
}
.dialog-btn--cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
.dialog-btn--confirm {
background: #52c41a; color: #fff;
}
.dialog-btn--confirm:hover { background: #62d42a; }
.dialog-btn--confirm:disabled {
background: rgba(82,196,26,0.3); cursor: not-allowed;
}
/* Toast */
.toast {
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
@@ -222,7 +410,7 @@
<div class="top-bar">
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a> <span class="top-bar__title">今日清洗任务</span>
<a class="back-btn" id="backBtn" href="javascript:void(0)">&#x25C0; 返回</a> <span class="top-bar__title">毛菜加工任务</span>
<span class="top-bar__user" id="topUser">--</span>
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
</div>
@@ -239,6 +427,7 @@
<span class="status-tab__count" id="doneCount">1</span>
</div>
<span class="tabs-spacer"></span>
<button class="add-task-btn" onclick="openAddTaskDialog()">+ 新增毛菜加工任务</button>
</div>
<!-- 任务列表区 -->
@@ -260,22 +449,22 @@
<div class="ingredient-row">
<div class="ingredient-row__icon icon--cucumber">🥒</div>
<span class="ingredient-row__name">黄瓜</span>
<span class="ingredient-row__weight" data-weight-g="2000">4 斤</span>
<span class="ingredient-row__recorded" data-weight-g="2000">已录 4 斤</span>
<span class="ingredient-row__weight" data-weight-g="2000">2000g</span>
<span class="ingredient-row__recorded" data-weight-g="2000">已录 2000g</span>
<span class="ingredient-row__deadline">截止 10:00</span>
</div>
<div class="ingredient-row">
<div class="ingredient-row__icon icon--potato"></div>
<span class="ingredient-row__name">土豆</span>
<span class="ingredient-row__weight" data-weight-g="2500">5 斤</span>
<span class="ingredient-row__recorded" data-weight-g="2500">已录 5 斤</span>
<span class="ingredient-row__weight" data-weight-g="2500">2500g</span>
<span class="ingredient-row__recorded" data-weight-g="2500">已录 2500g</span>
<span class="ingredient-row__deadline">截止 10:30</span>
</div>
<div class="ingredient-row">
<div class="ingredient-row__icon icon--tomato">🍅</div>
<span class="ingredient-row__name">西红柿</span>
<span class="ingredient-row__weight" data-weight-g="1750">3.5 斤</span>
<span class="ingredient-row__recorded" data-weight-g="1750">已录 3.5 斤</span>
<span class="ingredient-row__weight" data-weight-g="1750">1750g</span>
<span class="ingredient-row__recorded" data-weight-g="1750">已录 1750g</span>
<span class="ingredient-row__deadline">截止 11:00</span>
</div>
</div>
@@ -296,15 +485,15 @@
<div class="ingredient-row">
<div class="ingredient-row__icon icon--carrot"></div>
<span class="ingredient-row__name">胡萝卜</span>
<span class="ingredient-row__weight" data-weight-g="1000">2 斤</span>
<span class="ingredient-row__recorded" data-weight-g="1000">已录 2 斤</span>
<span class="ingredient-row__weight" data-weight-g="1000">1000g</span>
<span class="ingredient-row__recorded" data-weight-g="1000">已录 1000g</span>
<span class="ingredient-row__deadline">截止 11:30</span>
</div>
<div class="ingredient-row">
<div class="ingredient-row__icon icon--tomato">🍅</div>
<span class="ingredient-row__name">小番茄</span>
<span class="ingredient-row__weight" data-weight-g="750">1.5 斤</span>
<span class="ingredient-row__recorded" data-weight-g="750">已录 1.5 斤</span>
<span class="ingredient-row__weight" data-weight-g="750">750g</span>
<span class="ingredient-row__recorded" data-weight-g="750">已录 750g</span>
<span class="ingredient-row__deadline">截止 14:00</span>
</div>
</div>
@@ -324,22 +513,22 @@
<div class="ingredient-row">
<div class="ingredient-row__icon icon--cabbage">🥬</div>
<span class="ingredient-row__name">大白菜</span>
<span class="ingredient-row__weight" data-weight-g="4000">8 斤</span>
<span class="ingredient-row__recorded" data-weight-g="4000">已录 8 斤</span>
<span class="ingredient-row__weight" data-weight-g="4000">4000g</span>
<span class="ingredient-row__recorded" data-weight-g="4000">已录 4000g</span>
<span class="ingredient-row__deadline ingredient-row__deadline--overdue">截止 09:30</span>
</div>
<div class="ingredient-row">
<div class="ingredient-row__icon icon--cabbage">🥬</div>
<span class="ingredient-row__name">娃娃菜</span>
<span class="ingredient-row__weight" data-weight-g="1250">2.5 斤</span>
<span class="ingredient-row__recorded" data-weight-g="1250">已录 2.5 斤</span>
<span class="ingredient-row__weight" data-weight-g="1250">1250g</span>
<span class="ingredient-row__recorded" data-weight-g="1250">已录 1250g</span>
<span class="ingredient-row__deadline">截止 10:00</span>
</div>
<div class="ingredient-row">
<div class="ingredient-row__icon icon--potato"></div>
<span class="ingredient-row__name">土豆(晚餐)</span>
<span class="ingredient-row__weight" data-weight-g="1500">3 斤</span>
<span class="ingredient-row__recorded" data-weight-g="1500">已录 3 斤</span>
<span class="ingredient-row__weight" data-weight-g="1500">1500g</span>
<span class="ingredient-row__recorded" data-weight-g="1500">已录 1500g</span>
<span class="ingredient-row__deadline">截止 15:00</span>
</div>
</div>
@@ -350,6 +539,67 @@
</div>
</div>
<!-- 新增任务弹窗 — 仿 02-batch-weighing 左右分栏布局 -->
<div class="dialog-overlay" id="addTaskOverlay">
<div class="dialog-panel">
<!-- 弹窗顶栏 -->
<div class="dialog-panel__top">
<span class="dialog-panel__title">新增毛菜加工任务</span>
</div>
<!-- 弹窗主体:左右分栏 -->
<div class="dialog-body">
<!-- 左侧:任务号选择 -->
<div class="dialog-left">
<div class="dialog-section__label">选择关联任务号</div>
<div class="dialog-task-list" id="upstreamTaskList"></div>
</div>
<!-- 右侧:食材表格 + 统计 -->
<div class="dialog-right" id="dialogRight">
<!-- 汇总统计 — 仿 batch-summary -->
<div class="dialog-summary">
<div class="dialog-stat">
<div class="dialog-stat__value" id="footerKindCount"></div>
<div class="dialog-stat__label">品类数</div>
</div>
<div class="dialog-stat">
<div class="dialog-stat__value" id="footerTotalWeight"></div>
<div class="dialog-stat__label">总重量</div>
</div>
</div>
<!-- 食材表格 — 仿 batch-list-section -->
<div class="ingredient-table-section hide" id="ingredientTableSection">
<div class="ingredient-table-header">
<span class="ingredient-table-title">选择食材</span>
<span class="ingredient-table-count" id="ingTableCount">共 0 种,已选 0 种</span>
</div>
<div class="ingredient-table-cols">
<span class="ing-col ing-col--name">食材名称</span>
<span class="ing-col ing-col--actual">实取量</span>
<span class="ing-col ing-col--status">状态</span>
</div>
<div class="ingredient-table-list" id="ingredientTableList"></div>
</div>
<!-- 未选择任务号时展示占位 -->
<div class="ingredient-table-section" id="ingredientPlaceholder" style="align-items:center;justify-content:center;">
<div style="font-size:32px;opacity:0.2;margin-bottom:8px;">&#x1F4ED;</div>
<div style="font-size:12px;color:rgba(255,255,255,0.25);">请在左侧选择关联任务号</div>
</div>
</div>
</div>
<!-- 弹窗底部 — 仿 submit-bar -->
<div class="dialog-panel__footer">
<span class="dialog-hint" id="dialogHint">请选择任务号并勾选食材后完成生成</span>
<button class="dialog-btn dialog-btn--cancel" onclick="closeAddTaskDialog()">取消</button>
<button class="dialog-btn dialog-btn--confirm" id="confirmAddBtn" onclick="confirmAddTask()" disabled>完成生成</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
@@ -369,6 +619,11 @@
if (g % 500 === 0) return (g / 500) + '斤';
return formatGram(g);
}
/* 纯克展示:始终以 g 为单位 */
function formatGramOnly(g) {
if (g === undefined || g === null || g === '' || isNaN(g)) return '—';
return Number(g) + 'g';
}
// 获取 URL 参数
function getUrlParam(name) {
@@ -421,6 +676,223 @@
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
}
/* ==================== 新增任务弹窗逻辑 ==================== */
/* Mock: 上游任务数据 */
var UPSTREAM_TASKS = [
{
taskNo: 'PO-20260810-001',
ingredients: [
{ name: '土豆', icon: '🥔', iconClass: 'icon--potato', weight: 3000 },
{ name: '黄瓜', icon: '🥒', iconClass: 'icon--cucumber', weight: 2000 },
{ name: '西红柿', icon: '🍅', iconClass: 'icon--tomato', weight: 2500 },
{ name: '胡萝卜', icon: '🥕', iconClass: 'icon--carrot', weight: 1500 },
{ name: '大白菜', icon: '🥬', iconClass: 'icon--cabbage', weight: 4000 }
]
},
{
taskNo: 'PO-20260810-002',
ingredients: [
{ name: '芹菜', icon: '🥬', iconClass: 'icon--cabbage', weight: 1500 },
{ name: '青椒', icon: '🫑', iconClass: 'icon--cucumber', weight: 1200 },
{ name: '洋葱', icon: '🧅', iconClass: 'icon--potato', weight: 2000 },
{ name: '小番茄', icon: '🍅', iconClass: 'icon--tomato', weight: 800 }
]
},
{
taskNo: 'PO-20260810-003',
ingredients: [
{ name: '西兰花', icon: '🥦', iconClass: 'icon--cabbage', weight: 1800 },
{ name: '娃娃菜', icon: '🥬', iconClass: 'icon--cabbage', weight: 1200 },
{ name: '土豆', icon: '🥔', iconClass: 'icon--potato', weight: 2500 },
{ name: '黄瓜', icon: '🥒', iconClass: 'icon--cucumber', weight: 1500 },
{ name: '西红柿', icon: '🍅', iconClass: 'icon--tomato', weight: 2000 },
{ name: '胡萝卜', icon: '🥕', iconClass: 'icon--carrot', weight: 1000 }
]
}
];
var selectedUpstreamTask = null; /* 当前选中的上游任务号 */
var checkedIngredients = {}; /* 勾选的食材: {name: true/false} */
var taskCounter = 0; /* 任务编号自增 */
/* 打开弹窗 */
function openAddTaskDialog() {
selectedUpstreamTask = null;
checkedIngredients = {};
renderUpstreamTasks();
document.getElementById('ingredientTableSection').classList.add('hide');
document.getElementById('ingredientPlaceholder').style.display = 'flex';
document.getElementById('confirmAddBtn').disabled = true;
document.getElementById('dialogHint').textContent = '请选择任务号并勾选食材后完成生成';
document.getElementById('footerKindCount').textContent = '—';
document.getElementById('footerTotalWeight').textContent = '—';
document.getElementById('addTaskOverlay').classList.add('show');
}
/* 关闭弹窗 */
function closeAddTaskDialog() {
document.getElementById('addTaskOverlay').classList.remove('show');
}
/* 生成食材摘要文本 */
function ingredientSummary(ings) {
var names = ings.slice(0, 3).map(function(i) { return i.name; });
var tail = ings.length > 3 ? '…' : '';
return names.join('、') + tail + ' 等' + ings.length + '种食材';
}
/* 渲染上游任务号列表 */
function renderUpstreamTasks() {
var container = document.getElementById('upstreamTaskList');
container.innerHTML = '';
UPSTREAM_TASKS.forEach(function(t) {
var el = document.createElement('div');
el.className = 'task-option';
if (selectedUpstreamTask === t.taskNo) el.classList.add('task-option--selected');
el.innerHTML =
'<span class="task-option__radio"></span>' +
'<span class="task-option__no">' + t.taskNo + '</span>' +
'<span class="task-option__desc">' + ingredientSummary(t.ingredients) + '</span>';
el.onclick = function() { selectUpstreamTask(t.taskNo); };
container.appendChild(el);
});
}
/* 选择任务号 */
function selectUpstreamTask(taskNo) {
selectedUpstreamTask = taskNo;
checkedIngredients = {};
renderUpstreamTasks();
renderIngredientTable();
document.getElementById('ingredientTableSection').classList.remove('hide');
document.getElementById('ingredientPlaceholder').style.display = 'none';
updateConfirmBtn();
}
/* 渲染食材表格 */
function renderIngredientTable() {
var container = document.getElementById('ingredientTableList');
container.innerHTML = '';
var task = UPSTREAM_TASKS.find(function(t) { return t.taskNo === selectedUpstreamTask; });
if (!task) return;
task.ingredients.forEach(function(ing) {
var isChecked = checkedIngredients[ing.name];
var el = document.createElement('div');
el.className = 'ing-row';
if (isChecked) el.classList.add('ing-row--checked');
el.innerHTML =
'<span class="ing-row__checkbox">&#x2713;</span>' +
'<span class="ing-row__icon ' + ing.iconClass + '">' + ing.icon + '</span>' +
'<span class="ing-row__name">' + ing.name + '</span>' +
'<span class="ing-row__actual">' + (isChecked ? formatGramOnly(ing.weight) : '—') + '</span>' +
'<span class="ing-row__status' + (isChecked ? ' ing-row__status--checked' : ' ing-row__status--pending') + '">' +
(isChecked ? '已选择' : '未选择') + '</span>';
el.onclick = function() { toggleIngredient(ing.name); };
container.appendChild(el);
});
/* 更新计数 */
var total = task.ingredients.length;
var selected = Object.values(checkedIngredients).filter(function(v) { return v; }).length;
document.getElementById('ingTableCount').textContent = '共 ' + total + ' 种,已选 ' + selected + ' 种';
}
/* 勾选/取消食材 */
function toggleIngredient(name) {
checkedIngredients[name] = !checkedIngredients[name];
renderIngredientTable();
updateConfirmBtn();
}
/* 更新确认按钮状态 + 底部统计 */
function updateConfirmBtn() {
var hasChecked = Object.values(checkedIngredients).some(function(v) { return v; });
document.getElementById('confirmAddBtn').disabled = !(selectedUpstreamTask && hasChecked);
updateFooterStats();
}
/* 更新底部统计 */
function updateFooterStats() {
var task = selectedUpstreamTask ? UPSTREAM_TASKS.find(function(t) { return t.taskNo === selectedUpstreamTask; }) : null;
var selectedNames = Object.keys(checkedIngredients).filter(function(k) { return checkedIngredients[k]; });
var totalW = 0;
if (task) {
task.ingredients.forEach(function(ing) {
if (checkedIngredients[ing.name]) totalW += ing.weight;
});
}
document.getElementById('footerKindCount').textContent = selectedNames.length;
document.getElementById('footerTotalWeight').textContent = selectedNames.length > 0 ? formatGramOnly(totalW) : '—';
var hint = selectedNames.length > 0
? '已选 ' + selectedNames.length + ' 种食材,共 ' + formatGramOnly(totalW)
: '请勾选需要加工的食材';
document.getElementById('dialogHint').textContent = hint;
}
/* 确认新增任务 */
function confirmAddTask() {
if (!selectedUpstreamTask) return;
var selectedNames = Object.keys(checkedIngredients).filter(function(k) { return checkedIngredients[k]; });
if (selectedNames.length === 0) return;
var task = UPSTREAM_TASKS.find(function(t) { return t.taskNo === selectedUpstreamTask; });
var selectedIngs = task.ingredients.filter(function(ing) { return checkedIngredients[ing.name]; });
/* 生成清洗任务编号 */
taskCounter++;
var today = new Date();
var dateStr = today.getFullYear() +
('0' + (today.getMonth() + 1)).slice(-2) +
('0' + today.getDate()).slice(-2);
var taskNo = 'RW-' + dateStr + '-' + ('00' + taskCounter).slice(-3);
/* 构建食材行 HTML */
var rowsHtml = '';
selectedIngs.forEach(function(ing) {
rowsHtml +=
'<div class="ingredient-row">' +
'<div class="ingredient-row__icon ' + ing.iconClass + '">' + ing.icon + '</div>' +
'<span class="ingredient-row__name">' + ing.name + '</span>' +
'<span class="ingredient-row__weight" data-weight-g="' + ing.weight + '">需 ' + formatGramOnly(ing.weight) + '</span>' +
'<span class="ingredient-row__recorded ingredient-row__recorded--none">未录入</span>' +
'<span class="ingredient-row__deadline">—</span>' +
'</div>';
});
/* 构建任务卡片(直接生成已完成状态) */
var card = document.createElement('div');
card.className = 'order-card order-card--done';
card.dataset.status = 'done';
card.innerHTML =
'<div class="order-card__header">' +
'<div class="order-card__header-left">' +
'<span class="order-card__no">' + taskNo + '</span>' +
'</div>' +
'<span class="order-card__done-mark">✓ 已完成</span>' +
'</div>' +
'<div class="order-card__divider"></div>' +
'<div class="order-card__body">' + rowsHtml + '</div>' +
'<div class="order-card__divider"></div>';
var taskList = document.getElementById('taskList');
taskList.insertBefore(card, taskList.firstChild);
/* 更新已完成计数 */
var countEl = document.getElementById('doneCount');
countEl.textContent = (parseInt(countEl.textContent) + 1) + '';
/* 新增任务后强制切到已完成标签页,确保新任务可见 */
var doneTab = document.querySelector('[data-filter="done"]');
if (doneTab) {
filterTasks('done', doneTab);
}
/* 关闭弹窗并提示 */
closeAddTaskDialog();
showToast('任务单已生成:' + taskNo + ',关联 ' + selectedUpstreamTask, 2500);
}
// ── 模拟新任务单到达(15 秒后推送一个新任务单)──
setTimeout(function() {
var taskList = document.getElementById('taskList');
@@ -440,20 +912,27 @@
'<div class="ingredient-row">' +
'<div class="ingredient-row__icon icon--cabbage">🥦</div>' +
'<span class="ingredient-row__name">西兰花</span>' +
'<span class="ingredient-row__weight" data-weight-g="1500">需 3 斤</span>' +
'<span class="ingredient-row__weight" data-weight-g="1500">需 1500g</span>' +
'<span class="ingredient-row__recorded ingredient-row__recorded--none">未录入</span>' +
'<span class="ingredient-row__deadline">截止 13:00</span>' +
'</div>' +
'<div class="ingredient-row">' +
'<div class="ingredient-row__icon icon--carrot">🥕</div>' +
'<span class="ingredient-row__name">芹菜</span>' +
'<span class="ingredient-row__weight" data-weight-g="1000">需 2 斤</span>' +
'<span class="ingredient-row__weight" data-weight-g="1000">需 1000g</span>' +
'<span class="ingredient-row__recorded ingredient-row__recorded--none">未录入</span>' +
'<span class="ingredient-row__deadline">截止 13:30</span>' +
'</div>' +
'</div>' +
'<div class="order-card__divider"></div>';
taskList.insertBefore(newCard, taskList.firstChild);
/* 重新应用当前筛选状态 */
var activeTab = document.querySelector('.status-tab.active--pending, .status-tab.active--done');
if (activeTab) {
filterTasks(activeTab.dataset.filter, activeTab);
}
showToast('⚡ 新任务单已到达:RW-20260613-004', 2500);
// 更新待处理计数
document.getElementById('pendingCount').textContent = '3';