feat: 营养秤登录/菜单页创建 + 来料过秤预填修复 + 多模块SITE_MAP同步
- 营养秤新增 00-login.html(人脸/账号密码登录)和 00-welcome.html(功能菜单) - 修复 02-batch-weighing.html 预填逻辑:initTaskList 调用移至 batchItems 声明之后 - 验收秤/营养秤 index.html 新增登录页和菜单页卡片条目 - 清洗比例秤、比例秤等模块导航页完善 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
00f2ef7e73
commit
3c50fa83a4
Generated
+44
-3
@@ -1,21 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ClaudeCodeTabState">
|
||||
<option name="tabNames">
|
||||
<map>
|
||||
<entry key="1" value="RR" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="tabSessions">
|
||||
<map>
|
||||
<entry key="0">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="db7a87b0-595f-4ac5-95c5-11f059c1a686" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-7[1m]" />
|
||||
<option name="model" value="claude-sonnet-4-6" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="high" />
|
||||
<option name="reasoningEffort" value="medium" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="138df05e-0994-43f4-815e-ae3e2bce9d58" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-6" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="medium" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="2">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="147fc6bc-9ed3-4b0d-970d-0087ec03558c" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-6" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="medium" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="3">
|
||||
<value>
|
||||
<TabSessionState>
|
||||
<option name="provider" value="claude" />
|
||||
<option name="sessionId" value="56449601-4de2-4e73-b2ca-6b3ad0b2c6e0" />
|
||||
<option name="cwd" value="$PROJECT_DIR$" />
|
||||
<option name="model" value="claude-opus-4-6" />
|
||||
<option name="permissionMode" value="bypassPermissions" />
|
||||
<option name="reasoningEffort" value="medium" />
|
||||
</TabSessionState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="tabCount" value="4" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>餐线营养秤 - 登录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* ---- 主内容:登录面板居中 ---- */
|
||||
.login-main {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: calc(100% - 44px); padding: 24px 32px;
|
||||
}
|
||||
|
||||
/* 登录面板 */
|
||||
.login-panel {
|
||||
width: 480px; display: flex; flex-direction: column; gap: 20px;
|
||||
background: #1a2b4a; border-radius: 16px; padding: 32px 40px;
|
||||
}
|
||||
.login-panel__title {
|
||||
font-size: 18px; font-weight: 600; color: rgba(255,255,255,0.85); text-align: center;
|
||||
}
|
||||
.login-panel__subtitle {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; margin-top: -12px;
|
||||
}
|
||||
|
||||
/* 登录方式切换 Tab */
|
||||
.login-tabs {
|
||||
display: flex; gap: 0; border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.login-tab {
|
||||
flex: 1; padding: 12px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.5);
|
||||
cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s;
|
||||
background: none; border-top: none; border-left: none; border-right: none;
|
||||
}
|
||||
.login-tab:hover { color: rgba(255,255,255,0.8); }
|
||||
.login-tab.active {
|
||||
color: #4a90d9; border-bottom-color: #4a90d9;
|
||||
}
|
||||
|
||||
/* 人脸识别区 */
|
||||
.face-section {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 24px 0;
|
||||
}
|
||||
.face-section.hidden { display: none; }
|
||||
.face-frame {
|
||||
width: 200px; height: 200px; border-radius: 50%;
|
||||
border: 3px dashed rgba(74,144,217,0.4);
|
||||
display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px;
|
||||
background: rgba(26,43,74,0.3); cursor: pointer; transition: all 0.3s;
|
||||
}
|
||||
.face-frame:hover {
|
||||
border-color: #4a90d9; background: rgba(74,144,217,0.1);
|
||||
}
|
||||
.face-frame__icon { font-size: 44px; color: rgba(255,255,255,0.3); }
|
||||
.face-frame__text { font-size: 13px; color: rgba(255,255,255,0.4); text-align: center; }
|
||||
|
||||
@keyframes ringPulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.3); }
|
||||
50% { box-shadow: 0 0 0 12px rgba(74,144,217,0); }
|
||||
}
|
||||
.face-frame.detecting {
|
||||
border-color: #4a90d9; border-style: solid;
|
||||
animation: ringPulse 1.5s infinite;
|
||||
}
|
||||
.face-frame.success {
|
||||
border-color: #52c41a; border-style: solid; background: rgba(82,196,26,0.08);
|
||||
}
|
||||
|
||||
.face-info {
|
||||
text-align: center; min-height: 48px;
|
||||
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 10px 16px; width: 100%;
|
||||
}
|
||||
.face-info__status { font-size: 12px; color: rgba(255,255,255,0.35); display: block; }
|
||||
.face-info__name { font-size: 15px; font-weight: 700; color: #52c41a; display: none; }
|
||||
.face-info__name.show { display: block; }
|
||||
.face-info__detail { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; display: none; }
|
||||
.face-info__detail.show { display: block; }
|
||||
|
||||
/* 账号密码表单 */
|
||||
.password-section { display: flex; flex-direction: column; gap: 14px; padding: 16px 0; }
|
||||
.password-section.hidden { display: none; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
||||
.form-label { font-size: 12px; color: rgba(255,255,255,0.6); }
|
||||
.form-input {
|
||||
background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px; padding: 12px 14px; font-size: 14px; color: #fff;
|
||||
outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.form-input:focus { border-color: #4a90d9; }
|
||||
.form-input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
.login-error { font-size: 12px; color: #ff4d4f; min-height: 16px; }
|
||||
.login-btn {
|
||||
background: #4a90d9; border: none; border-radius: 8px;
|
||||
padding: 12px; font-size: 14px; font-weight: 600; color: #fff;
|
||||
cursor: pointer; transition: all 0.2s; margin-top: 8px;
|
||||
}
|
||||
.login-btn:hover { background: #5ba0e9; transform: translateY(-1px); }
|
||||
.login-btn:active { transform: translateY(0); }
|
||||
.login-hint { font-size: 11px; color: rgba(255,255,255,0.3); text-align: center; }
|
||||
|
||||
/* 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;
|
||||
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; max-width: 90%;
|
||||
text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--warn { background: rgba(250,173,22,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">餐线营养秤 - 员工登录</span>
|
||||
</div>
|
||||
|
||||
<div class="login-main">
|
||||
<div class="login-panel">
|
||||
<div class="login-panel__title">员工登录</div>
|
||||
<div class="login-panel__subtitle">请选择登录方式</div>
|
||||
|
||||
<div class="login-tabs">
|
||||
<button class="login-tab active" data-tab="face" onclick="switchTab('face')">人脸识别</button>
|
||||
<button class="login-tab" data-tab="account" onclick="switchTab('account')">账号密码</button>
|
||||
</div>
|
||||
|
||||
<!-- 人脸识别区 -->
|
||||
<div class="face-section" id="faceSection">
|
||||
<div class="face-frame" id="faceFrame" onclick="startFaceDetect()">
|
||||
<div class="face-frame__icon" id="faceIcon">👤</div>
|
||||
<div class="face-frame__text" id="faceText">点击激活<br>人脸识别</div>
|
||||
</div>
|
||||
<div class="face-info">
|
||||
<span class="face-info__status" id="faceStatus">识别成功后即可进入功能菜单</span>
|
||||
<div class="face-info__name" id="faceName"></div>
|
||||
<div class="face-info__detail" id="faceDetail"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 账号密码表单 -->
|
||||
<div class="password-section hidden" id="passwordSection">
|
||||
<div class="form-group">
|
||||
<label class="form-label">员工工号</label>
|
||||
<input type="text" class="form-input" id="empId" placeholder="请输入工号、账号名或手机号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">登录密码</label>
|
||||
<input type="password" class="form-input" id="empPassword" placeholder="请输入登录密码">
|
||||
</div>
|
||||
<div class="login-error" id="loginError"></div>
|
||||
<button class="login-btn" onclick="passwordLogin()">登 录</button>
|
||||
<div class="login-hint">演示账号:张三 / 123456</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var loginState = 'idle';
|
||||
var currentUser = null;
|
||||
var loginTab = 'face';
|
||||
|
||||
var mockUsers = [
|
||||
{ name: '张三', id: 'EMP-20260001', dept: '食堂运营部', role: '餐线操作员', account: 'zhangsan', phone: '13800000001', password: '123456' },
|
||||
{ name: '李四', id: 'EMP-20260015', dept: '食堂运营部', role: '食堂主管', account: 'lisi', phone: '13800000002', password: '123456' },
|
||||
{ name: '王五', id: 'EMP-20260023', dept: '营养管理部', role: '营养师', account: 'wangwu', phone: '13800000003', password: '123456' }
|
||||
];
|
||||
|
||||
function switchTab(tab) {
|
||||
loginTab = tab;
|
||||
document.querySelectorAll('.login-tab').forEach(function(el) {
|
||||
el.classList.toggle('active', el.dataset.tab === tab);
|
||||
});
|
||||
document.getElementById('faceSection').classList.toggle('hidden', tab !== 'face');
|
||||
document.getElementById('passwordSection').classList.toggle('hidden', tab !== 'account');
|
||||
document.getElementById('loginError').textContent = '';
|
||||
}
|
||||
|
||||
function startFaceDetect() {
|
||||
if (loginState !== 'idle') return;
|
||||
loginState = 'detecting';
|
||||
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.add('detecting');
|
||||
icon.innerHTML = '🔍';
|
||||
text.textContent = '识别中...';
|
||||
showToast('正在识别人脸...', 1500);
|
||||
|
||||
setTimeout(function() {
|
||||
var user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
|
||||
loginSuccess(user);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function passwordLogin() {
|
||||
var empId = document.getElementById('empId').value.trim();
|
||||
var password = document.getElementById('empPassword').value.trim();
|
||||
var errorEl = document.getElementById('loginError');
|
||||
|
||||
if (!empId || !password) { errorEl.textContent = '请输入工号和密码'; return; }
|
||||
|
||||
var user = null;
|
||||
for (var i = 0; i < mockUsers.length; i++) {
|
||||
var u = mockUsers[i];
|
||||
if ((u.account === empId || u.id === empId || u.phone === empId || u.name === empId) && u.password === password) {
|
||||
user = u; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) { errorEl.textContent = '账号或密码错误,请重试'; return; }
|
||||
loginSuccess(user);
|
||||
}
|
||||
|
||||
function loginSuccess(user) {
|
||||
currentUser = user;
|
||||
loginState = 'success';
|
||||
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.remove('detecting');
|
||||
frame.classList.add('success');
|
||||
icon.innerHTML = '✅';
|
||||
text.innerHTML = user.name;
|
||||
|
||||
document.getElementById('faceStatus').style.display = 'none';
|
||||
var nameEl = document.getElementById('faceName');
|
||||
nameEl.textContent = user.name + ' · ' + user.role;
|
||||
nameEl.classList.add('show');
|
||||
var detailEl = document.getElementById('faceDetail');
|
||||
detailEl.textContent = user.dept + ' · ' + user.id;
|
||||
detailEl.classList.add('show');
|
||||
|
||||
showToast('欢迎,' + user.name + ',正在进入功能菜单...', 1500);
|
||||
setTimeout(function() {
|
||||
window.location.href = '00-welcome.html?user=' + encodeURIComponent(user.name) + '&uid=' + user.id;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'warn') el.classList.add('toast--warn');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,207 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>餐线营养秤 - 功能菜单 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.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); }
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* ---- 主内容:菜单按钮列居中 ---- */
|
||||
.menu-main {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: calc(100% - 44px); padding: 24px 32px;
|
||||
}
|
||||
|
||||
.menu-panel {
|
||||
width: 560px; display: flex; flex-direction: column; gap: 14px;
|
||||
}
|
||||
.menu-panel__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
|
||||
}
|
||||
.menu-panel__title {
|
||||
font-size: 14px; color: rgba(255,255,255,0.4); letter-spacing: 1px;
|
||||
}
|
||||
.menu-panel__user {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 16px 20px; border-radius: 12px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; text-decoration: none; color: #fff;
|
||||
}
|
||||
.menu-btn:hover {
|
||||
transform: translateX(4px); border-color: rgba(74,144,217,0.3);
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
.menu-btn:active { transform: translateX(2px) scale(0.99); }
|
||||
.menu-btn__icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 22px; flex-shrink: 0;
|
||||
}
|
||||
.menu-btn__icon--blue { background: rgba(74,144,217,0.15); }
|
||||
.menu-btn__icon--green { background: rgba(82,196,26,0.15); }
|
||||
.menu-btn__icon--orange { background: rgba(250,173,22,0.15); }
|
||||
.menu-btn__icon--purple { background: rgba(114,46,209,0.15); }
|
||||
.menu-btn__icon--gray { background: rgba(255,255,255,0.08); }
|
||||
.menu-btn__info { flex: 1; }
|
||||
.menu-btn__title { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
|
||||
.menu-btn__desc { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.menu-btn__arrow { font-size: 16px; color: rgba(255,255,255,0.25); flex-shrink: 0; }
|
||||
|
||||
/* 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;
|
||||
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; max-width: 90%;
|
||||
text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-login.html">◀ 返回</a>
|
||||
<span class="top-bar__title">餐线营养秤 - 功能菜单</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="menu-main">
|
||||
<div class="menu-panel">
|
||||
<div class="menu-panel__header">
|
||||
<div class="menu-panel__title">📌 功能菜单</div>
|
||||
<div class="menu-panel__user" id="menuUser"></div>
|
||||
</div>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('01-meal-start.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">🍽️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">开餐界面</div>
|
||||
<div class="menu-btn__desc">开餐操作、今日菜品概览与就餐引导</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('02-dish-display.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--green">📋</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">餐品展示</div>
|
||||
<div class="menu-btn__desc">菜品详情、营养成分与热量展示</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('03-meal-pickup.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--orange">🧾</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">取餐界面</div>
|
||||
<div class="menu-btn__desc">取餐确认、订单生成与营养报告</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('05-dish-sampling.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--purple">📸</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">餐品采样</div>
|
||||
<div class="menu-btn__desc">菜品拍照采集、营养数据录入与 AI 识别</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('06-sampling-history.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">📝</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">采样历史</div>
|
||||
<div class="menu-btn__desc">历史采样记录查看与采集图片管理</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('04-settings.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--gray">⚙️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">设置</div>
|
||||
<div class="menu-btn__desc">环境切换、设备参数配置</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var currentUser = null;
|
||||
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
(function() {
|
||||
var userName = getUrlParam('user');
|
||||
var userId = getUrlParam('uid');
|
||||
if (!userName || !userId) {
|
||||
window.location.href = '00-login.html';
|
||||
return;
|
||||
}
|
||||
currentUser = { name: userName, id: userId };
|
||||
document.getElementById('topUser').textContent = '操作员:' + userName;
|
||||
document.getElementById('menuUser').textContent = '当前用户:' + userName;
|
||||
})();
|
||||
|
||||
function handleMenu(target) {
|
||||
if (!currentUser) {
|
||||
window.location.href = '00-login.html';
|
||||
return;
|
||||
}
|
||||
window.location.href = target + '?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
window.location.href = '00-login.html';
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -165,6 +165,8 @@ body {
|
||||
.action-bar .btn-save { flex: 1.5; padding: 10px; border: none; border-radius: 8px; background: #4a90d9; color: #fff; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s; }
|
||||
.action-bar .btn-save:hover { background: #3d7fc0; }
|
||||
.action-bar .btn-resample { flex: 1; padding: 10px; border: 1px solid rgba(255,193,7,0.3); border-radius: 8px; background: rgba(255,193,7,0.08); color: #ffc107; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
|
||||
.action-bar .btn-reidentify { flex: 1; padding: 10px; border: 1px solid rgba(74,144,217,0.3); border-radius: 8px; background: rgba(74,144,217,0.08); color: #4a90d9; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
|
||||
.action-bar .btn-reidentify:hover { background: rgba(74,144,217,0.18); border-color: rgba(74,144,217,0.5); }
|
||||
.action-bar .btn-resample:hover { background: rgba(255,193,7,0.15); border-color: rgba(255,193,7,0.5); }
|
||||
.action-bar .btn-cancel { flex: 1; padding: 10px; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.7); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
|
||||
.action-bar .btn-cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
@@ -255,6 +257,7 @@ body {
|
||||
<!-- 底部操作按钮 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn-cancel" id="btnCancel">取消</button>
|
||||
<button class="btn-reidentify" id="btnReidentify" onclick="handleReidentify()">🔄 重新识别</button>
|
||||
<button class="btn-resample" id="btnResample">↻ 重新采样</button>
|
||||
<button class="btn-save" id="btnSave">保存采集</button>
|
||||
<button class="btn-save-continue" id="btnSaveContinue">保存并继续</button>
|
||||
@@ -468,6 +471,26 @@ function updateSelectedDishInfo() {
|
||||
selectedDescEl.innerHTML = selectedDish.catLabel + ' · ' + selectedDish.mainIngredient + ' · ' + selectedDish.kcal + 'kcal/100g';
|
||||
}
|
||||
|
||||
/* ===== 重新识别:清除当前图片,重置摄像头,重新拍照识别 ===== */
|
||||
function handleReidentify() {
|
||||
if (!selectedDish) {
|
||||
showToast('⚠ 请先选择餐品', 1500);
|
||||
return;
|
||||
}
|
||||
// 清除已采集图片
|
||||
capturedImages = [];
|
||||
lastAddedIndex = -1;
|
||||
renderResultGrid();
|
||||
// 重置摄像头预览
|
||||
cameraPreviewEl.style.display = 'none';
|
||||
cameraTextEl.style.display = '';
|
||||
cameraTextEl.innerHTML = '摄像头预览区域';
|
||||
cameraFrameEl.classList.remove('active');
|
||||
btnPhotoEl.classList.remove('shooting');
|
||||
btnPhotoEl.innerHTML = '📸<br>拍照采集';
|
||||
showToast('✅ 已重置,请重新拍照识别', 1500);
|
||||
}
|
||||
|
||||
/* ===== Toast ===== */
|
||||
function showToast(msg, duration) {
|
||||
toastEl.textContent = msg;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# 餐线营养秤 - 变更记录
|
||||
|
||||
## [2026-06-15] feat: 新增重新识别按钮
|
||||
|
||||
### 新增
|
||||
- **重新识别按钮**(`05-dish-sampling.html`):底部操作栏新增「🔄 重新识别」按钮(位于"取消"与"重新采样"之间),点击后清空当前已采图片、重置摄像头状态,保留餐品选中状态,可重新拍照识别。
|
||||
|
||||
---
|
||||
|
||||
## [2026-06-12] feat: 同步父级 canteen-device 导航页 PAD 外壳改造 + 根导航挂载
|
||||
|
||||
**变更背景**:父级"餐线设备"导航页与营养秤系列保持视觉一致;同时把本模块 9 个完成页面挂载至根目录 `index.html` 的"其他终端 → 餐厅终端设备"分组,使卡片显示"已完成"绿色边框。
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
│ └─ 重量 = 0 → "拍照"和"保存"按钮禁用
|
||||
│
|
||||
├─ 右侧:摄像头拍照采集(最多9张)
|
||||
│ │
|
||||
│ └─ 点击「重新识别」→ 清空已采图片与摄像头状态,保留餐品选中,可重新拍照
|
||||
│
|
||||
└─ 保存采集 → 携带 sampleDishId 返回 01-开餐界面
|
||||
│
|
||||
|
||||
@@ -278,6 +278,8 @@ stateDiagram-v2
|
||||
│
|
||||
├─ 点击「取消」→ Toast 反馈 → 跳转至 02-待取餐界面
|
||||
│
|
||||
├─ 点击「重新识别」→ 清空当前已采图片与摄像头状态,保留餐品选中状态,Toast 反馈,可重新拍照识别
|
||||
│
|
||||
├─ 点击「重新采样」→ 删除该餐品全部已采图片,清空表单,保留餐品选中状态,Toast 反馈
|
||||
│
|
||||
├─ 点击「保存采集」→ 提交数据 → 跳转至 02-待取餐界面(携带 dishId)
|
||||
@@ -308,6 +310,7 @@ stateDiagram-v2
|
||||
| 按钮 | 正常采样模式 | 补充采集模式 | 行为 |
|
||||
|------|------------|------------|------|
|
||||
| 取消 | 显示 | 显示 | 清空当前操作状态 |
|
||||
| 重新识别 | 显示 | 显示 | 清空已采图片与摄像头状态,保留餐品选中,可重新拍照识别 |
|
||||
| 重新采样 | 显示 | 显示 | 清空该餐品全部已采图片 |
|
||||
| 保存采集 | 显示 | 显示 | 提交数据 → 跳转至 02-待取餐界面 |
|
||||
| 保存并继续 | 显示 | **隐藏** | 提交数据 → 重置表单继续采集下一个餐品 |
|
||||
|
||||
@@ -94,6 +94,16 @@
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<a class="card" href="00-login.html">
|
||||
<span class="card__icon">🔐</span>
|
||||
<div class="card__title">员工登录</div>
|
||||
<div class="card__desc">人脸识别、账号密码登录</div>
|
||||
</a>
|
||||
<a class="card" href="00-welcome.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__title">功能菜单</div>
|
||||
<div class="card__desc">开餐、餐品展示、取餐、采样、设置</div>
|
||||
</a>
|
||||
<a class="card" href="01-meal-start.html">
|
||||
<span class="card__icon">🍽️</span>
|
||||
<div class="card__title">开餐界面</div>
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜加工秤 - 欢迎 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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);
|
||||
}
|
||||
.welcome-main {
|
||||
display: flex; align-items: stretch;
|
||||
height: calc(100% - 44px); padding: 24px 32px; gap: 32px;
|
||||
}
|
||||
.login-panel {
|
||||
flex: 0 0 420px; display: flex; flex-direction: column; gap: 20px;
|
||||
background: #1a2b4a; border-radius: 16px; padding: 24px;
|
||||
}
|
||||
.login-panel__title {
|
||||
font-size: 16px; font-weight: 600; color: rgba(255,255,255,0.8); text-align: center;
|
||||
}
|
||||
.login-tabs {
|
||||
display: flex; gap: 0; border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.login-tab {
|
||||
flex: 1; padding: 10px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.5);
|
||||
cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s;
|
||||
}
|
||||
.login-tab:hover { color: rgba(255,255,255,0.8); }
|
||||
.login-tab.active { color: #4a90d9; border-bottom-color: #4a90d9; }
|
||||
.face-section {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 20px 0;
|
||||
}
|
||||
.face-section.hidden { display: none; }
|
||||
.face-frame {
|
||||
width: 180px; height: 180px; border-radius: 50%;
|
||||
border: 3px dashed rgba(74,144,217,0.4);
|
||||
display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px;
|
||||
position: relative; background: rgba(26,43,74,0.3);
|
||||
cursor: pointer; transition: all 0.3s;
|
||||
}
|
||||
.face-frame:hover { border-color: #4a90d9; background: rgba(74,144,217,0.1); }
|
||||
.face-frame__icon { font-size: 40px; color: rgba(255,255,255,0.3); }
|
||||
.face-frame__text { font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; }
|
||||
@keyframes ringPulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.3); }
|
||||
50% { box-shadow: 0 0 0 12px rgba(74,144,217,0); }
|
||||
}
|
||||
.face-frame.detecting { border-color: #4a90d9; border-style: solid; animation: ringPulse 1.5s infinite; }
|
||||
.face-frame.success { border-color: #52c41a; border-style: solid; background: rgba(82,196,26,0.08); }
|
||||
.face-info {
|
||||
text-align: center; min-height: 48px;
|
||||
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 10px 16px; width: 100%;
|
||||
}
|
||||
.face-info__status { font-size: 12px; color: rgba(255,255,255,0.35); display: block; }
|
||||
.face-info__name { font-size: 15px; font-weight: 700; color: #52c41a; display: none; }
|
||||
.face-info__name.show { display: block; }
|
||||
.face-info__detail { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; display: none; }
|
||||
.face-info__detail.show { display: block; }
|
||||
.password-section { display: flex; flex-direction: column; gap: 14px; padding: 16px 0; }
|
||||
.password-section.hidden { display: none; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
||||
.form-label { font-size: 12px; color: rgba(255,255,255,0.6); }
|
||||
.form-input {
|
||||
background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px; padding: 12px 14px; font-size: 14px; color: #fff;
|
||||
outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.form-input:focus { border-color: #4a90d9; }
|
||||
.form-input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
.login-btn {
|
||||
background: #4a90d9; border: none; border-radius: 8px;
|
||||
padding: 12px; font-size: 14px; font-weight: 600; color: #fff;
|
||||
cursor: pointer; transition: all 0.2s; margin-top: 8px;
|
||||
}
|
||||
.login-btn:hover { background: #5ba0e9; transform: translateY(-1px); }
|
||||
.menu-panel {
|
||||
flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 14px;
|
||||
}
|
||||
.menu-panel__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
|
||||
}
|
||||
.menu-panel__title { font-size: 14px; color: rgba(255,255,255,0.4); letter-spacing: 1px; }
|
||||
.menu-panel__user { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.menu-panel__user.show { display: block; }
|
||||
.menu-panel__user.hidden { display: none; }
|
||||
.menu-btn {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 16px 20px; border-radius: 12px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; text-decoration: none; color: #fff;
|
||||
}
|
||||
.menu-btn:hover {
|
||||
transform: translateX(4px); border-color: rgba(74,144,217,0.3);
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
.menu-btn:active { transform: translateX(2px) scale(0.99); }
|
||||
.menu-btn.disabled {
|
||||
opacity: 0.4; cursor: not-allowed; transform: none !important;
|
||||
box-shadow: none !important; border-color: transparent !important;
|
||||
}
|
||||
.menu-btn__icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 22px; flex-shrink: 0;
|
||||
}
|
||||
.menu-btn__icon--blue { background: rgba(74,144,217,0.15); }
|
||||
.menu-btn__icon--green { background: rgba(82,196,26,0.15); }
|
||||
.menu-btn__icon--orange { background: rgba(250,173,22,0.15); }
|
||||
.menu-btn__icon--purple { background: rgba(114,46,209,0.15); }
|
||||
.menu-btn__icon--gray { background: rgba(255,255,255,0.08); }
|
||||
.menu-btn__info { flex: 1; }
|
||||
.menu-btn__title { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
|
||||
.menu-btn__desc { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.menu-btn__arrow { font-size: 16px; color: rgba(255,255,255,0.25); flex-shrink: 0; }
|
||||
.toast {
|
||||
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
|
||||
background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none; max-width: 90%; text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--warn { background: rgba(250,173,22,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">净菜加工秤</span>
|
||||
</div>
|
||||
<div class="welcome-main">
|
||||
<div class="login-panel">
|
||||
<div class="login-panel__title">员工登录</div>
|
||||
<div class="login-tabs">
|
||||
<div class="login-tab active" data-tab="face" onclick="switchTab('face')">人脸识别</div>
|
||||
<div class="login-tab" data-tab="password" onclick="switchTab('password')">账号密码</div>
|
||||
</div>
|
||||
<div class="face-section" id="faceSection">
|
||||
<div class="face-frame" id="faceFrame" onclick="startFaceDetect()">
|
||||
<div class="face-frame__icon" id="faceIcon">👤</div>
|
||||
<div class="face-frame__text" id="faceText">点击激活<br>人脸识别</div>
|
||||
</div>
|
||||
<div class="face-info">
|
||||
<span class="face-info__status" id="faceStatus">识别成功后即可使用各项功能</span>
|
||||
<div class="face-info__name" id="faceName"></div>
|
||||
<div class="face-info__detail" id="faceDetail"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="password-section hidden" id="passwordSection">
|
||||
<div class="form-group">
|
||||
<label class="form-label">员工工号</label>
|
||||
<input type="text" class="form-input" id="empId" placeholder="请输入员工工号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">登录密码</label>
|
||||
<input type="password" class="form-input" id="empPassword" placeholder="请输入登录密码">
|
||||
</div>
|
||||
<button class="login-btn" onclick="passwordLogin()">登录</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-panel">
|
||||
<div class="menu-panel__header">
|
||||
<div class="menu-panel__title">📌 功能菜单</div>
|
||||
<div class="menu-panel__user hidden" id="menuUser"></div>
|
||||
</div>
|
||||
<a class="menu-btn disabled" id="btnTasks" onclick="handleMenu('01-cutting-tasks.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">📋</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">今日任务</div>
|
||||
<div class="menu-btn__desc">查看系统分配的切配任务清单</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
<a class="menu-btn disabled" id="btnWeighing" onclick="handleMenu('02-weighing.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--green">⚖️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">称重录入</div>
|
||||
<div class="menu-btn__desc">净菜上秤识别、称重、确认</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
<a class="menu-btn disabled" id="btnHistory" onclick="handleMenu('04-history.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--orange">📝</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">切配记录</div>
|
||||
<div class="menu-btn__desc">历史切配记录查看与筛选</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
<a class="menu-btn disabled" id="btnSettings" onclick="handleMenu('05-settings.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--gray">⚙️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">设备设置</div>
|
||||
<div class="menu-btn__desc">设备参数配置与状态查看</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
var loginState = 'idle';
|
||||
var currentUser = null;
|
||||
var loginTab = 'face';
|
||||
var mockUsers = [
|
||||
{ name: '赵六', id: 'EMP-20260030', dept: '加工车间', role: '切配员', empId: '20001', password: '123456' },
|
||||
{ name: '孙七', id: 'EMP-20260035', dept: '加工车间', role: '切配员', empId: '20002', password: '123456' },
|
||||
{ name: '周八', id: 'EMP-20260040', dept: '质检部', role: '质检员', empId: '20003', password: '123456' }
|
||||
];
|
||||
var menuButtons = ['btnTasks', 'btnWeighing', 'btnHistory', 'btnSettings'];
|
||||
|
||||
function switchTab(tab) {
|
||||
loginTab = tab;
|
||||
document.querySelectorAll('.login-tab').forEach(function(el) {
|
||||
el.classList.toggle('active', el.dataset.tab === tab);
|
||||
});
|
||||
document.getElementById('faceSection').classList.toggle('hidden', tab !== 'face');
|
||||
document.getElementById('passwordSection').classList.toggle('hidden', tab !== 'password');
|
||||
}
|
||||
|
||||
function startFaceDetect() {
|
||||
if (loginState !== 'idle') return;
|
||||
loginState = 'detecting';
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.add('detecting');
|
||||
icon.innerHTML = '🔍';
|
||||
text.textContent = '识别中...';
|
||||
showToast('正在识别人脸...', 1500);
|
||||
setTimeout(function() {
|
||||
var user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
|
||||
loginSuccess(user);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function passwordLogin() {
|
||||
var empId = document.getElementById('empId').value.trim();
|
||||
var password = document.getElementById('empPassword').value.trim();
|
||||
if (!empId || !password) {
|
||||
showToast('请输入工号和密码', 2000, 'warn');
|
||||
return;
|
||||
}
|
||||
var user = mockUsers.find(function(u) { return u.empId === empId && u.password === password; });
|
||||
if (!user) {
|
||||
showToast('工号或密码错误', 2000, 'error');
|
||||
return;
|
||||
}
|
||||
loginSuccess(user);
|
||||
}
|
||||
|
||||
function loginSuccess(user) {
|
||||
currentUser = user;
|
||||
loginState = 'success';
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.remove('detecting');
|
||||
frame.classList.add('success');
|
||||
icon.innerHTML = '✅';
|
||||
text.innerHTML = user.name;
|
||||
document.getElementById('faceStatus').style.display = 'none';
|
||||
var nameEl = document.getElementById('faceName');
|
||||
nameEl.textContent = user.name + ' · ' + user.role;
|
||||
nameEl.classList.add('show');
|
||||
var detailEl = document.getElementById('faceDetail');
|
||||
detailEl.textContent = user.dept + ' · ' + user.id;
|
||||
detailEl.classList.add('show');
|
||||
var menuUser = document.getElementById('menuUser');
|
||||
menuUser.textContent = '当前用户:' + user.name;
|
||||
menuUser.classList.remove('hidden');
|
||||
menuButtons.forEach(function(id) {
|
||||
document.getElementById(id).classList.remove('disabled');
|
||||
});
|
||||
showToast('欢迎,' + user.name, 2000);
|
||||
}
|
||||
|
||||
function handleMenu(target) {
|
||||
if (loginState !== 'success' || !currentUser) {
|
||||
showToast('请先登录', 2000, 'warn');
|
||||
return;
|
||||
}
|
||||
window.location.href = target + '?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'warn') el.classList.add('toast--warn');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,601 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜加工秤 - 今日任务 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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 { height: calc(100% - 44px); padding: 20px 28px; display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
/* 状态标签卡(统计+筛选二合一) */
|
||||
.tasks-tabs { display: flex; gap: 10px; }
|
||||
.status-tab {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border-radius: 8px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; user-select: none;
|
||||
}
|
||||
.status-tab:hover { border-color: rgba(255,255,255,0.15); }
|
||||
.status-tab.active--pending { background: rgba(250,173,22,0.15); border-color: rgba(250,173,22,0.4); }
|
||||
.status-tab.active--done { background: rgba(82,196,26,0.15); border-color: rgba(82,196,26,0.4); }
|
||||
.status-tab__label { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.7); }
|
||||
.status-tab__count { font-size: 20px; font-weight: 700; color: #fff; }
|
||||
|
||||
.tasks-section { flex: 1; display: flex; flex-direction: column; gap: 12px; overflow: hidden; }
|
||||
.task-list {
|
||||
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
|
||||
padding-right: 4px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.task-list::-webkit-scrollbar { width: 4px; }
|
||||
.task-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.task-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.task-item {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
border: 1px solid rgba(255,255,255,0.06); cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.task-item:hover { border-color: rgba(74,144,217,0.3); box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
|
||||
.task-icon {
|
||||
width: 52px; height: 52px; border-radius: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 26px; flex-shrink: 0;
|
||||
}
|
||||
.task-icon--potato { background: rgba(250,173,22,0.15); }
|
||||
.task-icon--tomato { background: rgba(255,77,79,0.15); }
|
||||
.task-icon--cabbage { background: rgba(82,196,26,0.15); }
|
||||
.task-icon--carrot { background: rgba(250,140,22,0.15); }
|
||||
.task-icon--cucumber { background: rgba(20,184,166,0.15); }
|
||||
.task-info { flex: 1; display: flex; flex-direction: column; gap: 4px; }
|
||||
.task-name { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.task-detail { font-size: 12px; color: rgba(255,255,255,0.5); display: flex; gap: 12px; }
|
||||
.task-detail__item { display: flex; align-items: center; gap: 4px; }
|
||||
.task-cutting {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 4px 10px; background: rgba(114,46,209,0.15); border-radius: 4px;
|
||||
font-size: 11px; color: #722ed1;
|
||||
}
|
||||
.task-progress { width: 140px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.progress-bar { height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; }
|
||||
.progress-bar__fill { height: 100%; border-radius: 3px; }
|
||||
.progress-bar__fill--blue { background: #4a90d9; }
|
||||
.progress-bar__fill--green { background: #52c41a; }
|
||||
.progress-text { font-size: 11px; color: rgba(255,255,255,0.5); text-align: right; }
|
||||
.task-status { width: 80px; text-align: center; }
|
||||
.status-badge { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
||||
.status-badge--pending { background: rgba(250,173,22,0.15); color: #faad16; }
|
||||
.status-badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.task-action { width: 80px; }
|
||||
.action-btn {
|
||||
padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 500;
|
||||
background: #4a90d9; color: #fff; border: none; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.action-btn:hover { background: #5ba0e9; }
|
||||
.action-btn--done { background: rgba(82,196,26,0.15); color: #52c41a; cursor: default; }
|
||||
|
||||
/* 弹窗遮罩层 */
|
||||
.modal-overlay {
|
||||
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.6); z-index: 900;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
}
|
||||
.modal-overlay.open { display: flex; }
|
||||
.modal-box {
|
||||
width: 1220px; height: 660px; background: #0e1326; border-radius: 16px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
box-shadow: 0 24px 64px rgba(0,0,0,0.7);
|
||||
}
|
||||
.modal-header {
|
||||
display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px;
|
||||
}
|
||||
.modal-close {
|
||||
font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); transition: color 0.2s;
|
||||
background: none; border: none;
|
||||
}
|
||||
.modal-close:hover { color: #fff; }
|
||||
.modal-title { flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); }
|
||||
.modal-body { flex: 1; display: flex; gap: 20px; padding: 16px 20px; overflow: hidden; }
|
||||
.modal-scale-panel { flex: 0 0 540px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.modal-task { background: #1a2b4a; border-radius: 12px; padding: 12px 16px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.modal-task__body { display: flex; align-items: center; gap: 12px; }
|
||||
.modal-task__icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 22px; }
|
||||
.modal-task__info { flex: 1; }
|
||||
.modal-task__name { font-size: 15px; font-weight: 600; color: #fff; margin-bottom: 2px; }
|
||||
.modal-task__cutting { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: rgba(114,46,209,0.15); border-radius: 4px; font-size: 11px; color: #722ed1; }
|
||||
.modal-task__progress { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
||||
.progress-mini { width: 80px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
|
||||
.progress-mini__fill { height: 100%; background: #4a90d9; border-radius: 2px; }
|
||||
.progress-mini__text { font-size: 11px; color: rgba(255,255,255,0.5); }
|
||||
.scale-display { flex: 1; background: #1a2b4a; border-radius: 14px; padding: 18px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; border: 2px solid rgba(255,255,255,0.06); }
|
||||
.scale-display.weighing { border-color: #4a90d9; }
|
||||
.scale-display.stable { border-color: #52c41a; }
|
||||
.ai-result { display: flex; align-items: center; gap: 14px; padding: 14px 18px; background: rgba(0,0,0,0.2); border-radius: 10px; width: 100%; }
|
||||
.ai-result__icon { width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 26px; background: rgba(74,144,217,0.15); }
|
||||
.ai-result__info { flex: 1; }
|
||||
.ai-result__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 2px; }
|
||||
.ai-result__name { font-size: 18px; font-weight: 700; color: #fff; }
|
||||
.ai-result__confidence { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
.ai-result__status { font-size: 12px; padding: 4px 10px; border-radius: 4px; }
|
||||
.ai-result__status--matching { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.ai-result__status--mismatch { background: rgba(255,77,79,0.15); color: #ff4d4f; }
|
||||
.ai-result__status--idle { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); }
|
||||
.weight-display { text-align: center; }
|
||||
.weight-display__value { font-size: 60px; font-weight: 700; color: #fff; line-height: 1; font-family: "Courier New", monospace; }
|
||||
.weight-display__unit { font-size: 18px; color: rgba(255,255,255,0.5); margin-left: 4px; }
|
||||
.weight-display__status { font-size: 13px; color: rgba(255,255,255,0.4); margin-top: 6px; }
|
||||
.weight-display__status.weighing { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
.scale-actions { display: flex; gap: 12px; width: 100%; }
|
||||
.scale-btn { flex: 1; padding: 12px; border-radius: 10px; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.scale-btn--primary { background: #4a90d9; color: #fff; }
|
||||
.scale-btn--primary:hover { background: #5ba0e9; }
|
||||
.scale-btn--primary:disabled { background: rgba(74,144,217,0.3); cursor: not-allowed; }
|
||||
.scale-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.scale-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
.modal-records-panel { flex: 1; display: flex; flex-direction: column; gap: 12px; }
|
||||
.modal-records { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 14px 16px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.modal-records__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
||||
.modal-records__title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.modal-records__count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.modal-records__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; }
|
||||
.modal-records__list::-webkit-scrollbar { width: 4px; }
|
||||
.modal-records__list::-webkit-scrollbar-track { background: transparent; }
|
||||
.modal-records__list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.modal-record { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: rgba(0,0,0,0.15); border-radius: 8px; }
|
||||
.modal-record__icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; background: rgba(250,173,22,0.15); }
|
||||
.modal-record__info { flex: 1; }
|
||||
.modal-record__name { font-size: 12px; font-weight: 500; color: #fff; }
|
||||
.modal-record__cutting { font-size: 11px; color: #722ed1; }
|
||||
.modal-record__time { font-size: 10px; color: rgba(255,255,255,0.3); }
|
||||
.modal-record__weight { font-size: 13px; font-weight: 600; color: #4a90d9; }
|
||||
.modal-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: rgba(255,255,255,0.3); }
|
||||
.modal-empty__icon { font-size: 28px; margin-bottom: 8px; }
|
||||
.modal-empty__text { font-size: 12px; }
|
||||
|
||||
.toast {
|
||||
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
|
||||
background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px;
|
||||
font-size: 13px; z-index: 1100; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">今日切配任务</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<!-- 状态标签卡(统计+筛选) -->
|
||||
<div class="tasks-tabs">
|
||||
<div class="status-tab active--pending" data-filter="pending" onclick="filterTasks('pending', this)">
|
||||
<span class="status-tab__label">待处理</span>
|
||||
<span class="status-tab__count" id="pendingCount">3</span>
|
||||
</div>
|
||||
<div class="status-tab" data-filter="done" onclick="filterTasks('done', this)">
|
||||
<span class="status-tab__label">已完成</span>
|
||||
<span class="status-tab__count" id="doneCount">2</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tasks-section">
|
||||
<div class="task-list" id="taskList">
|
||||
<!-- 任务项1 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--potato">🥔</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">土豆</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 需求: 5.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-cutting">🔪 切丝</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill progress-bar__fill--blue" style="width: 40%"></div>
|
||||
</div>
|
||||
<div class="progress-text">已切 2.0 / 需 5.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status">
|
||||
<span class="status-badge status-badge--pending">待处理</span>
|
||||
</div>
|
||||
<div class="task-action">
|
||||
<button class="action-btn" onclick="openModal('potato')">去处理</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 任务项2 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--carrot">🥕</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">胡萝卜</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 需求: 2.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-cutting">🔪 切丝</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill progress-bar__fill--blue" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="progress-text">已切 0 / 需 2.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status">
|
||||
<span class="status-badge status-badge--pending">待处理</span>
|
||||
</div>
|
||||
<div class="task-action">
|
||||
<button class="action-btn" onclick="openModal('carrot')">去处理</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 任务项3 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--cucumber">🥒</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">黄瓜</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 需求: 4.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 10:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-cutting">🔪 切片</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div>
|
||||
</div>
|
||||
<div class="progress-text">已切 4.2 / 需 4.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status">
|
||||
<span class="status-badge status-badge--done">已完成</span>
|
||||
</div>
|
||||
<div class="task-action">
|
||||
<button class="action-btn action-btn--done">完成</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 任务项4 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--potato">🥔</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">土豆(晚餐)</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 需求: 3.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 15:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-cutting">🔪 切块</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill progress-bar__fill--blue" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="progress-text">已切 0 / 需 3.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status">
|
||||
<span class="status-badge status-badge--pending">待处理</span>
|
||||
</div>
|
||||
<div class="task-action">
|
||||
<button class="action-btn" onclick="openModal('potato_dinner')">去处理</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 任务项5 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--cabbage">🥬</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">大白菜</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 需求: 8.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 10:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-cutting">🔪 切段</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div>
|
||||
</div>
|
||||
<div class="progress-text">已切 8.5 / 需 8.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status">
|
||||
<span class="status-badge status-badge--done">已完成</span>
|
||||
</div>
|
||||
<div class="task-action">
|
||||
<button class="action-btn action-btn--done">完成</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 任务项6 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--tomato">🍅</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">西红柿</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 需求: 3.5斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-cutting">🔪 切块</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar__fill progress-bar__fill--blue" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="progress-text">已切 0 / 需 3.5 斤</div>
|
||||
</div>
|
||||
<div class="task-status">
|
||||
<span class="status-badge status-badge--pending">待处理</span>
|
||||
</div>
|
||||
<div class="task-action">
|
||||
<button class="action-btn" onclick="openModal('tomato')">去处理</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 处理弹窗 -->
|
||||
<div class="modal-overlay" id="modalOverlay">
|
||||
<div class="modal-box">
|
||||
<div class="modal-header">
|
||||
<button class="modal-close" onclick="closeModal()">◀ 返回任务列表</button>
|
||||
<span class="modal-title" id="modalTitle">称重录入</span>
|
||||
<span style="width:80px"></span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-scale-panel">
|
||||
<div class="modal-task">
|
||||
<div class="modal-task__body">
|
||||
<div class="modal-task__icon task-icon--potato" id="modalTaskIcon">🥔</div>
|
||||
<div class="modal-task__info">
|
||||
<div class="modal-task__name" id="modalTaskName">土豆</div>
|
||||
<span class="modal-task__cutting" id="modalTaskCutting">🔪 切丝</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-task__progress">
|
||||
<div class="progress-mini">
|
||||
<div class="progress-mini__fill" id="modalProgressFill" style="width:40%"></div>
|
||||
</div>
|
||||
<span class="progress-mini__text" id="modalProgressText">2.0/5.0斤</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-display" id="scaleDisplay">
|
||||
<div class="ai-result">
|
||||
<div class="ai-result__icon" id="aiIcon">📇</div>
|
||||
<div class="ai-result__info">
|
||||
<div class="ai-result__label">AI 识别结果</div>
|
||||
<div class="ai-result__name" id="aiName">等待放置食材...</div>
|
||||
<div class="ai-result__confidence" id="aiConfidence"></div>
|
||||
</div>
|
||||
<span class="ai-result__status ai-result__status--idle" id="aiStatus">待机</span>
|
||||
</div>
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请将食材放置秤上</div>
|
||||
</div>
|
||||
<div class="scale-actions">
|
||||
<button class="scale-btn scale-btn--secondary" onclick="resetScale()">清零</button>
|
||||
<button class="scale-btn scale-btn--primary" id="confirmBtn" onclick="confirmCutting()" disabled>确认并切配</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-records-panel">
|
||||
<div class="modal-records">
|
||||
<div class="modal-records__header">
|
||||
<span class="modal-records__title">处理记录</span>
|
||||
<span class="modal-records__count" id="recordsCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="modal-records__list" id="recordsList">
|
||||
<div class="modal-empty" id="emptyState">
|
||||
<div class="modal-empty__icon">📭</div>
|
||||
<div class="modal-empty__text">暂无处理记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// 筛选任务
|
||||
function filterTasks(status, tab) {
|
||||
document.querySelectorAll('.status-tab').forEach(function(el) {
|
||||
el.classList.remove('active--pending', 'active--done');
|
||||
});
|
||||
tab.classList.add(status === 'pending' ? 'active--pending' : 'active--done');
|
||||
document.querySelectorAll('.task-item').forEach(function(item) {
|
||||
item.style.display = (item.dataset.status === status) ? 'flex' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// ── 弹窗逻辑 ──
|
||||
var modalKey = '';
|
||||
var scaleState = 'idle';
|
||||
var currentWeight = 0;
|
||||
var currentAiResult = null;
|
||||
var records = [];
|
||||
|
||||
var taskData = {
|
||||
'potato': { name: '土豆', icon: '🥔', iconClass: 'task-icon--potato', need: 5.0, done: 2.0, deadline: '11:00', cutting: '切丝' },
|
||||
'carrot': { name: '胡萝卜', icon: '🥕', iconClass: 'task-icon--carrot', need: 2.0, done: 0, deadline: '11:30', cutting: '切丝' },
|
||||
'tomato': { name: '西红柿', icon: '🍅', iconClass: 'task-icon--tomato', need: 3.5, done: 0, deadline: '11:30', cutting: '切块' },
|
||||
'potato_dinner': { name: '土豆(晚餐)', icon: '🥔', iconClass: 'task-icon--potato', need: 3.0, done: 0, deadline: '15:30', cutting: '切块' }
|
||||
};
|
||||
|
||||
var aiResults = {
|
||||
'potato': { name: '土豆', icon: '🥔', confidence: 97 },
|
||||
'carrot': { name: '胡萝卜', icon: '🥕', confidence: 96 },
|
||||
'tomato': { name: '西红柿', icon: '🍅', confidence: 95 },
|
||||
'cabbage': { name: '大白菜', icon: '🥬', confidence: 94 },
|
||||
'cucumber':{ name: '黄瓜', icon: '🥒', confidence: 93 }
|
||||
};
|
||||
|
||||
function openModal(key) {
|
||||
modalKey = key;
|
||||
var task = taskData[key] || taskData['potato'];
|
||||
var iconEl = document.getElementById('modalTaskIcon');
|
||||
iconEl.innerHTML = task.icon;
|
||||
iconEl.className = 'modal-task__icon ' + task.iconClass;
|
||||
document.getElementById('modalTaskName').textContent = task.name;
|
||||
document.getElementById('modalTaskCutting').innerHTML = '🔪 ' + task.cutting;
|
||||
var pct = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('modalProgressFill').style.width = pct + '%';
|
||||
document.getElementById('modalProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
document.getElementById('modalTitle').textContent = task.name + ' - 称重录入';
|
||||
resetScale();
|
||||
records = [];
|
||||
updateRecordsDisplay();
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
setTimeout(function() { startDetecting(key.replace('_dinner','')); }, 1500);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').classList.remove('open');
|
||||
}
|
||||
|
||||
function startDetecting(ingredientKey) {
|
||||
scaleState = 'detecting';
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.add('weighing');
|
||||
scale.classList.remove('stable');
|
||||
document.getElementById('weightStatus').textContent = '正在称重...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status weighing';
|
||||
var targetWeight = 1.85;
|
||||
var anim = 0;
|
||||
var timer = setInterval(function() {
|
||||
anim += 0.12;
|
||||
if (anim >= targetWeight) { anim = targetWeight; clearInterval(timer); onWeightStable(ingredientKey, targetWeight); }
|
||||
document.getElementById('weightValue').textContent = anim.toFixed(2);
|
||||
}, 100);
|
||||
setTimeout(function() {
|
||||
var result = aiResults[ingredientKey] || aiResults['potato'];
|
||||
currentAiResult = result;
|
||||
document.getElementById('aiIcon').innerHTML = result.icon;
|
||||
document.getElementById('aiName').textContent = result.name;
|
||||
document.getElementById('aiConfidence').textContent = '置信度: ' + result.confidence + '%';
|
||||
var task = taskData[modalKey];
|
||||
var isMatch = task.name === result.name || task.name.includes(result.name);
|
||||
var statusEl = document.getElementById('aiStatus');
|
||||
if (isMatch) {
|
||||
statusEl.textContent = '匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--matching';
|
||||
} else {
|
||||
statusEl.textContent = '不匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--mismatch';
|
||||
showToast('AI识别不匹配,请检查食材', 2500, 'error');
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function onWeightStable(key, weight) {
|
||||
scaleState = 'stable';
|
||||
currentWeight = weight;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing');
|
||||
scale.classList.add('stable');
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
document.getElementById('confirmBtn').disabled = false;
|
||||
}
|
||||
|
||||
function confirmCutting() {
|
||||
if (scaleState !== 'stable' || !currentAiResult) return;
|
||||
var task = taskData[modalKey];
|
||||
task.done += currentWeight;
|
||||
records.unshift({
|
||||
name: currentAiResult.name,
|
||||
icon: currentAiResult.icon,
|
||||
cutting: task.cutting,
|
||||
weight: currentWeight,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
});
|
||||
updateRecordsDisplay();
|
||||
var pct = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('modalProgressFill').style.width = pct + '%';
|
||||
document.getElementById('modalProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
showToast('已确认切配: ' + currentAiResult.name + ' ' + currentWeight.toFixed(2) + '斤', 2000, 'success');
|
||||
resetScale();
|
||||
}
|
||||
|
||||
function updateRecordsDisplay() {
|
||||
var list = document.getElementById('recordsList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
if (records.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('recordsCount').textContent = '共 0 条';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
document.getElementById('recordsCount').textContent = '共 ' + records.length + ' 条';
|
||||
var existing = list.querySelectorAll('.modal-record');
|
||||
existing.forEach(function(el) { el.remove(); });
|
||||
records.forEach(function(r) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'modal-record';
|
||||
item.innerHTML = '<div class="modal-record__icon">' + r.icon + '</div>' +
|
||||
'<div class="modal-record__info"><div class="modal-record__name">' + r.name + '</div>' +
|
||||
'<div class="modal-record__cutting">' + r.cutting + '</div>' +
|
||||
'<div class="modal-record__time">' + r.time + '</div></div>' +
|
||||
'<div class="modal-record__weight">+' + r.weight.toFixed(2) + '斤</div>';
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function resetScale() {
|
||||
scaleState = 'idle';
|
||||
currentWeight = 0;
|
||||
currentAiResult = null;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing', 'stable');
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请将食材放置秤上';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('aiIcon').innerHTML = '📇';
|
||||
document.getElementById('aiName').textContent = '等待放置食材...';
|
||||
document.getElementById('aiConfidence').textContent = '';
|
||||
document.getElementById('aiStatus').textContent = '待机';
|
||||
document.getElementById('aiStatus').className = 'ai-result__status ai-result__status--idle';
|
||||
document.getElementById('confirmBtn').disabled = true;
|
||||
setTimeout(function() {
|
||||
if (scaleState === 'idle' && document.getElementById('modalOverlay').classList.contains('open')) {
|
||||
var keys = ['potato', 'carrot', 'cucumber'];
|
||||
startDetecting(keys[Math.floor(Math.random() * keys.length)]);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,357 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜加工秤 - 称重录入 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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 { height: calc(100% - 44px); padding: 20px 28px; display: flex; gap: 24px; }
|
||||
.scale-panel { flex: 0 0 560px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.current-task { background: #1a2b4a; border-radius: 12px; padding: 16px 20px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.current-task__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.current-task__label { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.current-task__switch { font-size: 12px; color: #4a90d9; cursor: pointer; }
|
||||
.current-task__switch:hover { text-decoration: underline; }
|
||||
.current-task__body { display: flex; align-items: center; gap: 16px; }
|
||||
.current-task__icon { width: 52px; height: 52px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 26px; background: rgba(250,173,22,0.15); }
|
||||
.current-task__info { flex: 1; }
|
||||
.current-task__name { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }
|
||||
.current-task__detail { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.current-task__cutting { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; background: rgba(114,46,209,0.15); border-radius: 4px; font-size: 11px; color: #722ed1; margin-top: 4px; }
|
||||
.current-task__progress { display: flex; align-items: center; gap: 8px; }
|
||||
.progress-mini { width: 80px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
|
||||
.progress-mini__fill { height: 100%; background: #4a90d9; border-radius: 2px; }
|
||||
.progress-mini__text { font-size: 11px; color: rgba(255,255,255,0.5); }
|
||||
.scale-display { flex: 1; background: #1a2b4a; border-radius: 16px; padding: 24px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; border: 2px solid rgba(255,255,255,0.06); position: relative; }
|
||||
.scale-display.weighing { border-color: #4a90d9; }
|
||||
.scale-display.stable { border-color: #52c41a; }
|
||||
.ai-result { display: flex; align-items: center; gap: 16px; padding: 16px 24px; background: rgba(0,0,0,0.2); border-radius: 12px; width: 100%; }
|
||||
.ai-result__icon { width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 30px; background: rgba(74,144,217,0.15); }
|
||||
.ai-result__info { flex: 1; }
|
||||
.ai-result__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 4px; }
|
||||
.ai-result__name { font-size: 20px; font-weight: 700; color: #fff; }
|
||||
.ai-result__confidence { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
.ai-result__status { font-size: 12px; padding: 4px 10px; border-radius: 4px; }
|
||||
.ai-result__status--matching { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.ai-result__status--mismatch { background: rgba(255,77,79,0.15); color: #ff4d4f; }
|
||||
.ai-result__status--idle { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); }
|
||||
.weight-display { text-align: center; }
|
||||
.weight-display__value { font-size: 72px; font-weight: 700; color: #fff; line-height: 1; font-family: "Courier New", monospace; }
|
||||
.weight-display__unit { font-size: 20px; color: rgba(255,255,255,0.5); margin-left: 4px; }
|
||||
.weight-display__status { font-size: 13px; color: rgba(255,255,255,0.4); margin-top: 8px; }
|
||||
.weight-display__status.weighing { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
.scale-actions { display: flex; gap: 12px; width: 100%; }
|
||||
.scale-btn { flex: 1; padding: 14px; border-radius: 10px; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.scale-btn--primary { background: #4a90d9; color: #fff; }
|
||||
.scale-btn--primary:hover { background: #5ba0e9; }
|
||||
.scale-btn--primary:disabled { background: rgba(74,144,217,0.3); cursor: not-allowed; }
|
||||
.scale-btn--success { background: #52c41a; color: #fff; }
|
||||
.scale-btn--success:hover { background: #62d42a; }
|
||||
.scale-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.scale-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
.record-panel { flex: 1; display: flex; flex-direction: column; gap: 16px; }
|
||||
.today-stats { background: #1a2b4a; border-radius: 12px; padding: 16px 20px; display: flex; gap: 20px; }
|
||||
.today-stat { flex: 1; text-align: center; }
|
||||
.today-stat__value { font-size: 24px; font-weight: 700; color: #fff; }
|
||||
.today-stat__label { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; }
|
||||
.records-section { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.records-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.records-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.records-count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.records-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent; }
|
||||
.records-list::-webkit-scrollbar { width: 4px; }
|
||||
.records-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.records-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.record-item { display: flex; align-items: center; gap: 12px; padding: 12px; background: rgba(0,0,0,0.15); border-radius: 8px; }
|
||||
.record-item__icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 18px; background: rgba(250,173,22,0.15); }
|
||||
.record-item__info { flex: 1; }
|
||||
.record-item__name { font-size: 13px; font-weight: 500; color: #fff; }
|
||||
.record-item__cutting { font-size: 11px; color: #722ed1; }
|
||||
.record-item__time { font-size: 11px; color: rgba(255,255,255,0.4); }
|
||||
.record-item__weight { font-size: 14px; font-weight: 600; color: #4a90d9; }
|
||||
.empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: rgba(255,255,255,0.3); }
|
||||
.empty-state__icon { font-size: 36px; margin-bottom: 12px; }
|
||||
.empty-state__text { font-size: 13px; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="01-cutting-tasks.html">◀ 返回</a>
|
||||
<span class="top-bar__title">称重录入</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="scale-panel">
|
||||
<div class="current-task">
|
||||
<div class="current-task__header">
|
||||
<span class="current-task__label">当前任务</span>
|
||||
<span class="current-task__switch" onclick="switchTask()">切换任务</span>
|
||||
</div>
|
||||
<div class="current-task__body">
|
||||
<div class="current-task__icon" id="taskIcon">🥔</div>
|
||||
<div class="current-task__info">
|
||||
<div class="current-task__name" id="taskName">土豆</div>
|
||||
<div class="current-task__detail">截止 11:00 | 来自清洗工序</div>
|
||||
<div class="current-task__cutting">🔪 切丝</div>
|
||||
</div>
|
||||
<div class="current-task__progress">
|
||||
<div class="progress-mini">
|
||||
<div class="progress-mini__fill" id="taskProgressFill" style="width: 40%"></div>
|
||||
</div>
|
||||
<span class="progress-mini__text" id="taskProgressText">2.0/5.0斤</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-display" id="scaleDisplay">
|
||||
<div class="ai-result">
|
||||
<div class="ai-result__icon" id="aiIcon">📇</div>
|
||||
<div class="ai-result__info">
|
||||
<div class="ai-result__label">AI 识别结果</div>
|
||||
<div class="ai-result__name" id="aiName">等待放置食材...</div>
|
||||
<div class="ai-result__confidence" id="aiConfidence"></div>
|
||||
</div>
|
||||
<span class="ai-result__status ai-result__status--idle" id="aiStatus">待机</span>
|
||||
</div>
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请将食材放置秤上</div>
|
||||
</div>
|
||||
<div class="scale-actions">
|
||||
<button class="scale-btn scale-btn--secondary" onclick="resetScale()">清零</button>
|
||||
<button class="scale-btn scale-btn--primary" id="goCuttingBtn" onclick="goToCutting()" disabled>确认并切配</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="record-panel">
|
||||
<div class="today-stats">
|
||||
<div class="today-stat">
|
||||
<div class="today-stat__value">4</div>
|
||||
<div class="today-stat__label">今日切配次数</div>
|
||||
</div>
|
||||
<div class="today-stat">
|
||||
<div class="today-stat__value">18.2</div>
|
||||
<div class="today-stat__label">总重量(斤)</div>
|
||||
</div>
|
||||
<div class="today-stat">
|
||||
<div class="today-stat__value">2</div>
|
||||
<div class="today-stat__label">完成品类</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="records-section">
|
||||
<div class="records-header">
|
||||
<span class="records-title">本次操作记录</span>
|
||||
<span class="records-count" id="recordsCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="records-list" id="recordsList">
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">📭</div>
|
||||
<div class="empty-state__text">暂无切配记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
var tasks = [
|
||||
{ id: 1, name: '土豆', icon: '🥔', need: 5.0, done: 2.0, deadline: '11:00', cutting: '切丝' },
|
||||
{ id: 2, name: '胡萝卜', icon: '🥕', need: 2.0, done: 0, deadline: '11:30', cutting: '切丝' },
|
||||
{ id: 3, name: '西红柿', icon: '🍅', need: 3.5, done: 0, deadline: '11:30', cutting: '切块' }
|
||||
];
|
||||
var currentTaskIndex = 0;
|
||||
var aiResults = {
|
||||
'potato': { name: '土豆', icon: '🥔', confidence: 97 },
|
||||
'tomato': { name: '西红柿', icon: '🍅', confidence: 95 },
|
||||
'cabbage': { name: '大白菜', icon: '🥬', confidence: 94 },
|
||||
'carrot': { name: '胡萝卜', icon: '🥕', confidence: 96 },
|
||||
'cucumber': { name: '黄瓜', icon: '🥒', confidence: 93 },
|
||||
'unknown': { name: '未识别', icon: '❓', confidence: 0 }
|
||||
};
|
||||
var state = 'idle';
|
||||
var currentWeight = 0;
|
||||
var currentAiResult = null;
|
||||
var records = [];
|
||||
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
function init() {
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
var ingredient = getUrlParam('ingredient');
|
||||
if (ingredient) {
|
||||
var idx = tasks.findIndex(function(t) { return t.name.includes(ingredient) || ingredient.includes(t.name); });
|
||||
if (idx >= 0) currentTaskIndex = idx;
|
||||
}
|
||||
updateTaskDisplay();
|
||||
simulateWeighing();
|
||||
}
|
||||
|
||||
function updateTaskDisplay() {
|
||||
var task = tasks[currentTaskIndex];
|
||||
document.getElementById('taskIcon').innerHTML = task.icon;
|
||||
document.getElementById('taskName').textContent = task.name;
|
||||
document.querySelector('.current-task__cutting').innerHTML = '🔪 ' + task.cutting;
|
||||
var percent = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('taskProgressFill').style.width = percent + '%';
|
||||
document.getElementById('taskProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
}
|
||||
|
||||
function switchTask() {
|
||||
currentTaskIndex = (currentTaskIndex + 1) % tasks.length;
|
||||
updateTaskDisplay();
|
||||
resetScale();
|
||||
showToast('已切换到: ' + tasks[currentTaskIndex].name, 1500);
|
||||
}
|
||||
|
||||
function simulateWeighing() {
|
||||
setTimeout(function() { startDetecting('potato'); }, 2000);
|
||||
}
|
||||
|
||||
function startDetecting(ingredientKey) {
|
||||
state = 'detecting';
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.add('weighing');
|
||||
scale.classList.remove('stable');
|
||||
document.getElementById('weightStatus').textContent = '正在称重...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status weighing';
|
||||
|
||||
var targetWeight = 1.85;
|
||||
var currentWeightAnim = 0;
|
||||
var weightInterval = setInterval(function() {
|
||||
currentWeightAnim += 0.12;
|
||||
if (currentWeightAnim >= targetWeight) {
|
||||
currentWeightAnim = targetWeight;
|
||||
clearInterval(weightInterval);
|
||||
onWeightStable(ingredientKey, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = currentWeightAnim.toFixed(2);
|
||||
}, 100);
|
||||
|
||||
setTimeout(function() {
|
||||
var result = aiResults[ingredientKey];
|
||||
currentAiResult = result;
|
||||
document.getElementById('aiIcon').innerHTML = result.icon;
|
||||
document.getElementById('aiName').textContent = result.name;
|
||||
document.getElementById('aiConfidence').textContent = '置信度: ' + result.confidence + '%';
|
||||
var task = tasks[currentTaskIndex];
|
||||
var isMatch = task.name === result.name || task.name.includes(result.name);
|
||||
var statusEl = document.getElementById('aiStatus');
|
||||
if (isMatch) {
|
||||
statusEl.textContent = '匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--matching';
|
||||
} else {
|
||||
statusEl.textContent = '不匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--mismatch';
|
||||
showToast('AI识别不匹配,请检查食材', 2500, 'error');
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function onWeightStable(ingredientKey, weight) {
|
||||
state = 'stable';
|
||||
currentWeight = weight;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing');
|
||||
scale.classList.add('stable');
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
document.getElementById('goCuttingBtn').disabled = false;
|
||||
}
|
||||
|
||||
function goToCutting() {
|
||||
if (state !== 'stable' || !currentAiResult) return;
|
||||
var task = tasks[currentTaskIndex];
|
||||
task.done += currentWeight;
|
||||
var record = {
|
||||
name: currentAiResult.name,
|
||||
icon: currentAiResult.icon,
|
||||
cutting: task.cutting,
|
||||
weight: currentWeight,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
};
|
||||
records.unshift(record);
|
||||
updateRecordsDisplay();
|
||||
showToast('已确认,进入切配环节', 2000, 'success');
|
||||
setTimeout(function() {
|
||||
var userName = getUrlParam('user') || '';
|
||||
window.location.href = '03-cutting.html?user=' + encodeURIComponent(userName) + '&ingredient=' + task.name + '&cutting=' + task.cutting + '&weight=' + currentWeight;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function updateRecordsDisplay() {
|
||||
var list = document.getElementById('recordsList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
if (records.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('recordsCount').textContent = '共 0 条';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
document.getElementById('recordsCount').textContent = '共 ' + records.length + ' 条';
|
||||
var existingItems = list.querySelectorAll('.record-item');
|
||||
existingItems.forEach(function(el) { el.remove(); });
|
||||
records.forEach(function(r) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'record-item';
|
||||
item.innerHTML = '<div class="record-item__icon">' + r.icon + '</div>' +
|
||||
'<div class="record-item__info"><div class="record-item__name">' + r.name + '</div>' +
|
||||
'<div class="record-item__cutting">' + r.cutting + '</div>' +
|
||||
'<div class="record-item__time">' + r.time + '</div></div>' +
|
||||
'<div class="record-item__weight">+' + r.weight.toFixed(2) + '斤</div>';
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function resetScale() {
|
||||
state = 'idle';
|
||||
currentWeight = 0;
|
||||
currentAiResult = null;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing', 'stable');
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请将食材放置秤上';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('aiIcon').innerHTML = '📇';
|
||||
document.getElementById('aiName').textContent = '等待放置食材...';
|
||||
document.getElementById('aiConfidence').textContent = '';
|
||||
document.getElementById('aiStatus').textContent = '待机';
|
||||
document.getElementById('aiStatus').className = 'ai-result__status ai-result__status--idle';
|
||||
document.getElementById('goCuttingBtn').disabled = true;
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,316 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜加工秤 - 切配操作 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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 { height: calc(100% - 44px); padding: 24px 32px; display: flex; gap: 28px; }
|
||||
.cutting-panel { flex: 0 0 520px; display: flex; flex-direction: column; gap: 20px; }
|
||||
.ingredient-card { background: #1a2b4a; border-radius: 16px; padding: 24px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.ingredient-card__header { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
|
||||
.ingredient-card__icon { width: 64px; height: 64px; border-radius: 14px; display: flex; align-items: center; justify-content: center; font-size: 34px; background: rgba(250,173,22,0.15); }
|
||||
.ingredient-card__info { flex: 1; }
|
||||
.ingredient-card__name { font-size: 20px; font-weight: 700; color: #fff; margin-bottom: 4px; }
|
||||
.ingredient-card__meta { font-size: 12px; color: rgba(255,255,255,0.5); display: flex; gap: 12px; }
|
||||
.ingredient-card__cutting { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: rgba(114,46,209,0.15); border-radius: 4px; font-size: 12px; color: #722ed1; font-weight: 500; }
|
||||
.cutting-params { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.param-item { background: rgba(0,0,0,0.15); border-radius: 10px; padding: 14px 16px; }
|
||||
.param-item__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 6px; }
|
||||
.param-item__value { font-size: 16px; font-weight: 600; color: #fff; }
|
||||
.param-item__value--purple { color: #722ed1; }
|
||||
.cutting-progress { flex: 1; background: #1a2b4a; border-radius: 16px; padding: 28px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 24px; border: 2px solid rgba(255,255,255,0.06); }
|
||||
.cutting-progress.active { border-color: #722ed1; }
|
||||
.cutting-progress.done { border-color: #52c41a; }
|
||||
.progress-circle { position: relative; width: 180px; height: 180px; }
|
||||
.progress-circle__svg { transform: rotate(-90deg); }
|
||||
.progress-circle__bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 8; }
|
||||
.progress-circle__fill { fill: none; stroke: #722ed1; stroke-width: 8; stroke-linecap: round; stroke-dasharray: 502; stroke-dashoffset: 502; transition: stroke-dashoffset 0.5s; }
|
||||
.progress-circle__fill.done { stroke: #52c41a; }
|
||||
.progress-circle__text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; }
|
||||
.progress-circle__percent { font-size: 36px; font-weight: 700; color: #fff; }
|
||||
.progress-circle__label { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 4px; }
|
||||
.cutting-steps { display: flex; gap: 12px; width: 100%; }
|
||||
.cutting-step { flex: 1; text-align: center; padding: 12px; border-radius: 10px; background: rgba(0,0,0,0.15); }
|
||||
.cutting-step__icon { font-size: 20px; margin-bottom: 6px; }
|
||||
.cutting-step__name { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.cutting-step.active { background: rgba(114,46,209,0.15); }
|
||||
.cutting-step.active .cutting-step__name { color: #722ed1; }
|
||||
.cutting-step.done { background: rgba(82,196,26,0.15); }
|
||||
.cutting-step.done .cutting-step__name { color: #52c41a; }
|
||||
.cutting-actions { display: flex; gap: 12px; width: 100%; }
|
||||
.cutting-btn { flex: 1; padding: 14px; border-radius: 10px; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.cutting-btn--primary { background: #722ed1; color: #fff; }
|
||||
.cutting-btn--primary:hover { background: #8240e0; }
|
||||
.cutting-btn--success { background: #52c41a; color: #fff; }
|
||||
.cutting-btn--success:hover { background: #62d42a; }
|
||||
.cutting-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.cutting-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
.queue-panel { flex: 1; display: flex; flex-direction: column; gap: 16px; }
|
||||
.done-stats { background: #1a2b4a; border-radius: 12px; padding: 16px 20px; display: flex; gap: 20px; }
|
||||
.done-stat { flex: 1; text-align: center; }
|
||||
.done-stat__value { font-size: 20px; font-weight: 700; color: #52c41a; }
|
||||
.done-stat__label { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; }
|
||||
.queue-section { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.queue-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.queue-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.queue-count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.queue-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent; }
|
||||
.queue-list::-webkit-scrollbar { width: 4px; }
|
||||
.queue-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.queue-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.queue-item { display: flex; align-items: center; gap: 12px; padding: 12px; background: rgba(0,0,0,0.15); border-radius: 8px; }
|
||||
.queue-item__icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 18px; background: rgba(250,173,22,0.15); }
|
||||
.queue-item__info { flex: 1; }
|
||||
.queue-item__name { font-size: 13px; font-weight: 500; color: #fff; }
|
||||
.queue-item__cutting { font-size: 11px; color: #722ed1; }
|
||||
.queue-item__weight { font-size: 11px; color: rgba(255,255,255,0.4); }
|
||||
.queue-item__status { font-size: 11px; padding: 3px 8px; border-radius: 4px; }
|
||||
.queue-item__status--waiting { background: rgba(250,173,22,0.15); color: #faad16; }
|
||||
.queue-item__status--cutting { background: rgba(114,46,209,0.15); color: #722ed1; }
|
||||
.queue-item__status--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="02-weighing.html">◀ 返回</a>
|
||||
<span class="top-bar__title">切配操作</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="cutting-panel">
|
||||
<div class="ingredient-card">
|
||||
<div class="ingredient-card__header">
|
||||
<div class="ingredient-card__icon">🥔</div>
|
||||
<div class="ingredient-card__info">
|
||||
<div class="ingredient-card__name">土豆</div>
|
||||
<div class="ingredient-card__meta">
|
||||
<span>重量: 1.85斤</span>
|
||||
<span>批次 #20260613-C01</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ingredient-card__cutting">🔪 系统指定: 切丝</div>
|
||||
</div>
|
||||
<div class="cutting-params">
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">切法</div>
|
||||
<div class="param-item__value param-item__value--purple">丝</div>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">粗细</div>
|
||||
<div class="param-item__value">3mm</div>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">刀工</div>
|
||||
<div class="param-item__value">直刀</div>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">预计耗时</div>
|
||||
<div class="param-item__value">5分钟</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cutting-progress" id="cuttingProgress">
|
||||
<div class="progress-circle">
|
||||
<svg class="progress-circle__svg" width="180" height="180" viewBox="0 0 180 180">
|
||||
<circle class="progress-circle__bg" cx="90" cy="90" r="80"/>
|
||||
<circle class="progress-circle__fill" id="progressFill" cx="90" cy="90" r="80"/>
|
||||
</svg>
|
||||
<div class="progress-circle__text">
|
||||
<div class="progress-circle__percent" id="progressPercent">0%</div>
|
||||
<div class="progress-circle__label" id="progressLabel">等待开始</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cutting-steps">
|
||||
<div class="cutting-step" id="step1">
|
||||
<div class="cutting-step__icon">🔪</div>
|
||||
<div class="cutting-step__name">切片</div>
|
||||
</div>
|
||||
<div class="cutting-step" id="step2">
|
||||
<div class="cutting-step__icon">🔪</div>
|
||||
<div class="cutting-step__name">切丝</div>
|
||||
</div>
|
||||
<div class="cutting-step" id="step3">
|
||||
<div class="cutting-step__icon">✅</div>
|
||||
<div class="cutting-step__name">完成</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cutting-actions">
|
||||
<button class="cutting-btn cutting-btn--secondary" onclick="skipCutting()">跳过</button>
|
||||
<button class="cutting-btn cutting-btn--primary" id="startBtn" onclick="startCutting()">开始切配</button>
|
||||
<button class="cutting-btn cutting-btn--success" id="completeBtn" onclick="completeCutting()" style="display:none">完成切配</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="queue-panel">
|
||||
<div class="done-stats">
|
||||
<div class="done-stat">
|
||||
<div class="done-stat__value">2</div>
|
||||
<div class="done-stat__label">今日已切配</div>
|
||||
</div>
|
||||
<div class="done-stat">
|
||||
<div class="done-stat__value">8.5斤</div>
|
||||
<div class="done-stat__label">总重量</div>
|
||||
</div>
|
||||
<div class="done-stat">
|
||||
<div class="done-stat__value">100%</div>
|
||||
<div class="done-stat__label">合格率</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="queue-section">
|
||||
<div class="queue-header">
|
||||
<span class="queue-title">待切配队列</span>
|
||||
<span class="queue-count" id="queueCount">共 3 项</span>
|
||||
</div>
|
||||
<div class="queue-list">
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🥕</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">胡萝卜</div>
|
||||
<div class="queue-item__cutting">切丝</div>
|
||||
<div class="queue-item__weight">2.0斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待切配</span>
|
||||
</div>
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🍅</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">西红柿</div>
|
||||
<div class="queue-item__cutting">切块</div>
|
||||
<div class="queue-item__weight">3.5斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待切配</span>
|
||||
</div>
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🥔</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">土豆(晚餐)</div>
|
||||
<div class="queue-item__cutting">切块</div>
|
||||
<div class="queue-item__weight">3.0斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待切配</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// 根据URL参数更新页面
|
||||
var ingredient = getUrlParam('ingredient');
|
||||
var cutting = getUrlParam('cutting');
|
||||
var weight = getUrlParam('weight');
|
||||
if (ingredient) document.querySelector('.ingredient-card__name').textContent = ingredient;
|
||||
if (cutting) document.querySelector('.ingredient-card__cutting').innerHTML = '🔪 系统指定: ' + cutting;
|
||||
if (weight) document.querySelector('.ingredient-card__meta').innerHTML = '<span>重量: ' + weight + '斤</span><span>批次 #20260613-C01</span>';
|
||||
|
||||
var isCutting = false;
|
||||
var cuttingProgress = 0;
|
||||
var cuttingInterval = null;
|
||||
|
||||
function startCutting() {
|
||||
if (isCutting) return;
|
||||
isCutting = true;
|
||||
cuttingProgress = 0;
|
||||
document.getElementById('startBtn').style.display = 'none';
|
||||
document.getElementById('cuttingProgress').classList.add('active');
|
||||
showToast('切配开始...', 1500);
|
||||
|
||||
cuttingInterval = setInterval(function() {
|
||||
cuttingProgress += 2;
|
||||
var offset = 502 - (502 * cuttingProgress / 100);
|
||||
document.getElementById('progressFill').style.strokeDashoffset = offset;
|
||||
document.getElementById('progressPercent').textContent = cuttingProgress + '%';
|
||||
updateSteps();
|
||||
|
||||
if (cuttingProgress >= 100) {
|
||||
clearInterval(cuttingInterval);
|
||||
onCuttingComplete();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
function updateSteps() {
|
||||
var steps = ['step1', 'step2', 'step3'];
|
||||
var thresholds = [33, 66, 100];
|
||||
steps.forEach(function(id, idx) {
|
||||
var el = document.getElementById(id);
|
||||
el.classList.remove('active', 'done');
|
||||
if (cuttingProgress >= thresholds[idx]) {
|
||||
el.classList.add('done');
|
||||
} else if (cuttingProgress >= (thresholds[idx - 1] || 0)) {
|
||||
el.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
if (cuttingProgress < 33) {
|
||||
document.getElementById('progressLabel').textContent = '切片中...';
|
||||
} else if (cuttingProgress < 66) {
|
||||
document.getElementById('progressLabel').textContent = '切丝中...';
|
||||
} else if (cuttingProgress < 100) {
|
||||
document.getElementById('progressLabel').textContent = '整理中...';
|
||||
} else {
|
||||
document.getElementById('progressLabel').textContent = '切配完成';
|
||||
}
|
||||
}
|
||||
|
||||
function onCuttingComplete() {
|
||||
isCutting = false;
|
||||
document.getElementById('cuttingProgress').classList.remove('active');
|
||||
document.getElementById('cuttingProgress').classList.add('done');
|
||||
document.getElementById('progressFill').classList.add('done');
|
||||
document.getElementById('completeBtn').style.display = 'block';
|
||||
showToast('切配完成,请点击"完成切配"提交', 3000, 'success');
|
||||
}
|
||||
|
||||
function completeCutting() {
|
||||
showToast('已提交,净菜已转入打包区', 2500, 'success');
|
||||
setTimeout(function() {
|
||||
window.location.href = '02-weighing.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function skipCutting() {
|
||||
if (confirm('确定跳过切配步骤?')) {
|
||||
showToast('已跳过切配', 1500);
|
||||
setTimeout(function() {
|
||||
window.location.href = '02-weighing.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,251 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜加工秤 - 切配记录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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 { height: calc(100% - 44px); padding: 20px 28px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.filter-bar { display: flex; align-items: center; gap: 12px; }
|
||||
.filter-group { display: flex; align-items: center; gap: 8px; }
|
||||
.filter-label { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.filter-select { background: #1a2b4a; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 12px; color: #fff; outline: none; cursor: pointer; }
|
||||
.filter-select:focus { border-color: #4a90d9; }
|
||||
.filter-input { background: #1a2b4a; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 12px; color: #fff; outline: none; width: 140px; }
|
||||
.filter-input:focus { border-color: #4a90d9; }
|
||||
.filter-input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
.filter-btn { padding: 8px 16px; border-radius: 6px; font-size: 12px; background: #4a90d9; color: #fff; border: none; cursor: pointer; }
|
||||
.filter-btn:hover { background: #5ba0e9; }
|
||||
.filter-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.filter-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
.stats-row { display: flex; gap: 14px; }
|
||||
.stat-card { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.stat-card__label { font-size: 12px; color: rgba(255,255,255,0.5); margin-bottom: 6px; }
|
||||
.stat-card__value { font-size: 24px; font-weight: 700; color: #fff; }
|
||||
.stat-card__value--green { color: #52c41a; }
|
||||
.stat-card__value--blue { color: #4a90d9; }
|
||||
.stat-card__value--purple { color: #722ed1; }
|
||||
.records-section { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.records-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
|
||||
.records-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.records-count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.table-header { display: flex; align-items: center; padding: 10px 16px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 8px; }
|
||||
.table-header__cell { font-size: 12px; color: rgba(255,255,255,0.5); font-weight: 500; }
|
||||
.col-batch { flex: 0 0 140px; }
|
||||
.col-name { flex: 0 0 100px; }
|
||||
.col-cutting { flex: 0 0 80px; }
|
||||
.col-weight { flex: 0 0 80px; }
|
||||
.col-time { flex: 0 0 100px; }
|
||||
.col-duration { flex: 0 0 80px; }
|
||||
.col-operator { flex: 0 0 80px; }
|
||||
.col-status { flex: 0 0 80px; }
|
||||
.col-action { flex: 1; text-align: right; }
|
||||
.records-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; }
|
||||
.records-list::-webkit-scrollbar { width: 4px; }
|
||||
.records-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.records-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.record-row { display: flex; align-items: center; padding: 12px 16px; background: rgba(0,0,0,0.15); border-radius: 8px; transition: background 0.2s; }
|
||||
.record-row:hover { background: rgba(74,144,217,0.08); }
|
||||
.record-row__cell { font-size: 13px; color: rgba(255,255,255,0.8); }
|
||||
.record-row__cell--icon { font-size: 18px; }
|
||||
.cutting-badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 11px; background: rgba(114,46,209,0.15); color: #722ed1; }
|
||||
.status-badge { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
||||
.status-badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.action-link { font-size: 12px; color: #4a90d9; cursor: pointer; text-decoration: none; }
|
||||
.action-link:hover { text-decoration: underline; }
|
||||
.pagination { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.06); }
|
||||
.pagination__info { font-size: 12px; color: rgba(255,255,255,0.4); margin-right: auto; }
|
||||
.pagination__btn { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6); font-size: 12px; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.pagination__btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.pagination__btn.active { background: #4a90d9; color: #fff; }
|
||||
.pagination__btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">切配记录</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">日期</span>
|
||||
<input type="date" class="filter-input" value="2026-06-13">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">食材</span>
|
||||
<select class="filter-select">
|
||||
<option value="">全部</option>
|
||||
<option value="potato">土豆</option>
|
||||
<option value="carrot">胡萝卜</option>
|
||||
<option value="cucumber">黄瓜</option>
|
||||
<option value="cabbage">大白菜</option>
|
||||
<option value="tomato">西红柿</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">切法</span>
|
||||
<select class="filter-select">
|
||||
<option value="">全部</option>
|
||||
<option value="slice">切片</option>
|
||||
<option value="shred">切丝</option>
|
||||
<option value="chunk">切块</option>
|
||||
<option value="dice">切丁</option>
|
||||
<option value="section">切段</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="filter-btn">查询</button>
|
||||
<button class="filter-btn filter-btn--secondary">重置</button>
|
||||
</div>
|
||||
<div class="stats-row">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">今日切配批次</div>
|
||||
<div class="stat-card__value stat-card__value--purple">8</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">切配总量</div>
|
||||
<div class="stat-card__value">32.5斤</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">平均切配时长</div>
|
||||
<div class="stat-card__value">5.3分钟</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">合格率</div>
|
||||
<div class="stat-card__value stat-card__value--green">100%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="records-section">
|
||||
<div class="records-header">
|
||||
<span class="records-title">切配记录列表</span>
|
||||
<span class="records-count">共 8 条记录</span>
|
||||
</div>
|
||||
<div class="table-header">
|
||||
<div class="table-header__cell col-batch">批次号</div>
|
||||
<div class="table-header__cell col-name">食材</div>
|
||||
<div class="table-header__cell col-cutting">切法</div>
|
||||
<div class="table-header__cell col-weight">重量</div>
|
||||
<div class="table-header__cell col-time">完成时间</div>
|
||||
<div class="table-header__cell col-duration">时长</div>
|
||||
<div class="table-header__cell col-operator">操作员</div>
|
||||
<div class="table-header__cell col-status">状态</div>
|
||||
<div class="table-header__cell col-action">操作</div>
|
||||
</div>
|
||||
<div class="records-list">
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C01</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切丝</span></div>
|
||||
<div class="record-row__cell col-weight">5.2斤</div>
|
||||
<div class="record-row__cell col-time">09:35</div>
|
||||
<div class="record-row__cell col-duration">5分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C02</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥒</span> 黄瓜</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切片</span></div>
|
||||
<div class="record-row__cell col-weight">4.2斤</div>
|
||||
<div class="record-row__cell col-time">09:52</div>
|
||||
<div class="record-row__cell col-duration">4分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C03</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥬</span> 大白菜</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切段</span></div>
|
||||
<div class="record-row__cell col-weight">8.5斤</div>
|
||||
<div class="record-row__cell col-time">10:15</div>
|
||||
<div class="record-row__cell col-duration">6分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C04</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥕</span> 胡萝卜</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切丝</span></div>
|
||||
<div class="record-row__cell col-weight">2.8斤</div>
|
||||
<div class="record-row__cell col-time">10:32</div>
|
||||
<div class="record-row__cell col-duration">5分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C05</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🍅</span> 西红柿</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切块</span></div>
|
||||
<div class="record-row__cell col-weight">3.5斤</div>
|
||||
<div class="record-row__cell col-time">10:48</div>
|
||||
<div class="record-row__cell col-duration">4分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C06</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切块</span></div>
|
||||
<div class="record-row__cell col-weight">3.2斤</div>
|
||||
<div class="record-row__cell col-time">14:20</div>
|
||||
<div class="record-row__cell col-duration">5分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C07</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切条</span></div>
|
||||
<div class="record-row__cell col-weight">2.6斤</div>
|
||||
<div class="record-row__cell col-time">14:38</div>
|
||||
<div class="record-row__cell col-duration">5分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#20260613-C08</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥕</span> 胡萝卜</div>
|
||||
<div class="record-row__cell col-cutting"><span class="cutting-badge">切丁</span></div>
|
||||
<div class="record-row__cell col-weight">2.5斤</div>
|
||||
<div class="record-row__cell col-time">14:55</div>
|
||||
<div class="record-row__cell col-duration">6分钟</div>
|
||||
<div class="record-row__cell col-operator">赵六</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span class="pagination__info">共 8 条,第 1/1 页</span>
|
||||
<button class="pagination__btn" disabled>‹</button>
|
||||
<button class="pagination__btn active">1</button>
|
||||
<button class="pagination__btn" disabled>›</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜加工秤 - 设备设置 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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 { height: calc(100% - 44px); padding: 24px 32px; display: flex; gap: 24px; }
|
||||
.settings-menu { flex: 0 0 200px; background: #1a2b4a; border-radius: 12px; padding: 12px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.menu-item { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; color: rgba(255,255,255,0.6); transition: all 0.2s; }
|
||||
.menu-item:hover { background: rgba(255,255,255,0.06); color: #fff; }
|
||||
.menu-item.active { background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
.menu-item__icon { font-size: 16px; }
|
||||
.settings-content { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 24px 28px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent; }
|
||||
.settings-content::-webkit-scrollbar { width: 4px; }
|
||||
.settings-content::-webkit-scrollbar-track { background: transparent; }
|
||||
.settings-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.settings-section { margin-bottom: 28px; }
|
||||
.settings-section:last-child { margin-bottom: 0; }
|
||||
.section-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.08); margin-bottom: 16px; }
|
||||
.setting-item { display: flex; align-items: center; padding: 14px 0; border-bottom: 1px solid rgba(255,255,255,0.04); }
|
||||
.setting-item:last-child { border-bottom: none; }
|
||||
.setting-item__info { flex: 1; }
|
||||
.setting-item__label { font-size: 13px; color: rgba(255,255,255,0.8); margin-bottom: 2px; }
|
||||
.setting-item__desc { font-size: 11px; color: rgba(255,255,255,0.4); }
|
||||
.setting-item__control { flex: 0 0 auto; }
|
||||
.setting-input { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 13px; color: #fff; outline: none; width: 120px; text-align: right; }
|
||||
.setting-input:focus { border-color: #4a90d9; }
|
||||
.setting-select { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 13px; color: #fff; outline: none; cursor: pointer; }
|
||||
.setting-select:focus { border-color: #4a90d9; }
|
||||
.toggle { width: 44px; height: 24px; border-radius: 12px; background: rgba(255,255,255,0.15); position: relative; cursor: pointer; transition: background 0.2s; }
|
||||
.toggle.on { background: #4a90d9; }
|
||||
.toggle__knob { width: 20px; height: 20px; border-radius: 50%; background: #fff; position: absolute; top: 2px; left: 2px; transition: left 0.2s; }
|
||||
.toggle.on .toggle__knob { left: 22px; }
|
||||
.device-info { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.info-item { background: rgba(0,0,0,0.15); border-radius: 10px; padding: 14px 16px; }
|
||||
.info-item__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 4px; }
|
||||
.info-item__value { font-size: 14px; color: #fff; font-weight: 500; }
|
||||
.info-item__value--online { color: #52c41a; }
|
||||
.save-btn { display: block; width: 100%; padding: 14px; border-radius: 10px; background: #4a90d9; color: #fff; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; margin-top: 24px; }
|
||||
.save-btn:hover { background: #5ba0e9; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">设备设置</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="settings-menu">
|
||||
<div class="menu-item active" onclick="switchSection('cutting', this)"><span class="menu-item__icon">🔪</span><span>切配参数</span></div>
|
||||
<div class="menu-item" onclick="switchSection('ai', this)"><span class="menu-item__icon">🤖</span><span>AI识别</span></div>
|
||||
<div class="menu-item" onclick="switchSection('scale', this)"><span class="menu-item__icon">⚖️</span><span>秤参数</span></div>
|
||||
<div class="menu-item" onclick="switchSection('device', this)"><span class="menu-item__icon">💻</span><span>设备信息</span></div>
|
||||
<div class="menu-item" onclick="switchSection('about', this)"><span class="menu-item__icon">ℹ️</span><span>关于</span></div>
|
||||
</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-section" id="section-cutting">
|
||||
<div class="section-title">切配参数配置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认切丝粗细</div><div class="setting-item__desc">切丝模式的标准粗细</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="3" min="1" max="10"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">mm</span></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认切块大小</div><div class="setting-item__desc">切块模式的标准大小</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>小(2cm)</option><option selected>中(3cm)</option><option>大(5cm)</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认切丁大小</div><div class="setting-item__desc">切丁模式的标准大小</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>小(5mm)</option><option selected>中(10mm)</option><option>大(15mm)</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">默认刀工</div><div class="setting-item__desc">默认使用的刀工方式</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option selected>直刀</option><option>斜刀</option><option>花刀</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-ai" style="display:none">
|
||||
<div class="section-title">AI 识别设置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">识别置信度阈值</div><div class="setting-item__desc">低于此值将提示人工确认</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="85" min="60" max="99"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">%</span></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">不匹配自动告警</div><div class="setting-item__desc">AI识别与任务不匹配时弹窗提示</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">连续识别模式</div><div class="setting-item__desc">放置食材后自动触发识别</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-scale" style="display:none">
|
||||
<div class="section-title">秤参数配置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">重量单位</div><div class="setting-item__desc">称重显示单位</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option selected>斤</option><option>克</option><option>千克</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">小数位数</div><div class="setting-item__desc">重量显示精度</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>1位</option><option selected>2位</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">稳定判定时间</div><div class="setting-item__desc">重量稳定后自动锁定的等待时间</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="3" min="1" max="10"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">秒</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-device" style="display:none">
|
||||
<div class="section-title">设备信息</div>
|
||||
<div class="device-info">
|
||||
<div class="info-item"><div class="info-item__label">设备名称</div><div class="info-item__value">净菜加工秤-01</div></div>
|
||||
<div class="info-item"><div class="info-item__label">设备编号</div><div class="info-item__value">JCJG-2026-001</div></div>
|
||||
<div class="info-item"><div class="info-item__label">固件版本</div><div class="info-item__value">v2.3.1</div></div>
|
||||
<div class="info-item"><div class="info-item__label">AI模型版本</div><div class="info-item__value">cut-ai-v3.1</div></div>
|
||||
<div class="info-item"><div class="info-item__label">设备状态</div><div class="info-item__value info-item__value--online">在线</div></div>
|
||||
<div class="info-item"><div class="info-item__label">网络状态</div><div class="info-item__value info-item__value--online">已连接</div></div>
|
||||
<div class="info-item"><div class="info-item__label">上次校准</div><div class="info-item__value">2026-06-10</div></div>
|
||||
<div class="info-item"><div class="info-item__label">累计使用</div><div class="info-item__value">152天</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-about" style="display:none">
|
||||
<div class="section-title">关于</div>
|
||||
<div class="device-info">
|
||||
<div class="info-item"><div class="info-item__label">系统名称</div><div class="info-item__value">蚁熊智能餐养平台</div></div>
|
||||
<div class="info-item"><div class="info-item__label">系统版本</div><div class="info-item__value">v5.8.0</div></div>
|
||||
<div class="info-item"><div class="info-item__label">设备类型</div><div class="info-item__value">净菜加工秤</div></div>
|
||||
<div class="info-item"><div class="info-item__label">制造商</div><div class="info-item__value">蚁熊智能科技有限公司</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="save-btn" onclick="saveSettings()">保存设置</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
function switchSection(section, el) {
|
||||
document.querySelectorAll('.menu-item').forEach(function(item) { item.classList.remove('active'); });
|
||||
el.classList.add('active');
|
||||
document.querySelectorAll('.settings-section').forEach(function(sec) { sec.style.display = 'none'; });
|
||||
document.getElementById('section-' + section).style.display = 'block';
|
||||
}
|
||||
function toggleSwitch(el) { el.classList.toggle('on'); }
|
||||
function saveSettings() { showToast('设置已保存', 2000, 'success'); }
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
# 净菜加工秤 CHANGELOG
|
||||
|
||||
## [1.0.0] - 2026-06-13
|
||||
|
||||
### 新增
|
||||
- 首次创建净菜加工秤模块(6个页面)
|
||||
- `00-welcome.html` — 欢迎页:人脸识别+账号密码登录+功能菜单
|
||||
- `01-cutting-tasks.html` — 今日切配任务列表:任务卡片含切法标签、状态筛选、进度展示
|
||||
- `02-weighing.html` — 称重录入:AI识别品类、实时称重、确认后跳转切配页面
|
||||
- `03-cutting.html` — 切配操作:系统指定切法/粗细/刀工、圆形进度、三阶段(切片/切丝/完成)、待切配队列
|
||||
- `04-history.html` — 切配记录:日期/食材/切法筛选、统计卡片、切法标签、表格记录、分页
|
||||
- `05-settings.html` — 设备设置:切配参数/AI识别/秤参数/设备信息/关于 五个配置区
|
||||
@@ -3,33 +3,78 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜配比秤 - 蚁熊智能餐养平台</title>
|
||||
<title>净菜加工秤 - 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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); }
|
||||
.main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); }
|
||||
.placeholder { text-align: center; color: rgba(255,255,255,0.3); }
|
||||
.placeholder__icon { font-size: 48px; margin-bottom: 16px; }
|
||||
.placeholder__text { font-size: 14px; }
|
||||
.back-link { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; }
|
||||
.back-link:hover { color: #fff; }
|
||||
.main-content { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px; padding: 24px 40px; height: calc(100% - 44px); align-content: center; }
|
||||
.card { display: flex; align-items: center; gap: 20px; padding: 24px 28px; background: #1a2b4a; border-radius: 14px; text-decoration: none; cursor: pointer; transition: all 0.2s; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3); border-color: rgba(74,144,217,0.3); }
|
||||
.card__icon { font-size: 28px; width: 40px; text-align: center; flex-shrink: 0; }
|
||||
.card__info { flex: 1; }
|
||||
.card__title { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }
|
||||
.card__desc { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.card__arrow { font-size: 18px; color: rgba(255,255,255,0.4); flex-shrink: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">净菜配比秤</span>
|
||||
<a class="back-link" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">净菜加工秤</span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__icon">🚧</div>
|
||||
<div class="placeholder__text">净菜配比秤 页面待开发</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<a class="card" href="00-welcome.html">
|
||||
<span class="card__icon">👋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">欢迎页面</div>
|
||||
<div class="card__desc">人脸识别/账号密码登录、功能菜单入口</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="01-cutting-tasks.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">今日切配任务</div>
|
||||
<div class="card__desc">系统分配的净菜切配任务列表</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="02-weighing.html">
|
||||
<span class="card__icon">⚖️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">称重录入</div>
|
||||
<div class="card__desc">净菜上秤AI识别、称重、确认进入切配</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="03-cutting.html">
|
||||
<span class="card__icon">🔪</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">切配操作</div>
|
||||
<div class="card__desc">系统指定切法、切配进度跟踪</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="04-history.html">
|
||||
<span class="card__icon">📝</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">切配记录</div>
|
||||
<div class="card__desc">历史切配记录查看与筛选</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="05-settings.html">
|
||||
<span class="card__icon">⚙️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">设备设置</div>
|
||||
<div class="card__desc">切配参数、AI识别、秤参数配置</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -111,119 +111,114 @@
|
||||
<!-- 主内容 -->
|
||||
<div class="main-content">
|
||||
|
||||
<!-- 营养秤系列 -->
|
||||
<div class="section-title">营养秤系列</div>
|
||||
<!-- 卫生供应 -->
|
||||
<div class="section-title">卫生供应</div>
|
||||
<div class="grid">
|
||||
<a class="card" href="canteen-nutrition-scale/index.html">
|
||||
<div class="card__icon">🥘</div>
|
||||
<div class="card__name">餐线营养秤</div>
|
||||
<div class="card__desc">开餐、补餐、取餐、餐品展示、设置、采样</div>
|
||||
</a>
|
||||
<a class="card" href="stall-nutrition-scale/index.html">
|
||||
<div class="card__icon">🏪</div>
|
||||
<div class="card__name">档口营养秤</div>
|
||||
<div class="card__desc">档口菜品营养分析与称重结算</div>
|
||||
</a>
|
||||
<a class="card" href="ratio-scale/index.html">
|
||||
<div class="card__icon">⚖️</div>
|
||||
<div class="card__name">配比秤</div>
|
||||
<div class="card__desc">食材精准配比称重</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 净菜系列 -->
|
||||
<div class="section-title">净菜系列</div>
|
||||
<div class="grid">
|
||||
<a class="card" href="clean-veg-packaging-labeler/index.html">
|
||||
<div class="card__icon">🏷️</div>
|
||||
<div class="card__name">净菜包装贴标机</div>
|
||||
<div class="card__desc">净菜自动包装与贴标打印</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-kitchen-cabinet/index.html">
|
||||
<div class="card__icon">🗄️</div>
|
||||
<div class="card__name">净菜厨房柜</div>
|
||||
<div class="card__desc">净菜暂存与保鲜存储</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-inventory-cabinet/index.html">
|
||||
<div class="card__icon">📦</div>
|
||||
<div class="card__name">净菜库存柜</div>
|
||||
<div class="card__desc">净菜库存管理与盘点</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-ratio-scale/index.html">
|
||||
<div class="card__icon">⚖️</div>
|
||||
<div class="card__name">净菜配比秤</div>
|
||||
<div class="card__desc">净菜搭配称重与营养配比</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 毛菜系列 -->
|
||||
<div class="section-title">毛菜系列</div>
|
||||
<div class="grid">
|
||||
<a class="card" href="raw-veg-processing-scale/index.html">
|
||||
<div class="card__icon">🔪</div>
|
||||
<div class="card__name">加工毛菜秤</div>
|
||||
<div class="card__desc">毛菜初加工称重与记录</div>
|
||||
</a>
|
||||
<a class="card" href="raw-veg-hygiene-inspection/index.html">
|
||||
<div class="card__icon">🔬</div>
|
||||
<div class="card__icon">🔬</div>
|
||||
<div class="card__name">毛菜卫检设备</div>
|
||||
<div class="card__desc">毛菜卫生检测与质量判定</div>
|
||||
</a>
|
||||
<a class="card" href="raw-veg-inventory-cabinet/index.html">
|
||||
<div class="card__icon">📦</div>
|
||||
<div class="card__name">毛菜库存柜</div>
|
||||
<div class="card__desc">毛菜库存管理与出入库</div>
|
||||
</a>
|
||||
<a class="card" href="raw-veg-washing-scale/index.html">
|
||||
<div class="card__icon">🚿</div>
|
||||
<div class="card__name">毛菜清洗秤</div>
|
||||
<div class="card__desc">毛菜清洗流程与称重管理</div>
|
||||
</a>
|
||||
<a class="card" href="raw-veg-inspection-scale/index.html">
|
||||
<div class="card__icon">✅</div>
|
||||
<div class="card__icon">✅</div>
|
||||
<div class="card__name">毛菜验收秤</div>
|
||||
<div class="card__desc">毛菜到货验收与质检</div>
|
||||
</a>
|
||||
<a class="card" href="raw-veg-inventory-cabinet/index.html">
|
||||
<div class="card__icon">📦</div>
|
||||
<div class="card__name">毛菜库存柜</div>
|
||||
<div class="card__desc">毛菜库存管理与出入库</div>
|
||||
</a>
|
||||
<a class="card" href="raw-veg-processing-scale/index.html">
|
||||
<div class="card__icon">🔪</div>
|
||||
<div class="card__name">加工毛菜秤</div>
|
||||
<div class="card__desc">毛菜清洗:AI识别、称重、清洗操作</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-ratio-scale/index.html">
|
||||
<div class="card__icon">⚖️</div>
|
||||
<div class="card__name">净菜加工秤</div>
|
||||
<div class="card__desc">净菜切配:AI识别、称重、切丝/片/块/丁</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-packaging-labeler/index.html">
|
||||
<div class="card__icon">🏷️</div>
|
||||
<div class="card__name">净菜包装贴标机</div>
|
||||
<div class="card__desc">净菜自动包装与贴标打印</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-inventory-cabinet/index.html">
|
||||
<div class="card__icon">📦</div>
|
||||
<div class="card__name">净菜库存柜</div>
|
||||
<div class="card__desc">净菜库存管理与盘点</div>
|
||||
</a>
|
||||
<a class="card" href="ratio-scale/index.html">
|
||||
<div class="card__icon">📦</div>
|
||||
<div class="card__name">净菜配比秤</div>
|
||||
<div class="card__desc">净菜打包:AI识别、称重、按员工拆单塑封</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 其他设备 -->
|
||||
<div class="section-title">其他设备</div>
|
||||
<!-- 健康生产 -->
|
||||
<div class="section-title">健康生产</div>
|
||||
<div class="grid">
|
||||
<a class="card" href="processing-prompt-screen/index.html">
|
||||
<div class="card__icon">📺</div>
|
||||
<div class="card__name">加工提示屏</div>
|
||||
<div class="card__desc">加工流程提示与操作指引</div>
|
||||
</a>
|
||||
<a class="card" href="plate-dispenser/index.html">
|
||||
<div class="card__icon">🍽️</div>
|
||||
<div class="card__name">吐盘机</div>
|
||||
<div class="card__desc">自动出盘与回盘管理</div>
|
||||
</a>
|
||||
<a class="card" href="canteen-recharge-screen/index.html">
|
||||
<div class="card__icon">💳</div>
|
||||
<div class="card__name">餐线充值屏</div>
|
||||
<div class="card__desc">餐卡充值与余额查询</div>
|
||||
</a>
|
||||
<a class="card" href="checkout-device/index.html">
|
||||
<div class="card__icon">🧾</div>
|
||||
<div class="card__name">结算设备</div>
|
||||
<div class="card__desc">智能结算与支付</div>
|
||||
</a>
|
||||
<a class="card" href="nutrition-score-screen/index.html">
|
||||
<div class="card__icon">📊</div>
|
||||
<div class="card__name">营养得分屏</div>
|
||||
<div class="card__desc">个人营养评分与健康建议</div>
|
||||
</a>
|
||||
<a class="card" href="food-safety-monitor/index.html">
|
||||
<div class="card__icon">🛡️</div>
|
||||
<div class="card__icon">🛡️</div>
|
||||
<div class="card__name">食安监控设备</div>
|
||||
<div class="card__desc">食品安全监控与预警</div>
|
||||
</a>
|
||||
<a class="card" href="utility-meter/index.html">
|
||||
<div class="card__icon">⚡</div>
|
||||
<div class="card__icon">⚡</div>
|
||||
<div class="card__name">水电气计量表</div>
|
||||
<div class="card__desc">水电气能耗计量与统计</div>
|
||||
</a>
|
||||
<a class="card" href="clean-veg-kitchen-cabinet/index.html">
|
||||
<div class="card__icon">🗄️</div>
|
||||
<div class="card__name">净菜厨房柜</div>
|
||||
<div class="card__desc">净菜暂存与保鲜存储</div>
|
||||
</a>
|
||||
<a class="card" href="processing-prompt-screen/index.html">
|
||||
<div class="card__icon">📺</div>
|
||||
<div class="card__name">加工提示屏</div>
|
||||
<div class="card__desc">加工流程提示与操作指引</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 营养用餐 -->
|
||||
<div class="section-title">营养用餐</div>
|
||||
<div class="grid">
|
||||
<a class="card" href="nutrition-detection/index.html">
|
||||
<div class="card__icon">🔍</div>
|
||||
<div class="card__name">营养检测设备</div>
|
||||
<div class="card__desc">营养成分检测与分析</div>
|
||||
</a>
|
||||
<a class="card" href="plate-dispenser/index.html">
|
||||
<div class="card__icon">🍽️</div>
|
||||
<div class="card__name">吐盘机</div>
|
||||
<div class="card__desc">自动出盘与回盘管理</div>
|
||||
</a>
|
||||
<a class="card" href="canteen-recharge-screen/index.html">
|
||||
<div class="card__icon">💳</div>
|
||||
<div class="card__name">餐线充值屏</div>
|
||||
<div class="card__desc">餐卡充值与余额查询</div>
|
||||
</a>
|
||||
<a class="card" href="canteen-nutrition-scale/index.html">
|
||||
<div class="card__icon">🥘</div>
|
||||
<div class="card__name">餐线营养秤</div>
|
||||
<div class="card__desc">开餐、补餐、取餐、餐品展示、设置、采样</div>
|
||||
</a>
|
||||
<a class="card" href="stall-nutrition-scale/index.html">
|
||||
<div class="card__icon">🏪</div>
|
||||
<div class="card__name">档口营养秤</div>
|
||||
<div class="card__desc">档口菜品营养分析与称重结算</div>
|
||||
</a>
|
||||
<a class="card" href="checkout-device/index.html">
|
||||
<div class="card__icon">🧾</div>
|
||||
<div class="card__name">结算设备</div>
|
||||
<div class="card__desc">智能结算与支付</div>
|
||||
</a>
|
||||
<a class="card" href="nutrition-score-screen/index.html">
|
||||
<div class="card__icon">📊</div>
|
||||
<div class="card__name">营养得分屏</div>
|
||||
<div class="card__desc">个人营养评分与健康建议</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>营养检测设备 - 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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); }
|
||||
.main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); }
|
||||
.placeholder { text-align: center; color: rgba(255,255,255,0.3); }
|
||||
.placeholder__icon { font-size: 48px; margin-bottom: 16px; }
|
||||
.placeholder__text { font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">营养检测设备</span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__icon">🚧</div>
|
||||
<div class="placeholder__text">营养检测设备 页面待开发</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,230 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜配比秤 - 欢迎 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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); }
|
||||
.welcome-main { display: flex; align-items: stretch; height: calc(100% - 44px); padding: 24px 32px; gap: 32px; }
|
||||
.login-panel { flex: 0 0 420px; display: flex; flex-direction: column; gap: 20px; background: #1a2b4a; border-radius: 16px; padding: 24px; }
|
||||
.login-panel__title { font-size: 16px; font-weight: 600; color: rgba(255,255,255,0.8); text-align: center; }
|
||||
.login-tabs { display: flex; gap: 0; border-bottom: 1px solid rgba(255,255,255,0.1); }
|
||||
.login-tab { flex: 1; padding: 10px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.5); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s; }
|
||||
.login-tab:hover { color: rgba(255,255,255,0.8); }
|
||||
.login-tab.active { color: #4a90d9; border-bottom-color: #4a90d9; }
|
||||
.face-section { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 20px 0; }
|
||||
.face-section.hidden { display: none; }
|
||||
.face-frame { width: 180px; height: 180px; border-radius: 50%; border: 3px dashed rgba(74,144,217,0.4); display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px; position: relative; background: rgba(26,43,74,0.3); cursor: pointer; transition: all 0.3s; }
|
||||
.face-frame:hover { border-color: #4a90d9; background: rgba(74,144,217,0.1); }
|
||||
.face-frame__icon { font-size: 40px; color: rgba(255,255,255,0.3); }
|
||||
.face-frame__text { font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; }
|
||||
@keyframes ringPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.3); } 50% { box-shadow: 0 0 0 12px rgba(74,144,217,0); } }
|
||||
.face-frame.detecting { border-color: #4a90d9; border-style: solid; animation: ringPulse 1.5s infinite; }
|
||||
.face-frame.success { border-color: #52c41a; border-style: solid; background: rgba(82,196,26,0.08); }
|
||||
.face-info { text-align: center; min-height: 48px; background: rgba(0,0,0,0.15); border-radius: 10px; padding: 10px 16px; width: 100%; }
|
||||
.face-info__status { font-size: 12px; color: rgba(255,255,255,0.35); display: block; }
|
||||
.face-info__name { font-size: 15px; font-weight: 700; color: #52c41a; display: none; }
|
||||
.face-info__name.show { display: block; }
|
||||
.face-info__detail { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; display: none; }
|
||||
.face-info__detail.show { display: block; }
|
||||
.password-section { display: flex; flex-direction: column; gap: 14px; padding: 16px 0; }
|
||||
.password-section.hidden { display: none; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
||||
.form-label { font-size: 12px; color: rgba(255,255,255,0.6); }
|
||||
.form-input { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 12px 14px; font-size: 14px; color: #fff; outline: none; transition: border-color 0.2s; }
|
||||
.form-input:focus { border-color: #4a90d9; }
|
||||
.form-input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
.login-btn { background: #4a90d9; border: none; border-radius: 8px; padding: 12px; font-size: 14px; font-weight: 600; color: #fff; cursor: pointer; transition: all 0.2s; margin-top: 8px; }
|
||||
.login-btn:hover { background: #5ba0e9; transform: translateY(-1px); }
|
||||
.menu-panel { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 14px; }
|
||||
.menu-panel__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
||||
.menu-panel__title { font-size: 14px; color: rgba(255,255,255,0.4); letter-spacing: 1px; }
|
||||
.menu-panel__user { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.menu-panel__user.show { display: block; }
|
||||
.menu-panel__user.hidden { display: none; }
|
||||
.menu-btn { display: flex; align-items: center; gap: 14px; padding: 16px 20px; border-radius: 12px; background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06); cursor: pointer; transition: all 0.2s; text-decoration: none; color: #fff; }
|
||||
.menu-btn:hover { transform: translateX(4px); border-color: rgba(74,144,217,0.3); box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
|
||||
.menu-btn:active { transform: translateX(2px) scale(0.99); }
|
||||
.menu-btn.disabled { opacity: 0.4; cursor: not-allowed; transform: none !important; box-shadow: none !important; border-color: transparent !important; }
|
||||
.menu-btn__icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 22px; flex-shrink: 0; }
|
||||
.menu-btn__icon--blue { background: rgba(74,144,217,0.15); }
|
||||
.menu-btn__icon--green { background: rgba(82,196,26,0.15); }
|
||||
.menu-btn__icon--orange { background: rgba(250,173,22,0.15); }
|
||||
.menu-btn__icon--purple { background: rgba(114,46,209,0.15); }
|
||||
.menu-btn__icon--gray { background: rgba(255,255,255,0.08); }
|
||||
.menu-btn__info { flex: 1; }
|
||||
.menu-btn__title { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
|
||||
.menu-btn__desc { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.menu-btn__arrow { font-size: 16px; color: rgba(255,255,255,0.25); flex-shrink: 0; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; max-width: 90%; text-align: center; line-height: 1.5; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--warn { background: rgba(250,173,22,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">净菜配比秤</span>
|
||||
</div>
|
||||
<div class="welcome-main">
|
||||
<div class="login-panel">
|
||||
<div class="login-panel__title">员工登录</div>
|
||||
<div class="login-tabs">
|
||||
<div class="login-tab active" data-tab="face" onclick="switchTab('face')">人脸识别</div>
|
||||
<div class="login-tab" data-tab="password" onclick="switchTab('password')">账号密码</div>
|
||||
</div>
|
||||
<div class="face-section" id="faceSection">
|
||||
<div class="face-frame" id="faceFrame" onclick="startFaceDetect()">
|
||||
<div class="face-frame__icon" id="faceIcon">👤</div>
|
||||
<div class="face-frame__text" id="faceText">点击激活<br>人脸识别</div>
|
||||
</div>
|
||||
<div class="face-info">
|
||||
<span class="face-info__status" id="faceStatus">识别成功后即可使用各项功能</span>
|
||||
<div class="face-info__name" id="faceName"></div>
|
||||
<div class="face-info__detail" id="faceDetail"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="password-section hidden" id="passwordSection">
|
||||
<div class="form-group">
|
||||
<label class="form-label">员工工号</label>
|
||||
<input type="text" class="form-input" id="empId" placeholder="请输入员工工号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">登录密码</label>
|
||||
<input type="password" class="form-input" id="empPassword" placeholder="请输入登录密码">
|
||||
</div>
|
||||
<button class="login-btn" onclick="passwordLogin()">登录</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-panel">
|
||||
<div class="menu-panel__header">
|
||||
<div class="menu-panel__title">📌 功能菜单</div>
|
||||
<div class="menu-panel__user hidden" id="menuUser"></div>
|
||||
</div>
|
||||
<a class="menu-btn disabled" id="btnTasks" onclick="handleMenu('01-packing-tasks.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">📋</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">今日任务</div>
|
||||
<div class="menu-btn__desc">查看系统分配的打包任务清单</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
<a class="menu-btn disabled" id="btnPacking" onclick="handleMenu('02-weighing-packing.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--green">📦</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">称重打包</div>
|
||||
<div class="menu-btn__desc">净菜识别、称重、按单打包</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
<a class="menu-btn disabled" id="btnHistory" onclick="handleMenu('03-history.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--orange">📝</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">打包记录</div>
|
||||
<div class="menu-btn__desc">历史打包记录查看与筛选</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
<a class="menu-btn disabled" id="btnSettings" onclick="handleMenu('04-settings.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--gray">⚙️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">设备设置</div>
|
||||
<div class="menu-btn__desc">设备参数配置与状态查看</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
var loginState = 'idle';
|
||||
var currentUser = null;
|
||||
var loginTab = 'face';
|
||||
var mockUsers = [
|
||||
{ name: '刘九', id: 'EMP-20260050', dept: '打包车间', role: '打包员', empId: '30001', password: '123456' },
|
||||
{ name: '陈十', id: 'EMP-20260055', dept: '打包车间', role: '打包员', empId: '30002', password: '123456' },
|
||||
{ name: '杨一', id: 'EMP-20260060', dept: '质检部', role: '质检员', empId: '30003', password: '123456' }
|
||||
];
|
||||
var menuButtons = ['btnTasks', 'btnPacking', 'btnHistory', 'btnSettings'];
|
||||
|
||||
function switchTab(tab) {
|
||||
loginTab = tab;
|
||||
document.querySelectorAll('.login-tab').forEach(function(el) { el.classList.toggle('active', el.dataset.tab === tab); });
|
||||
document.getElementById('faceSection').classList.toggle('hidden', tab !== 'face');
|
||||
document.getElementById('passwordSection').classList.toggle('hidden', tab !== 'password');
|
||||
}
|
||||
|
||||
function startFaceDetect() {
|
||||
if (loginState !== 'idle') return;
|
||||
loginState = 'detecting';
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.add('detecting');
|
||||
icon.innerHTML = '🔍';
|
||||
text.textContent = '识别中...';
|
||||
showToast('正在识别人脸...', 1500);
|
||||
setTimeout(function() {
|
||||
var user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
|
||||
loginSuccess(user);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function passwordLogin() {
|
||||
var empId = document.getElementById('empId').value.trim();
|
||||
var password = document.getElementById('empPassword').value.trim();
|
||||
if (!empId || !password) { showToast('请输入工号和密码', 2000, 'warn'); return; }
|
||||
var user = mockUsers.find(function(u) { return u.empId === empId && u.password === password; });
|
||||
if (!user) { showToast('工号或密码错误', 2000, 'error'); return; }
|
||||
loginSuccess(user);
|
||||
}
|
||||
|
||||
function loginSuccess(user) {
|
||||
currentUser = user;
|
||||
loginState = 'success';
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.remove('detecting');
|
||||
frame.classList.add('success');
|
||||
icon.innerHTML = '✅';
|
||||
text.innerHTML = user.name;
|
||||
document.getElementById('faceStatus').style.display = 'none';
|
||||
var nameEl = document.getElementById('faceName');
|
||||
nameEl.textContent = user.name + ' · ' + user.role;
|
||||
nameEl.classList.add('show');
|
||||
var detailEl = document.getElementById('faceDetail');
|
||||
detailEl.textContent = user.dept + ' · ' + user.id;
|
||||
detailEl.classList.add('show');
|
||||
var menuUser = document.getElementById('menuUser');
|
||||
menuUser.textContent = '当前用户:' + user.name;
|
||||
menuUser.classList.remove('hidden');
|
||||
menuButtons.forEach(function(id) { document.getElementById(id).classList.remove('disabled'); });
|
||||
showToast('欢迎,' + user.name, 2000);
|
||||
}
|
||||
|
||||
function handleMenu(target) {
|
||||
if (loginState !== 'success' || !currentUser) { showToast('请先登录', 2000, 'warn'); return; }
|
||||
window.location.href = target + '?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'warn') el.classList.add('toast--warn');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,604 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜配比秤 - 今日任务 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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 { height: calc(100% - 44px); padding: 20px 28px; display: flex; flex-direction: column; gap: 16px; }
|
||||
|
||||
/* 状态标签卡(统计+筛选二合一) */
|
||||
.tasks-tabs { display: flex; gap: 10px; }
|
||||
.status-tab {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border-radius: 8px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; user-select: none;
|
||||
}
|
||||
.status-tab:hover { border-color: rgba(255,255,255,0.15); }
|
||||
.status-tab.active--pending { background: rgba(250,173,22,0.15); border-color: rgba(250,173,22,0.4); }
|
||||
.status-tab.active--done { background: rgba(82,196,26,0.15); border-color: rgba(82,196,26,0.4); }
|
||||
.status-tab__label { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.7); }
|
||||
.status-tab__count { font-size: 20px; font-weight: 700; color: #fff; }
|
||||
|
||||
.tasks-section { flex: 1; display: flex; flex-direction: column; gap: 12px; overflow: hidden; }
|
||||
.task-list {
|
||||
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
|
||||
padding-right: 4px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.task-list::-webkit-scrollbar { width: 4px; }
|
||||
.task-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.task-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.task-item {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
border: 1px solid rgba(255,255,255,0.06); cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.task-item:hover { border-color: rgba(74,144,217,0.3); box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
|
||||
.task-icon { width: 52px; height: 52px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 26px; flex-shrink: 0; }
|
||||
.task-icon--potato { background: rgba(250,173,22,0.15); }
|
||||
.task-icon--tomato { background: rgba(255,77,79,0.15); }
|
||||
.task-icon--cabbage { background: rgba(82,196,26,0.15); }
|
||||
.task-icon--carrot { background: rgba(250,140,22,0.15); }
|
||||
.task-icon--cucumber { background: rgba(20,184,166,0.15); }
|
||||
.task-info { flex: 1; display: flex; flex-direction: column; gap: 4px; }
|
||||
.task-name { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.task-detail { font-size: 12px; color: rgba(255,255,255,0.5); display: flex; gap: 12px; }
|
||||
.task-detail__item { display: flex; align-items: center; gap: 4px; }
|
||||
.task-target { display: flex; align-items: center; gap: 6px; padding: 4px 10px; background: rgba(74,144,217,0.15); border-radius: 4px; font-size: 11px; color: #4a90d9; }
|
||||
.task-progress { width: 140px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.progress-bar { height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; }
|
||||
.progress-bar__fill { height: 100%; border-radius: 3px; }
|
||||
.progress-bar__fill--blue { background: #4a90d9; }
|
||||
.progress-bar__fill--green { background: #52c41a; }
|
||||
.progress-text { font-size: 11px; color: rgba(255,255,255,0.5); text-align: right; }
|
||||
.task-status { width: 80px; text-align: center; }
|
||||
.status-badge { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
||||
.status-badge--pending { background: rgba(250,173,22,0.15); color: #faad16; }
|
||||
.status-badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.task-action { width: 80px; }
|
||||
.action-btn { padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 500; background: #4a90d9; color: #fff; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.action-btn:hover { background: #5ba0e9; }
|
||||
.action-btn--done { background: rgba(82,196,26,0.15); color: #52c41a; cursor: default; }
|
||||
|
||||
/* 弹窗遮罩层 */
|
||||
.modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); z-index: 900; display: none; align-items: center; justify-content: center; }
|
||||
.modal-overlay.open { display: flex; }
|
||||
.modal-box { width: 1220px; height: 660px; background: #0e1326; border-radius: 16px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 24px 64px rgba(0,0,0,0.7); }
|
||||
.modal-header { display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px; }
|
||||
.modal-close { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); transition: color 0.2s; background: none; border: none; }
|
||||
.modal-close:hover { color: #fff; }
|
||||
.modal-title { flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); }
|
||||
.modal-body { flex: 1; display: flex; gap: 20px; padding: 16px 20px; overflow: hidden; }
|
||||
.modal-scale-panel { flex: 0 0 540px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.modal-task { background: #1a2b4a; border-radius: 12px; padding: 12px 16px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.modal-task__body { display: flex; align-items: center; gap: 12px; }
|
||||
.modal-task__icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 22px; }
|
||||
.modal-task__info { flex: 1; }
|
||||
.modal-task__name { font-size: 15px; font-weight: 600; color: #fff; margin-bottom: 2px; }
|
||||
.modal-task__target { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: rgba(74,144,217,0.15); border-radius: 4px; font-size: 11px; color: #4a90d9; }
|
||||
.modal-task__progress { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
||||
.progress-mini { width: 80px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
|
||||
.progress-mini__fill { height: 100%; background: #4a90d9; border-radius: 2px; }
|
||||
.progress-mini__text { font-size: 11px; color: rgba(255,255,255,0.5); }
|
||||
.scale-display { flex: 1; background: #1a2b4a; border-radius: 14px; padding: 18px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; border: 2px solid rgba(255,255,255,0.06); }
|
||||
.scale-display.weighing { border-color: #4a90d9; }
|
||||
.scale-display.stable { border-color: #52c41a; }
|
||||
.ai-result { display: flex; align-items: center; gap: 14px; padding: 14px 18px; background: rgba(0,0,0,0.2); border-radius: 10px; width: 100%; }
|
||||
.ai-result__icon { width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 26px; background: rgba(74,144,217,0.15); }
|
||||
.ai-result__info { flex: 1; }
|
||||
.ai-result__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 2px; }
|
||||
.ai-result__name { font-size: 18px; font-weight: 700; color: #fff; }
|
||||
.ai-result__confidence { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
.ai-result__status { font-size: 12px; padding: 4px 10px; border-radius: 4px; }
|
||||
.ai-result__status--matching { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.ai-result__status--mismatch { background: rgba(255,77,79,0.15); color: #ff4d4f; }
|
||||
.ai-result__status--idle { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); }
|
||||
.weight-display { text-align: center; }
|
||||
.weight-display__value { font-size: 60px; font-weight: 700; color: #fff; line-height: 1; font-family: "Courier New", monospace; }
|
||||
.weight-display__unit { font-size: 18px; color: rgba(255,255,255,0.5); margin-left: 4px; }
|
||||
.weight-display__status { font-size: 13px; color: rgba(255,255,255,0.4); margin-top: 6px; }
|
||||
.weight-display__status.weighing { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
.scale-actions { display: flex; gap: 12px; width: 100%; }
|
||||
.scale-btn { flex: 1; padding: 12px; border-radius: 10px; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.scale-btn--primary { background: #52c41a; color: #fff; }
|
||||
.scale-btn--primary:hover { background: #62d42a; }
|
||||
.scale-btn--primary:disabled { background: rgba(82,196,26,0.3); cursor: not-allowed; }
|
||||
.scale-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.scale-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
.modal-records-panel { flex: 1; display: flex; flex-direction: column; gap: 12px; }
|
||||
.modal-records { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 14px 16px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.modal-records__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
||||
.modal-records__title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.modal-records__count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.modal-records__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; }
|
||||
.modal-records__list::-webkit-scrollbar { width: 4px; }
|
||||
.modal-records__list::-webkit-scrollbar-track { background: transparent; }
|
||||
.modal-records__list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.modal-record { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: rgba(0,0,0,0.15); border-radius: 8px; }
|
||||
.modal-record__icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; background: rgba(250,173,22,0.15); }
|
||||
.modal-record__info { flex: 1; }
|
||||
.modal-record__name { font-size: 12px; font-weight: 500; color: #fff; }
|
||||
.modal-record__target { font-size: 11px; color: #4a90d9; }
|
||||
.modal-record__time { font-size: 10px; color: rgba(255,255,255,0.3); }
|
||||
.modal-record__weight { font-size: 13px; font-weight: 600; color: #52c41a; }
|
||||
.modal-record__seal { font-size: 11px; padding: 2px 6px; background: rgba(74,144,217,0.15); border-radius: 3px; color: #4a90d9; }
|
||||
.modal-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: rgba(255,255,255,0.3); }
|
||||
.modal-empty__icon { font-size: 28px; margin-bottom: 8px; }
|
||||
.modal-empty__text { font-size: 12px; }
|
||||
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1100; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">今日打包任务</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<!-- 状态标签卡(统计+筛选) -->
|
||||
<div class="tasks-tabs">
|
||||
<div class="status-tab active--pending" data-filter="pending" onclick="filterTasks('pending', this)">
|
||||
<span class="status-tab__label">待处理</span>
|
||||
<span class="status-tab__count" id="pendingCount">4</span>
|
||||
</div>
|
||||
<div class="status-tab" data-filter="done" onclick="filterTasks('done', this)">
|
||||
<span class="status-tab__label">已完成</span>
|
||||
<span class="status-tab__count" id="doneCount">6</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tasks-section">
|
||||
<div class="task-list" id="taskList">
|
||||
<!-- 任务项1 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--potato">🥔</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">土豆丝</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 5.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 张三</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--blue" style="width: 60%"></div></div>
|
||||
<div class="progress-text">已包 3.0 / 需 5.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--pending">待处理</span></div>
|
||||
<div class="task-action"><button class="action-btn" onclick="openModal('potato')">去处理</button></div>
|
||||
</div>
|
||||
<!-- 任务项2 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--carrot">🥕</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">胡萝卜丝</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 2.8斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 12:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 李四</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--blue" style="width: 0%"></div></div>
|
||||
<div class="progress-text">已包 0 / 需 2.8 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--pending">待处理</span></div>
|
||||
<div class="task-action"><button class="action-btn" onclick="openModal('carrot')">去处理</button></div>
|
||||
</div>
|
||||
<!-- 任务项3 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--cucumber">🥒</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">黄瓜片</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 4.2斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 王五</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div></div>
|
||||
<div class="progress-text">已包 4.2 / 需 4.2 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="task-action"><button class="action-btn action-btn--done">完成</button></div>
|
||||
</div>
|
||||
<!-- 任务项4 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--cabbage">🥬</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">白菜段</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 8.5斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 10:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 赵六</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div></div>
|
||||
<div class="progress-text">已包 8.5 / 需 8.5 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="task-action"><button class="action-btn action-btn--done">完成</button></div>
|
||||
</div>
|
||||
<!-- 任务项5 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--tomato">🍅</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">西红柿块</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 3.5斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 12:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 孙七</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--blue" style="width: 0%"></div></div>
|
||||
<div class="progress-text">已包 0 / 需 3.5 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--pending">待处理</span></div>
|
||||
<div class="task-action"><button class="action-btn" onclick="openModal('tomato')">去处理</button></div>
|
||||
</div>
|
||||
<!-- 任务项6 -->
|
||||
<div class="task-item" data-status="pending">
|
||||
<div class="task-icon task-icon--potato">🥔</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">土豆块(晚餐)</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 3.2斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 16:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 张三</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--blue" style="width: 30%"></div></div>
|
||||
<div class="progress-text">已包 1.0 / 需 3.2 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--pending">待处理</span></div>
|
||||
<div class="task-action"><button class="action-btn" onclick="openModal('potato_dinner')">去处理</button></div>
|
||||
</div>
|
||||
<!-- 任务项7 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--carrot">🥕</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">胡萝卜丁</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 2.5斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 周八</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div></div>
|
||||
<div class="progress-text">已包 2.5 / 需 2.5 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="task-action"><button class="action-btn action-btn--done">完成</button></div>
|
||||
</div>
|
||||
<!-- 任务项8 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--cucumber">🥒</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">黄瓜条</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 3.0斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 刘九</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div></div>
|
||||
<div class="progress-text">已包 3.0 / 需 3.0 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="task-action"><button class="action-btn action-btn--done">完成</button></div>
|
||||
</div>
|
||||
<!-- 任务项9 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--potato">🥔</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">土豆条</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 2.6斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 11:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 陈十</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div></div>
|
||||
<div class="progress-text">已包 2.6 / 需 2.6 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="task-action"><button class="action-btn action-btn--done">完成</button></div>
|
||||
</div>
|
||||
<!-- 任务项10 -->
|
||||
<div class="task-item" data-status="done">
|
||||
<div class="task-icon task-icon--tomato">🍅</div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">小番茄</div>
|
||||
<div class="task-detail">
|
||||
<span class="task-detail__item">📦 总量: 1.8斤</span>
|
||||
<span class="task-detail__item">⏰ 截止: 14:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-target">👤 分配给: 杨一</div>
|
||||
<div class="task-progress">
|
||||
<div class="progress-bar"><div class="progress-bar__fill progress-bar__fill--green" style="width: 100%"></div></div>
|
||||
<div class="progress-text">已包 1.8 / 需 1.8 斤</div>
|
||||
</div>
|
||||
<div class="task-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="task-action"><button class="action-btn action-btn--done">完成</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 处理弹窗 -->
|
||||
<div class="modal-overlay" id="modalOverlay">
|
||||
<div class="modal-box">
|
||||
<div class="modal-header">
|
||||
<button class="modal-close" onclick="closeModal()">◀ 返回任务列表</button>
|
||||
<span class="modal-title" id="modalTitle">称重打包</span>
|
||||
<span style="width:80px"></span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-scale-panel">
|
||||
<div class="modal-task">
|
||||
<div class="modal-task__body">
|
||||
<div class="modal-task__icon task-icon--potato" id="modalTaskIcon">🥔</div>
|
||||
<div class="modal-task__info">
|
||||
<div class="modal-task__name" id="modalTaskName">土豆丝</div>
|
||||
<span class="modal-task__target" id="modalTaskTarget">👤 分配给: 张三</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-task__progress">
|
||||
<div class="progress-mini">
|
||||
<div class="progress-mini__fill" id="modalProgressFill" style="width:60%"></div>
|
||||
</div>
|
||||
<span class="progress-mini__text" id="modalProgressText">3.0/5.0斤</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale-display" id="scaleDisplay">
|
||||
<div class="ai-result">
|
||||
<div class="ai-result__icon" id="aiIcon">📇</div>
|
||||
<div class="ai-result__info">
|
||||
<div class="ai-result__label">AI 识别结果</div>
|
||||
<div class="ai-result__name" id="aiName">等待放置食材...</div>
|
||||
<div class="ai-result__confidence" id="aiConfidence"></div>
|
||||
</div>
|
||||
<span class="ai-result__status ai-result__status--idle" id="aiStatus">待机</span>
|
||||
</div>
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请将食材放置秤上</div>
|
||||
</div>
|
||||
<div class="scale-actions">
|
||||
<button class="scale-btn scale-btn--secondary" onclick="resetScale()">清零</button>
|
||||
<button class="scale-btn scale-btn--primary" id="packBtn" onclick="confirmPack()" disabled>打包+塑封</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-records-panel">
|
||||
<div class="modal-records">
|
||||
<div class="modal-records__header">
|
||||
<span class="modal-records__title">打包记录</span>
|
||||
<span class="modal-records__count" id="recordsCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="modal-records__list" id="recordsList">
|
||||
<div class="modal-empty" id="emptyState">
|
||||
<div class="modal-empty__icon">📭</div>
|
||||
<div class="modal-empty__text">暂无打包记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// 筛选任务
|
||||
function filterTasks(status, tab) {
|
||||
document.querySelectorAll('.status-tab').forEach(function(el) {
|
||||
el.classList.remove('active--pending', 'active--done');
|
||||
});
|
||||
tab.classList.add(status === 'pending' ? 'active--pending' : 'active--done');
|
||||
document.querySelectorAll('.task-item').forEach(function(item) {
|
||||
item.style.display = (item.dataset.status === status) ? 'flex' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// ── 弹窗逻辑 ──
|
||||
var modalKey = '';
|
||||
var scaleState = 'idle';
|
||||
var currentWeight = 0;
|
||||
var currentAiResult = null;
|
||||
var records = [];
|
||||
|
||||
var taskData = {
|
||||
'potato': { name: '土豆丝', icon: '🥔', iconClass: 'task-icon--potato', need: 5.0, done: 3.0, target: '张三', deadline: '11:30' },
|
||||
'carrot': { name: '胡萝卜丝', icon: '🥕', iconClass: 'task-icon--carrot', need: 2.8, done: 0, target: '李四', deadline: '12:00' },
|
||||
'tomato': { name: '西红柿块', icon: '🍅', iconClass: 'task-icon--tomato', need: 3.5, done: 0, target: '孙七', deadline: '12:00' },
|
||||
'potato_dinner': { name: '土豆块(晚餐)', icon: '🥔', iconClass: 'task-icon--potato', need: 3.2, done: 1.0, target: '张三', deadline: '16:00' }
|
||||
};
|
||||
|
||||
var aiResults = {
|
||||
'potato': { name: '土豆丝', icon: '🥔', confidence: 97 },
|
||||
'tomato': { name: '西红柿块', icon: '🍅', confidence: 95 },
|
||||
'carrot': { name: '胡萝卜丝', icon: '🥕', confidence: 96 },
|
||||
'cucumber':{ name: '黄瓜片', icon: '🥒', confidence: 94 },
|
||||
'cabbage': { name: '白菜段', icon: '🥬', confidence: 93 }
|
||||
};
|
||||
|
||||
function openModal(key) {
|
||||
modalKey = key;
|
||||
var task = taskData[key] || taskData['potato'];
|
||||
var iconEl = document.getElementById('modalTaskIcon');
|
||||
iconEl.innerHTML = task.icon;
|
||||
iconEl.className = 'modal-task__icon ' + task.iconClass;
|
||||
document.getElementById('modalTaskName').textContent = task.name;
|
||||
document.getElementById('modalTaskTarget').innerHTML = '👤 分配给: ' + task.target;
|
||||
var pct = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('modalProgressFill').style.width = pct + '%';
|
||||
document.getElementById('modalProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
document.getElementById('modalTitle').textContent = task.name + ' - 称重打包';
|
||||
resetScale();
|
||||
records = [];
|
||||
updateRecordsDisplay();
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
setTimeout(function() { startDetecting(key.replace('_dinner','')); }, 1500);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').classList.remove('open');
|
||||
}
|
||||
|
||||
function startDetecting(ingredientKey) {
|
||||
scaleState = 'detecting';
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.add('weighing');
|
||||
scale.classList.remove('stable');
|
||||
document.getElementById('weightStatus').textContent = '正在称重...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status weighing';
|
||||
var targetWeight = 2.05;
|
||||
var anim = 0;
|
||||
var timer = setInterval(function() {
|
||||
anim += 0.13;
|
||||
if (anim >= targetWeight) { anim = targetWeight; clearInterval(timer); onWeightStable(ingredientKey, targetWeight); }
|
||||
document.getElementById('weightValue').textContent = anim.toFixed(2);
|
||||
}, 100);
|
||||
setTimeout(function() {
|
||||
var result = aiResults[ingredientKey] || aiResults['potato'];
|
||||
currentAiResult = result;
|
||||
document.getElementById('aiIcon').innerHTML = result.icon;
|
||||
document.getElementById('aiName').textContent = result.name;
|
||||
document.getElementById('aiConfidence').textContent = '置信度: ' + result.confidence + '%';
|
||||
var task = taskData[modalKey];
|
||||
var isMatch = task.name === result.name || task.name.includes(result.name);
|
||||
var statusEl = document.getElementById('aiStatus');
|
||||
if (isMatch) {
|
||||
statusEl.textContent = '匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--matching';
|
||||
} else {
|
||||
statusEl.textContent = '不匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--mismatch';
|
||||
showToast('AI识别不匹配,请检查食材', 2500, 'error');
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function onWeightStable(key, weight) {
|
||||
scaleState = 'stable';
|
||||
currentWeight = weight;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing');
|
||||
scale.classList.add('stable');
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
document.getElementById('packBtn').disabled = false;
|
||||
}
|
||||
|
||||
function confirmPack() {
|
||||
if (scaleState !== 'stable' || !currentAiResult) return;
|
||||
var task = taskData[modalKey];
|
||||
task.done += currentWeight;
|
||||
records.unshift({
|
||||
name: currentAiResult.name,
|
||||
icon: currentAiResult.icon,
|
||||
target: task.target,
|
||||
weight: currentWeight,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
});
|
||||
updateRecordsDisplay();
|
||||
var pct = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('modalProgressFill').style.width = pct + '%';
|
||||
document.getElementById('modalProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
showToast('打包成功: ' + currentAiResult.name + ' ' + currentWeight.toFixed(2) + '斤 → ' + task.target, 2500, 'success');
|
||||
resetScale();
|
||||
}
|
||||
|
||||
function updateRecordsDisplay() {
|
||||
var list = document.getElementById('recordsList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
if (records.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('recordsCount').textContent = '共 0 条';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
document.getElementById('recordsCount').textContent = '共 ' + records.length + ' 条';
|
||||
var existing = list.querySelectorAll('.modal-record');
|
||||
existing.forEach(function(el) { el.remove(); });
|
||||
records.forEach(function(r) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'modal-record';
|
||||
item.innerHTML = '<div class="modal-record__icon">' + r.icon + '</div>' +
|
||||
'<div class="modal-record__info"><div class="modal-record__name">' + r.name + '</div>' +
|
||||
'<div class="modal-record__target">→ ' + r.target + '</div>' +
|
||||
'<div class="modal-record__time">' + r.time + '</div></div>' +
|
||||
'<div class="modal-record__weight">+' + r.weight.toFixed(2) + '斤</div>' +
|
||||
'<div class="modal-record__seal">🔒 已塑封</div>';
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function resetScale() {
|
||||
scaleState = 'idle';
|
||||
currentWeight = 0;
|
||||
currentAiResult = null;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing', 'stable');
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请将食材放置秤上';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('aiIcon').innerHTML = '📇';
|
||||
document.getElementById('aiName').textContent = '等待放置食材...';
|
||||
document.getElementById('aiConfidence').textContent = '';
|
||||
document.getElementById('aiStatus').textContent = '待机';
|
||||
document.getElementById('aiStatus').className = 'ai-result__status ai-result__status--idle';
|
||||
document.getElementById('packBtn').disabled = true;
|
||||
setTimeout(function() {
|
||||
if (scaleState === 'idle' && document.getElementById('modalOverlay').classList.contains('open')) {
|
||||
var keys = ['potato', 'carrot', 'cucumber'];
|
||||
startDetecting(keys[Math.floor(Math.random() * keys.length)]);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,410 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜配比秤 - 称重打包 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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 { height: calc(100% - 44px); padding: 20px 28px; display: flex; gap: 24px; }
|
||||
|
||||
/* 左侧:任务+秤 */
|
||||
.left-panel { flex: 0 0 560px; display: flex; flex-direction: column; gap: 14px; }
|
||||
|
||||
/* 当前任务卡 */
|
||||
.current-task { background: #1a2b4a; border-radius: 12px; padding: 14px 18px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.current-task__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
||||
.current-task__label { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.current-task__switch { font-size: 12px; color: #4a90d9; cursor: pointer; }
|
||||
.current-task__switch:hover { text-decoration: underline; }
|
||||
.current-task__body { display: flex; align-items: center; gap: 14px; }
|
||||
.current-task__icon { width: 48px; height: 48px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 24px; background: rgba(250,173,22,0.15); }
|
||||
.current-task__info { flex: 1; }
|
||||
.current-task__name { font-size: 15px; font-weight: 600; color: #fff; margin-bottom: 2px; }
|
||||
.current-task__target { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; background: rgba(74,144,217,0.15); border-radius: 4px; font-size: 11px; color: #4a90d9; }
|
||||
.current-task__progress { display: flex; align-items: center; gap: 8px; }
|
||||
.progress-mini { width: 80px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden; }
|
||||
.progress-mini__fill { height: 100%; background: #4a90d9; border-radius: 2px; }
|
||||
.progress-mini__text { font-size: 11px; color: rgba(255,255,255,0.5); }
|
||||
|
||||
/* 秤显示区 */
|
||||
.scale-display { flex: 1; background: #1a2b4a; border-radius: 16px; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; border: 2px solid rgba(255,255,255,0.06); }
|
||||
.scale-display.weighing { border-color: #4a90d9; }
|
||||
.scale-display.stable { border-color: #52c41a; }
|
||||
.ai-result { display: flex; align-items: center; gap: 14px; padding: 14px 20px; background: rgba(0,0,0,0.2); border-radius: 12px; width: 100%; }
|
||||
.ai-result__icon { width: 50px; height: 50px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 28px; background: rgba(74,144,217,0.15); }
|
||||
.ai-result__info { flex: 1; }
|
||||
.ai-result__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 2px; }
|
||||
.ai-result__name { font-size: 18px; font-weight: 700; color: #fff; }
|
||||
.ai-result__confidence { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
.ai-result__status { font-size: 12px; padding: 4px 10px; border-radius: 4px; }
|
||||
.ai-result__status--matching { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.ai-result__status--mismatch { background: rgba(255,77,79,0.15); color: #ff4d4f; }
|
||||
.ai-result__status--idle { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); }
|
||||
.weight-display { text-align: center; }
|
||||
.weight-display__value { font-size: 64px; font-weight: 700; color: #fff; line-height: 1; font-family: "Courier New", monospace; }
|
||||
.weight-display__unit { font-size: 18px; color: rgba(255,255,255,0.5); margin-left: 4px; }
|
||||
.weight-display__status { font-size: 13px; color: rgba(255,255,255,0.4); margin-top: 6px; }
|
||||
.weight-display__status.weighing { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
.scale-actions { display: flex; gap: 12px; width: 100%; }
|
||||
.scale-btn { flex: 1; padding: 12px; border-radius: 10px; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.scale-btn--primary { background: #52c41a; color: #fff; }
|
||||
.scale-btn--primary:hover { background: #62d42a; }
|
||||
.scale-btn--primary:disabled { background: rgba(82,196,26,0.3); cursor: not-allowed; }
|
||||
.scale-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.scale-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
|
||||
/* 右侧 */
|
||||
.right-panel { flex: 1; display: flex; flex-direction: column; gap: 14px; }
|
||||
|
||||
/* 打包统计 */
|
||||
.pack-stats { background: #1a2b4a; border-radius: 12px; padding: 14px 18px; display: flex; gap: 16px; }
|
||||
.pack-stat { flex: 1; text-align: center; }
|
||||
.pack-stat__value { font-size: 22px; font-weight: 700; color: #fff; }
|
||||
.pack-stat__label { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; }
|
||||
|
||||
/* 当前打包信息 */
|
||||
.packing-info { background: #1a2b4a; border-radius: 12px; padding: 16px 20px; border: 1px solid rgba(82,196,26,0.2); }
|
||||
.packing-info__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.packing-info__title { font-size: 14px; font-weight: 600; color: #52c41a; }
|
||||
.packing-info__badge { font-size: 11px; padding: 3px 8px; background: rgba(82,196,26,0.15); border-radius: 4px; color: #52c41a; }
|
||||
.packing-detail { display: flex; gap: 16px; }
|
||||
.packing-detail__item { flex: 1; }
|
||||
.packing-detail__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 4px; }
|
||||
.packing-detail__value { font-size: 16px; font-weight: 600; color: #fff; }
|
||||
|
||||
/* 打包记录 */
|
||||
.pack-records { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 14px 18px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.pack-records__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
|
||||
.pack-records__title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.pack-records__count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.pack-records__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; }
|
||||
.pack-records__list::-webkit-scrollbar { width: 4px; }
|
||||
.pack-records__list::-webkit-scrollbar-track { background: transparent; }
|
||||
.pack-records__list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.pack-record { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: rgba(0,0,0,0.15); border-radius: 8px; }
|
||||
.pack-record__icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; background: rgba(250,173,22,0.15); }
|
||||
.pack-record__info { flex: 1; }
|
||||
.pack-record__name { font-size: 12px; font-weight: 500; color: #fff; }
|
||||
.pack-record__target { font-size: 11px; color: #4a90d9; }
|
||||
.pack-record__time { font-size: 10px; color: rgba(255,255,255,0.3); }
|
||||
.pack-record__weight { font-size: 13px; font-weight: 600; color: #52c41a; }
|
||||
.pack-record__seal { font-size: 11px; padding: 2px 6px; background: rgba(74,144,217,0.15); border-radius: 3px; color: #4a90d9; }
|
||||
.empty-state { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: rgba(255,255,255,0.3); }
|
||||
.empty-state__icon { font-size: 32px; margin-bottom: 8px; }
|
||||
.empty-state__text { font-size: 12px; }
|
||||
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="01-packing-tasks.html">◀ 返回</a>
|
||||
<span class="top-bar__title">称重打包</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="left-panel">
|
||||
<div class="current-task">
|
||||
<div class="current-task__header">
|
||||
<span class="current-task__label">当前任务</span>
|
||||
<span class="current-task__switch" onclick="switchTask()">切换任务</span>
|
||||
</div>
|
||||
<div class="current-task__body">
|
||||
<div class="current-task__icon" id="taskIcon">🥔</div>
|
||||
<div class="current-task__info">
|
||||
<div class="current-task__name" id="taskName">土豆丝</div>
|
||||
<div class="current-task__target" id="taskTarget">👤 分配给: 张三</div>
|
||||
</div>
|
||||
<div class="current-task__progress">
|
||||
<div class="progress-mini">
|
||||
<div class="progress-mini__fill" id="taskProgressFill" style="width: 60%"></div>
|
||||
</div>
|
||||
<span class="progress-mini__text" id="taskProgressText">3.0/5.0斤</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scale-display" id="scaleDisplay">
|
||||
<div class="ai-result">
|
||||
<div class="ai-result__icon" id="aiIcon">📇</div>
|
||||
<div class="ai-result__info">
|
||||
<div class="ai-result__label">AI 识别结果</div>
|
||||
<div class="ai-result__name" id="aiName">等待放置食材...</div>
|
||||
<div class="ai-result__confidence" id="aiConfidence"></div>
|
||||
</div>
|
||||
<span class="ai-result__status ai-result__status--idle" id="aiStatus">待机</span>
|
||||
</div>
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请将食材放置秤上</div>
|
||||
</div>
|
||||
<div class="scale-actions">
|
||||
<button class="scale-btn scale-btn--secondary" onclick="resetScale()">清零</button>
|
||||
<button class="scale-btn scale-btn--primary" id="packBtn" onclick="confirmPack()" disabled>打包+塑封</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="pack-stats">
|
||||
<div class="pack-stat">
|
||||
<div class="pack-stat__value">8</div>
|
||||
<div class="pack-stat__label">今日打包数</div>
|
||||
</div>
|
||||
<div class="pack-stat">
|
||||
<div class="pack-stat__value">35.6</div>
|
||||
<div class="pack-stat__label">总重量(斤)</div>
|
||||
</div>
|
||||
<div class="pack-stat">
|
||||
<div class="pack-stat__value">6</div>
|
||||
<div class="pack-stat__label">分配员工数</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="packing-info">
|
||||
<div class="packing-info__header">
|
||||
<span class="packing-info__title">📦 当前打包中</span>
|
||||
<span class="packing-info__badge" id="packBadge">进行中</span>
|
||||
</div>
|
||||
<div class="packing-detail">
|
||||
<div class="packing-detail__item">
|
||||
<div class="packing-detail__label">食材</div>
|
||||
<div class="packing-detail__value" id="packIngredient">土豆丝</div>
|
||||
</div>
|
||||
<div class="packing-detail__item">
|
||||
<div class="packing-detail__label">目标员工</div>
|
||||
<div class="packing-detail__value" id="packTarget">张三</div>
|
||||
</div>
|
||||
<div class="packing-detail__item">
|
||||
<div class="packing-detail__label">目标重量</div>
|
||||
<div class="packing-detail__value" id="packTargetWeight">5.0斤</div>
|
||||
</div>
|
||||
<div class="packing-detail__item">
|
||||
<div class="packing-detail__label">已打包</div>
|
||||
<div class="packing-detail__value" id="packDone" style="color: #52c41a">3.0斤</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pack-records">
|
||||
<div class="pack-records__header">
|
||||
<span class="pack-records__title">本次打包记录</span>
|
||||
<span class="pack-records__count" id="recordsCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="pack-records__list" id="recordsList">
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">📭</div>
|
||||
<div class="empty-state__text">暂无打包记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var tasks = [
|
||||
{ id: 1, name: '土豆丝', icon: '🥔', need: 5.0, done: 3.0, target: '张三', deadline: '11:30' },
|
||||
{ id: 2, name: '胡萝卜丝', icon: '🥕', need: 2.8, done: 0, target: '李四', deadline: '12:00' },
|
||||
{ id: 3, name: '西红柿块', icon: '🍅', need: 3.5, done: 0, target: '孙七', deadline: '12:00' }
|
||||
];
|
||||
var currentTaskIndex = 0;
|
||||
var aiResults = {
|
||||
'potato': { name: '土豆丝', icon: '🥔', confidence: 97 },
|
||||
'tomato': { name: '西红柿块', icon: '🍅', confidence: 95 },
|
||||
'carrot': { name: '胡萝卜丝', icon: '🥕', confidence: 96 },
|
||||
'cucumber': { name: '黄瓜片', icon: '🥒', confidence: 94 },
|
||||
'cabbage': { name: '白菜段', icon: '🥬', confidence: 93 },
|
||||
'unknown': { name: '未识别', icon: '❓', confidence: 0 }
|
||||
};
|
||||
var state = 'idle';
|
||||
var currentWeight = 0;
|
||||
var currentAiResult = null;
|
||||
var records = [];
|
||||
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
function init() {
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
var ingredient = getUrlParam('ingredient');
|
||||
if (ingredient) {
|
||||
var idx = tasks.findIndex(function(t) { return t.name.includes(ingredient) || ingredient.includes(t.name); });
|
||||
if (idx >= 0) currentTaskIndex = idx;
|
||||
}
|
||||
updateTaskDisplay();
|
||||
simulateWeighing();
|
||||
}
|
||||
|
||||
function updateTaskDisplay() {
|
||||
var task = tasks[currentTaskIndex];
|
||||
document.getElementById('taskIcon').innerHTML = task.icon;
|
||||
document.getElementById('taskName').textContent = task.name;
|
||||
document.getElementById('taskTarget').innerHTML = '👤 分配给: ' + task.target;
|
||||
var percent = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('taskProgressFill').style.width = percent + '%';
|
||||
document.getElementById('taskProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
document.getElementById('packIngredient').textContent = task.name;
|
||||
document.getElementById('packTarget').textContent = task.target;
|
||||
document.getElementById('packTargetWeight').textContent = task.need.toFixed(1) + '斤';
|
||||
document.getElementById('packDone').textContent = task.done.toFixed(1) + '斤';
|
||||
}
|
||||
|
||||
function switchTask() {
|
||||
currentTaskIndex = (currentTaskIndex + 1) % tasks.length;
|
||||
updateTaskDisplay();
|
||||
resetScale();
|
||||
showToast('已切换到: ' + tasks[currentTaskIndex].name, 1500);
|
||||
}
|
||||
|
||||
function simulateWeighing() {
|
||||
setTimeout(function() { startDetecting('potato'); }, 2000);
|
||||
}
|
||||
|
||||
function startDetecting(ingredientKey) {
|
||||
state = 'detecting';
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.add('weighing');
|
||||
scale.classList.remove('stable');
|
||||
document.getElementById('weightStatus').textContent = '正在称重...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status weighing';
|
||||
var targetWeight = 2.05;
|
||||
var currentWeightAnim = 0;
|
||||
var weightInterval = setInterval(function() {
|
||||
currentWeightAnim += 0.13;
|
||||
if (currentWeightAnim >= targetWeight) {
|
||||
currentWeightAnim = targetWeight;
|
||||
clearInterval(weightInterval);
|
||||
onWeightStable(ingredientKey, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = currentWeightAnim.toFixed(2);
|
||||
}, 100);
|
||||
setTimeout(function() {
|
||||
var result = aiResults[ingredientKey];
|
||||
currentAiResult = result;
|
||||
document.getElementById('aiIcon').innerHTML = result.icon;
|
||||
document.getElementById('aiName').textContent = result.name;
|
||||
document.getElementById('aiConfidence').textContent = '置信度: ' + result.confidence + '%';
|
||||
var task = tasks[currentTaskIndex];
|
||||
var isMatch = task.name === result.name || task.name.includes(result.name);
|
||||
var statusEl = document.getElementById('aiStatus');
|
||||
if (isMatch) {
|
||||
statusEl.textContent = '匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--matching';
|
||||
} else {
|
||||
statusEl.textContent = '不匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--mismatch';
|
||||
showToast('AI识别不匹配,请检查食材', 2500, 'error');
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
|
||||
function onWeightStable(ingredientKey, weight) {
|
||||
state = 'stable';
|
||||
currentWeight = weight;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing');
|
||||
scale.classList.add('stable');
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
document.getElementById('packBtn').disabled = false;
|
||||
}
|
||||
|
||||
function confirmPack() {
|
||||
if (state !== 'stable' || !currentAiResult) return;
|
||||
var task = tasks[currentTaskIndex];
|
||||
task.done += currentWeight;
|
||||
var record = {
|
||||
name: currentAiResult.name,
|
||||
icon: currentAiResult.icon,
|
||||
target: task.target,
|
||||
weight: currentWeight,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
};
|
||||
records.unshift(record);
|
||||
updateRecordsDisplay();
|
||||
updateTaskDisplay();
|
||||
showToast('打包成功: ' + currentAiResult.name + ' ' + currentWeight.toFixed(2) + '斤 → ' + task.target, 2500, 'success');
|
||||
resetScale();
|
||||
}
|
||||
|
||||
function updateRecordsDisplay() {
|
||||
var list = document.getElementById('recordsList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
if (records.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('recordsCount').textContent = '共 0 条';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
document.getElementById('recordsCount').textContent = '共 ' + records.length + ' 条';
|
||||
var existingItems = list.querySelectorAll('.pack-record');
|
||||
existingItems.forEach(function(el) { el.remove(); });
|
||||
records.forEach(function(r) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'pack-record';
|
||||
item.innerHTML = '<div class="pack-record__icon">' + r.icon + '</div>' +
|
||||
'<div class="pack-record__info"><div class="pack-record__name">' + r.name + '</div>' +
|
||||
'<div class="pack-record__target">→ ' + r.target + '</div>' +
|
||||
'<div class="pack-record__time">' + r.time + '</div></div>' +
|
||||
'<div class="pack-record__weight">+' + r.weight.toFixed(2) + '斤</div>' +
|
||||
'<div class="pack-record__seal">🔒 已塑封</div>';
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function resetScale() {
|
||||
state = 'idle';
|
||||
currentWeight = 0;
|
||||
currentAiResult = null;
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing', 'stable');
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请将食材放置秤上';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('aiIcon').innerHTML = '📇';
|
||||
document.getElementById('aiName').textContent = '等待放置食材...';
|
||||
document.getElementById('aiConfidence').textContent = '';
|
||||
document.getElementById('aiStatus').textContent = '待机';
|
||||
document.getElementById('aiStatus').className = 'ai-result__status ai-result__status--idle';
|
||||
document.getElementById('packBtn').disabled = true;
|
||||
setTimeout(function() {
|
||||
var ingredients = ['potato', 'carrot', 'cucumber'];
|
||||
var randomIng = ingredients[Math.floor(Math.random() * ingredients.length)];
|
||||
startDetecting(randomIng);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜配比秤 - 打包记录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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 { height: calc(100% - 44px); padding: 20px 28px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.filter-bar { display: flex; align-items: center; gap: 12px; }
|
||||
.filter-group { display: flex; align-items: center; gap: 8px; }
|
||||
.filter-label { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.filter-select { background: #1a2b4a; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 12px; color: #fff; outline: none; cursor: pointer; }
|
||||
.filter-input { background: #1a2b4a; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 12px; color: #fff; outline: none; width: 140px; }
|
||||
.filter-btn { padding: 8px 16px; border-radius: 6px; font-size: 12px; background: #4a90d9; color: #fff; border: none; cursor: pointer; }
|
||||
.filter-btn:hover { background: #5ba0e9; }
|
||||
.filter-btn--secondary { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.filter-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
.stats-row { display: flex; gap: 14px; }
|
||||
.stat-card { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.stat-card__label { font-size: 12px; color: rgba(255,255,255,0.5); margin-bottom: 6px; }
|
||||
.stat-card__value { font-size: 24px; font-weight: 700; color: #fff; }
|
||||
.stat-card__value--green { color: #52c41a; }
|
||||
.stat-card__value--blue { color: #4a90d9; }
|
||||
.records-section { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px; display: flex; flex-direction: column; overflow: hidden; }
|
||||
.records-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
|
||||
.records-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.records-count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.table-header { display: flex; align-items: center; padding: 10px 16px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 8px; }
|
||||
.table-header__cell { font-size: 12px; color: rgba(255,255,255,0.5); font-weight: 500; }
|
||||
.col-batch { flex: 0 0 130px; }
|
||||
.col-name { flex: 0 0 100px; }
|
||||
.col-target { flex: 0 0 80px; }
|
||||
.col-weight { flex: 0 0 80px; }
|
||||
.col-time { flex: 0 0 80px; }
|
||||
.col-seal { flex: 0 0 80px; }
|
||||
.col-operator { flex: 0 0 80px; }
|
||||
.col-status { flex: 0 0 80px; }
|
||||
.col-action { flex: 1; text-align: right; }
|
||||
.records-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; }
|
||||
.records-list::-webkit-scrollbar { width: 4px; }
|
||||
.records-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.records-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.record-row { display: flex; align-items: center; padding: 12px 16px; background: rgba(0,0,0,0.15); border-radius: 8px; transition: background 0.2s; }
|
||||
.record-row:hover { background: rgba(74,144,217,0.08); }
|
||||
.record-row__cell { font-size: 13px; color: rgba(255,255,255,0.8); }
|
||||
.record-row__cell--icon { font-size: 18px; }
|
||||
.seal-badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 11px; background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
.status-badge { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
||||
.status-badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.action-link { font-size: 12px; color: #4a90d9; cursor: pointer; text-decoration: none; }
|
||||
.action-link:hover { text-decoration: underline; }
|
||||
.pagination { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.06); }
|
||||
.pagination__info { font-size: 12px; color: rgba(255,255,255,0.4); margin-right: auto; }
|
||||
.pagination__btn { width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6); font-size: 12px; border: none; cursor: pointer; transition: all 0.2s; }
|
||||
.pagination__btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.pagination__btn.active { background: #4a90d9; color: #fff; }
|
||||
.pagination__btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">打包记录</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="filter-bar">
|
||||
<div class="filter-group"><span class="filter-label">日期</span><input type="date" class="filter-input" value="2026-06-13"></div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">食材</span>
|
||||
<select class="filter-select"><option value="">全部</option><option value="potato">土豆丝</option><option value="carrot">胡萝卜丝</option><option value="cucumber">黄瓜片</option><option value="cabbage">白菜段</option><option value="tomato">西红柿块</option></select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">目标员工</span>
|
||||
<select class="filter-select"><option value="">全部</option><option>张三</option><option>李四</option><option>王五</option><option>赵六</option><option>孙七</option></select>
|
||||
</div>
|
||||
<button class="filter-btn">查询</button>
|
||||
<button class="filter-btn filter-btn--secondary">重置</button>
|
||||
</div>
|
||||
<div class="stats-row">
|
||||
<div class="stat-card"><div class="stat-card__label">今日打包批次</div><div class="stat-card__value stat-card__value--blue">15</div></div>
|
||||
<div class="stat-card"><div class="stat-card__label">打包总量</div><div class="stat-card__value">48.6斤</div></div>
|
||||
<div class="stat-card"><div class="stat-card__label">分配员工数</div><div class="stat-card__value">8</div></div>
|
||||
<div class="stat-card"><div class="stat-card__label">塑封完成率</div><div class="stat-card__value stat-card__value--green">100%</div></div>
|
||||
</div>
|
||||
<div class="records-section">
|
||||
<div class="records-header">
|
||||
<span class="records-title">打包记录列表</span>
|
||||
<span class="records-count">共 15 条记录</span>
|
||||
</div>
|
||||
<div class="table-header">
|
||||
<div class="table-header__cell col-batch">批次号</div>
|
||||
<div class="table-header__cell col-name">食材</div>
|
||||
<div class="table-header__cell col-target">目标员工</div>
|
||||
<div class="table-header__cell col-weight">重量</div>
|
||||
<div class="table-header__cell col-time">打包时间</div>
|
||||
<div class="table-header__cell col-seal">塑封</div>
|
||||
<div class="table-header__cell col-operator">操作员</div>
|
||||
<div class="table-header__cell col-status">状态</div>
|
||||
<div class="table-header__cell col-action">操作</div>
|
||||
</div>
|
||||
<div class="records-list">
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-001</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆丝</div>
|
||||
<div class="record-row__cell col-target">张三</div>
|
||||
<div class="record-row__cell col-weight">5.2斤</div>
|
||||
<div class="record-row__cell col-time">09:45</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-002</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥕</span> 胡萝卜丝</div>
|
||||
<div class="record-row__cell col-target">李四</div>
|
||||
<div class="record-row__cell col-weight">2.8斤</div>
|
||||
<div class="record-row__cell col-time">09:58</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-003</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥒</span> 黄瓜片</div>
|
||||
<div class="record-row__cell col-target">王五</div>
|
||||
<div class="record-row__cell col-weight">4.2斤</div>
|
||||
<div class="record-row__cell col-time">10:12</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-004</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥬</span> 白菜段</div>
|
||||
<div class="record-row__cell col-target">赵六</div>
|
||||
<div class="record-row__cell col-weight">8.5斤</div>
|
||||
<div class="record-row__cell col-time">10:28</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-005</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🍅</span> 西红柿块</div>
|
||||
<div class="record-row__cell col-target">孙七</div>
|
||||
<div class="record-row__cell col-weight">3.5斤</div>
|
||||
<div class="record-row__cell col-time">10:42</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-006</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆丝</div>
|
||||
<div class="record-row__cell col-target">周八</div>
|
||||
<div class="record-row__cell col-weight">3.0斤</div>
|
||||
<div class="record-row__cell col-time">10:55</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-007</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆块</div>
|
||||
<div class="record-row__cell col-target">张三</div>
|
||||
<div class="record-row__cell col-weight">3.2斤</div>
|
||||
<div class="record-row__cell col-time">14:30</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-008</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥕</span> 胡萝卜丁</div>
|
||||
<div class="record-row__cell col-target">陈十</div>
|
||||
<div class="record-row__cell col-weight">2.5斤</div>
|
||||
<div class="record-row__cell col-time">14:45</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-009</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🥔</span> 土豆条</div>
|
||||
<div class="record-row__cell col-target">刘九</div>
|
||||
<div class="record-row__cell col-weight">2.6斤</div>
|
||||
<div class="record-row__cell col-time">14:58</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
<div class="record-row">
|
||||
<div class="record-row__cell col-batch">#P20260613-010</div>
|
||||
<div class="record-row__cell col-name"><span class="record-row__cell--icon">🍅</span> 小番茄</div>
|
||||
<div class="record-row__cell col-target">杨一</div>
|
||||
<div class="record-row__cell col-weight">1.8斤</div>
|
||||
<div class="record-row__cell col-time">15:10</div>
|
||||
<div class="record-row__cell col-seal"><span class="seal-badge">🔒 已塑封</span></div>
|
||||
<div class="record-row__cell col-operator">刘九</div>
|
||||
<div class="record-row__cell col-status"><span class="status-badge status-badge--done">已完成</span></div>
|
||||
<div class="record-row__cell col-action"><a class="action-link">查看详情</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span class="pagination__info">共 15 条,第 1/2 页</span>
|
||||
<button class="pagination__btn" disabled>‹</button>
|
||||
<button class="pagination__btn active">1</button>
|
||||
<button class="pagination__btn">2</button>
|
||||
<button class="pagination__btn">›</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,172 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>净菜配比秤 - 设备设置 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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 { height: calc(100% - 44px); padding: 24px 32px; display: flex; gap: 24px; }
|
||||
.settings-menu { flex: 0 0 200px; background: #1a2b4a; border-radius: 12px; padding: 12px; display: flex; flex-direction: column; gap: 4px; }
|
||||
.menu-item { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-radius: 8px; cursor: pointer; font-size: 13px; color: rgba(255,255,255,0.6); transition: all 0.2s; }
|
||||
.menu-item:hover { background: rgba(255,255,255,0.06); color: #fff; }
|
||||
.menu-item.active { background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
.menu-item__icon { font-size: 16px; }
|
||||
.settings-content { flex: 1; background: #1a2b4a; border-radius: 12px; padding: 24px 28px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent; }
|
||||
.settings-content::-webkit-scrollbar { width: 4px; }
|
||||
.settings-content::-webkit-scrollbar-track { background: transparent; }
|
||||
.settings-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
.settings-section { margin-bottom: 28px; }
|
||||
.settings-section:last-child { margin-bottom: 0; }
|
||||
.section-title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.08); margin-bottom: 16px; }
|
||||
.setting-item { display: flex; align-items: center; padding: 14px 0; border-bottom: 1px solid rgba(255,255,255,0.04); }
|
||||
.setting-item:last-child { border-bottom: none; }
|
||||
.setting-item__info { flex: 1; }
|
||||
.setting-item__label { font-size: 13px; color: rgba(255,255,255,0.8); margin-bottom: 2px; }
|
||||
.setting-item__desc { font-size: 11px; color: rgba(255,255,255,0.4); }
|
||||
.setting-item__control { flex: 0 0 auto; }
|
||||
.setting-input { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 13px; color: #fff; outline: none; width: 120px; text-align: right; }
|
||||
.setting-input:focus { border-color: #4a90d9; }
|
||||
.setting-select { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 8px 12px; font-size: 13px; color: #fff; outline: none; cursor: pointer; }
|
||||
.toggle { width: 44px; height: 24px; border-radius: 12px; background: rgba(255,255,255,0.15); position: relative; cursor: pointer; transition: background 0.2s; }
|
||||
.toggle.on { background: #4a90d9; }
|
||||
.toggle__knob { width: 20px; height: 20px; border-radius: 50%; background: #fff; position: absolute; top: 2px; left: 2px; transition: left 0.2s; }
|
||||
.toggle.on .toggle__knob { left: 22px; }
|
||||
.device-info { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.info-item { background: rgba(0,0,0,0.15); border-radius: 10px; padding: 14px 16px; }
|
||||
.info-item__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 4px; }
|
||||
.info-item__value { font-size: 14px; color: #fff; font-weight: 500; }
|
||||
.info-item__value--online { color: #52c41a; }
|
||||
.save-btn { display: block; width: 100%; padding: 14px; border-radius: 10px; background: #4a90d9; color: #fff; font-size: 14px; font-weight: 600; border: none; cursor: pointer; transition: all 0.2s; margin-top: 24px; }
|
||||
.save-btn:hover { background: #5ba0e9; }
|
||||
.toast { position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px); background: rgba(0,0,0,0.85); color: #fff; padding: 12px 28px; border-radius: 8px; font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden; transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none; }
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">设备设置</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<div class="settings-menu">
|
||||
<div class="menu-item active" onclick="switchSection('packing', this)"><span class="menu-item__icon">📦</span><span>打包参数</span></div>
|
||||
<div class="menu-item" onclick="switchSection('ai', this)"><span class="menu-item__icon">🤖</span><span>AI识别</span></div>
|
||||
<div class="menu-item" onclick="switchSection('scale', this)"><span class="menu-item__icon">⚖️</span><span>秤参数</span></div>
|
||||
<div class="menu-item" onclick="switchSection('device', this)"><span class="menu-item__icon">💻</span><span>设备信息</span></div>
|
||||
<div class="menu-item" onclick="switchSection('about', this)"><span class="menu-item__icon">ℹ️</span><span>关于</span></div>
|
||||
</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-section" id="section-packing">
|
||||
<div class="section-title">打包参数配置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">自动塑封</div><div class="setting-item__desc">打包完成后自动塑封</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">标签打印</div><div class="setting-item__desc">打包完成后自动打印标签</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">重量误差范围</div><div class="setting-item__desc">打包重量与目标重量的允许误差</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="0.2" min="0.1" max="1" step="0.1"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">斤</span></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">打包完成提示音</div><div class="setting-item__desc">打包完成时播放提示音</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-ai" style="display:none">
|
||||
<div class="section-title">AI 识别设置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">识别置信度阈值</div><div class="setting-item__desc">低于此值将提示人工确认</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="85" min="60" max="99"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">%</span></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">不匹配自动告警</div><div class="setting-item__desc">AI识别与任务不匹配时弹窗提示</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">连续识别模式</div><div class="setting-item__desc">放置食材后自动触发识别</div></div>
|
||||
<div class="setting-item__control"><div class="toggle on" onclick="toggleSwitch(this)"><div class="toggle__knob"></div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-scale" style="display:none">
|
||||
<div class="section-title">秤参数配置</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">重量单位</div><div class="setting-item__desc">称重显示单位</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option selected>斤</option><option>克</option><option>千克</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">小数位数</div><div class="setting-item__desc">重量显示精度</div></div>
|
||||
<div class="setting-item__control"><select class="setting-select"><option>1位</option><option selected>2位</option></select></div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__info"><div class="setting-item__label">稳定判定时间</div><div class="setting-item__desc">重量稳定后自动锁定的等待时间</div></div>
|
||||
<div class="setting-item__control"><input type="number" class="setting-input" value="3" min="1" max="10"> <span style="color: rgba(255,255,255,0.5); font-size: 12px; margin-left: 4px">秒</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-device" style="display:none">
|
||||
<div class="section-title">设备信息</div>
|
||||
<div class="device-info">
|
||||
<div class="info-item"><div class="info-item__label">设备名称</div><div class="info-item__value">净菜配比秤-01</div></div>
|
||||
<div class="info-item"><div class="info-item__label">设备编号</div><div class="info-item__value">JC PB-2026-001</div></div>
|
||||
<div class="info-item"><div class="info-item__label">固件版本</div><div class="info-item__value">v2.3.1</div></div>
|
||||
<div class="info-item"><div class="info-item__label">AI模型版本</div><div class="info-item__value">pack-ai-v2.5</div></div>
|
||||
<div class="info-item"><div class="info-item__label">设备状态</div><div class="info-item__value info-item__value--online">在线</div></div>
|
||||
<div class="info-item"><div class="info-item__label">网络状态</div><div class="info-item__value info-item__value--online">已连接</div></div>
|
||||
<div class="info-item"><div class="info-item__label">上次校准</div><div class="info-item__value">2026-06-10</div></div>
|
||||
<div class="info-item"><div class="info-item__label">累计使用</div><div class="info-item__value">128天</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-section" id="section-about" style="display:none">
|
||||
<div class="section-title">关于</div>
|
||||
<div class="device-info">
|
||||
<div class="info-item"><div class="info-item__label">系统名称</div><div class="info-item__value">蚁熊智能餐养平台</div></div>
|
||||
<div class="info-item"><div class="info-item__label">系统版本</div><div class="info-item__value">v5.8.0</div></div>
|
||||
<div class="info-item"><div class="info-item__label">设备类型</div><div class="info-item__value">净菜配比秤</div></div>
|
||||
<div class="info-item"><div class="info-item__label">制造商</div><div class="info-item__value">蚁熊智能科技有限公司</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="save-btn" onclick="saveSettings()">保存设置</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
function switchSection(section, el) {
|
||||
document.querySelectorAll('.menu-item').forEach(function(item) { item.classList.remove('active'); });
|
||||
el.classList.add('active');
|
||||
document.querySelectorAll('.settings-section').forEach(function(sec) { sec.style.display = 'none'; });
|
||||
document.getElementById('section-' + section).style.display = 'block';
|
||||
}
|
||||
function toggleSwitch(el) { el.classList.toggle('on'); }
|
||||
function saveSettings() { showToast('设置已保存', 2000, 'success'); }
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
# 净菜配比秤 CHANGELOG
|
||||
|
||||
## [1.0.0] - 2026-06-13
|
||||
|
||||
### 新增
|
||||
- 首次创建净菜配比秤模块(5个页面)
|
||||
- `00-welcome.html` — 欢迎页:人脸识别+账号密码登录+功能菜单
|
||||
- `01-packing-tasks.html` — 今日打包任务列表:任务卡片含目标员工、状态筛选、进度展示
|
||||
- `02-weighing-packing.html` — 称重打包:AI识别品类、实时称重、当前打包信息(食材/目标员工/目标重量/已打包)、打包记录(含塑封状态)
|
||||
- `03-history.html` — 打包记录:日期/食材/目标员工筛选、统计卡片、塑封状态标签、表格记录、分页
|
||||
- `04-settings.html` — 设备设置:打包参数/AI识别/秤参数/设备信息/关于 五个配置区
|
||||
@@ -3,33 +3,70 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>配比秤 - 蚁熊智能餐养平台</title>
|
||||
<title>净菜配比秤 - 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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); }
|
||||
.main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); }
|
||||
.placeholder { text-align: center; color: rgba(255,255,255,0.3); }
|
||||
.placeholder__icon { font-size: 48px; margin-bottom: 16px; }
|
||||
.placeholder__text { font-size: 14px; }
|
||||
.back-link { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; }
|
||||
.back-link:hover { color: #fff; }
|
||||
.main-content { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px; padding: 24px 40px; height: calc(100% - 44px); align-content: center; }
|
||||
.card { display: flex; align-items: center; gap: 20px; padding: 24px 28px; background: #1a2b4a; border-radius: 14px; text-decoration: none; cursor: pointer; transition: all 0.2s; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3); border-color: rgba(74,144,217,0.3); }
|
||||
.card__icon { font-size: 28px; width: 40px; text-align: center; flex-shrink: 0; }
|
||||
.card__info { flex: 1; }
|
||||
.card__title { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }
|
||||
.card__desc { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.card__arrow { font-size: 18px; color: rgba(255,255,255,0.4); flex-shrink: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">配比秤</span>
|
||||
<a class="back-link" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">净菜配比秤</span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__icon">🚧</div>
|
||||
<div class="placeholder__text">配比秤 页面待开发</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<a class="card" href="00-welcome.html">
|
||||
<span class="card__icon">👋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">欢迎页面</div>
|
||||
<div class="card__desc">人脸识别/账号密码登录、功能菜单入口</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="01-packing-tasks.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">今日打包任务</div>
|
||||
<div class="card__desc">系统分配的净菜打包任务列表</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="02-weighing-packing.html">
|
||||
<span class="card__icon">📦</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">称重打包</div>
|
||||
<div class="card__desc">净菜AI识别、称重、按员工拆单打包塑封</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="03-history.html">
|
||||
<span class="card__icon">📝</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">打包记录</div>
|
||||
<div class="card__desc">历史打包记录查看与筛选</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="04-settings.html">
|
||||
<span class="card__icon">⚙️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">设备设置</div>
|
||||
<div class="card__desc">打包参数、AI识别、秤参数配置</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>毛菜验收秤 - 登录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* ---- 主内容:登录面板居中 ---- */
|
||||
.login-main {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: calc(100% - 44px); padding: 24px 32px;
|
||||
}
|
||||
|
||||
/* 登录面板 */
|
||||
.login-panel {
|
||||
width: 480px; display: flex; flex-direction: column; gap: 20px;
|
||||
background: #1a2b4a; border-radius: 16px; padding: 32px 40px;
|
||||
}
|
||||
.login-panel__title {
|
||||
font-size: 18px; font-weight: 600; color: rgba(255,255,255,0.85); text-align: center;
|
||||
}
|
||||
.login-panel__subtitle {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; margin-top: -12px;
|
||||
}
|
||||
|
||||
/* 登录方式切换 Tab */
|
||||
.login-tabs {
|
||||
display: flex; gap: 0; border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.login-tab {
|
||||
flex: 1; padding: 12px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.5);
|
||||
cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s;
|
||||
background: none; border-top: none; border-left: none; border-right: none;
|
||||
}
|
||||
.login-tab:hover { color: rgba(255,255,255,0.8); }
|
||||
.login-tab.active {
|
||||
color: #4a90d9; border-bottom-color: #4a90d9;
|
||||
}
|
||||
|
||||
/* 人脸识别区 */
|
||||
.face-section {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 24px 0;
|
||||
}
|
||||
.face-section.hidden { display: none; }
|
||||
.face-frame {
|
||||
width: 200px; height: 200px; border-radius: 50%;
|
||||
border: 3px dashed rgba(74,144,217,0.4);
|
||||
display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px;
|
||||
background: rgba(26,43,74,0.3); cursor: pointer; transition: all 0.3s;
|
||||
}
|
||||
.face-frame:hover {
|
||||
border-color: #4a90d9; background: rgba(74,144,217,0.1);
|
||||
}
|
||||
.face-frame__icon { font-size: 44px; color: rgba(255,255,255,0.3); }
|
||||
.face-frame__text { font-size: 13px; color: rgba(255,255,255,0.4); text-align: center; }
|
||||
|
||||
@keyframes ringPulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.3); }
|
||||
50% { box-shadow: 0 0 0 12px rgba(74,144,217,0); }
|
||||
}
|
||||
.face-frame.detecting {
|
||||
border-color: #4a90d9; border-style: solid;
|
||||
animation: ringPulse 1.5s infinite;
|
||||
}
|
||||
.face-frame.success {
|
||||
border-color: #52c41a; border-style: solid; background: rgba(82,196,26,0.08);
|
||||
}
|
||||
|
||||
.face-info {
|
||||
text-align: center; min-height: 48px;
|
||||
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 10px 16px; width: 100%;
|
||||
}
|
||||
.face-info__status { font-size: 12px; color: rgba(255,255,255,0.35); display: block; }
|
||||
.face-info__name { font-size: 15px; font-weight: 700; color: #52c41a; display: none; }
|
||||
.face-info__name.show { display: block; }
|
||||
.face-info__detail { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; display: none; }
|
||||
.face-info__detail.show { display: block; }
|
||||
|
||||
/* 账号密码表单 */
|
||||
.password-section { display: flex; flex-direction: column; gap: 14px; padding: 16px 0; }
|
||||
.password-section.hidden { display: none; }
|
||||
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
||||
.form-label { font-size: 12px; color: rgba(255,255,255,0.6); }
|
||||
.form-input {
|
||||
background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px; padding: 12px 14px; font-size: 14px; color: #fff;
|
||||
outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.form-input:focus { border-color: #4a90d9; }
|
||||
.form-input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
.login-error { font-size: 12px; color: #ff4d4f; min-height: 16px; }
|
||||
.login-btn {
|
||||
background: #4a90d9; border: none; border-radius: 8px;
|
||||
padding: 12px; font-size: 14px; font-weight: 600; color: #fff;
|
||||
cursor: pointer; transition: all 0.2s; margin-top: 8px;
|
||||
}
|
||||
.login-btn:hover { background: #5ba0e9; transform: translateY(-1px); }
|
||||
.login-btn:active { transform: translateY(0); }
|
||||
.login-hint { font-size: 11px; color: rgba(255,255,255,0.3); text-align: center; }
|
||||
|
||||
/* 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;
|
||||
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; max-width: 90%;
|
||||
text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--warn { background: rgba(250,173,22,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">毛菜验收秤 - 员工登录</span>
|
||||
</div>
|
||||
|
||||
<div class="login-main">
|
||||
<div class="login-panel">
|
||||
<div class="login-panel__title">员工登录</div>
|
||||
<div class="login-panel__subtitle">请选择登录方式</div>
|
||||
|
||||
<div class="login-tabs">
|
||||
<button class="login-tab active" data-tab="face" onclick="switchTab('face')">人脸识别</button>
|
||||
<button class="login-tab" data-tab="account" onclick="switchTab('account')">账号密码</button>
|
||||
</div>
|
||||
|
||||
<!-- 人脸识别区 -->
|
||||
<div class="face-section" id="faceSection">
|
||||
<div class="face-frame" id="faceFrame" onclick="startFaceDetect()">
|
||||
<div class="face-frame__icon" id="faceIcon">👤</div>
|
||||
<div class="face-frame__text" id="faceText">点击激活<br>人脸识别</div>
|
||||
</div>
|
||||
<div class="face-info">
|
||||
<span class="face-info__status" id="faceStatus">识别成功后即可进入功能菜单</span>
|
||||
<div class="face-info__name" id="faceName"></div>
|
||||
<div class="face-info__detail" id="faceDetail"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 账号密码表单 -->
|
||||
<div class="password-section hidden" id="passwordSection">
|
||||
<div class="form-group">
|
||||
<label class="form-label">员工工号</label>
|
||||
<input type="text" class="form-input" id="empId" placeholder="请输入工号、账号名或手机号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">登录密码</label>
|
||||
<input type="password" class="form-input" id="empPassword" placeholder="请输入登录密码">
|
||||
</div>
|
||||
<div class="login-error" id="loginError"></div>
|
||||
<button class="login-btn" onclick="passwordLogin()">登 录</button>
|
||||
<div class="login-hint">演示账号:张三 / 123456</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var loginState = 'idle';
|
||||
var currentUser = null;
|
||||
var loginTab = 'face';
|
||||
|
||||
var mockUsers = [
|
||||
{ name: '张三', id: 'EMP-20260001', dept: '仓储管理部', role: '仓库管理员', account: 'zhangsan', phone: '13800000001', password: '123456' },
|
||||
{ name: '李四', id: 'EMP-20260015', dept: '食堂运营部', role: '食堂主管', account: 'lisi', phone: '13800000002', password: '123456' },
|
||||
{ name: '王五', id: 'EMP-20260023', dept: '仓储管理部', role: '质检员', account: 'wangwu', phone: '13800000003', password: '123456' }
|
||||
];
|
||||
|
||||
function switchTab(tab) {
|
||||
loginTab = tab;
|
||||
document.querySelectorAll('.login-tab').forEach(function(el) {
|
||||
el.classList.toggle('active', el.dataset.tab === tab);
|
||||
});
|
||||
document.getElementById('faceSection').classList.toggle('hidden', tab !== 'face');
|
||||
document.getElementById('passwordSection').classList.toggle('hidden', tab !== 'account');
|
||||
document.getElementById('loginError').textContent = '';
|
||||
}
|
||||
|
||||
function startFaceDetect() {
|
||||
if (loginState !== 'idle') return;
|
||||
loginState = 'detecting';
|
||||
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.add('detecting');
|
||||
icon.innerHTML = '🔍';
|
||||
text.textContent = '识别中...';
|
||||
showToast('正在识别人脸...', 1500);
|
||||
|
||||
setTimeout(function() {
|
||||
var user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
|
||||
loginSuccess(user);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function passwordLogin() {
|
||||
var empId = document.getElementById('empId').value.trim();
|
||||
var password = document.getElementById('empPassword').value.trim();
|
||||
var errorEl = document.getElementById('loginError');
|
||||
|
||||
if (!empId || !password) { errorEl.textContent = '请输入工号和密码'; return; }
|
||||
|
||||
var user = null;
|
||||
for (var i = 0; i < mockUsers.length; i++) {
|
||||
var u = mockUsers[i];
|
||||
if ((u.account === empId || u.id === empId || u.phone === empId || u.name === empId) && u.password === password) {
|
||||
user = u; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) { errorEl.textContent = '账号或密码错误,请重试'; return; }
|
||||
loginSuccess(user);
|
||||
}
|
||||
|
||||
function loginSuccess(user) {
|
||||
currentUser = user;
|
||||
loginState = 'success';
|
||||
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.remove('detecting');
|
||||
frame.classList.add('success');
|
||||
icon.innerHTML = '✅';
|
||||
text.innerHTML = user.name;
|
||||
|
||||
document.getElementById('faceStatus').style.display = 'none';
|
||||
var nameEl = document.getElementById('faceName');
|
||||
nameEl.textContent = user.name + ' · ' + user.role;
|
||||
nameEl.classList.add('show');
|
||||
var detailEl = document.getElementById('faceDetail');
|
||||
detailEl.textContent = user.dept + ' · ' + user.id;
|
||||
detailEl.classList.add('show');
|
||||
|
||||
showToast('欢迎,' + user.name + ',正在进入功能菜单...', 1500);
|
||||
setTimeout(function() {
|
||||
window.location.href = '00-welcome.html?user=' + encodeURIComponent(user.name) + '&uid=' + user.id;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'warn') el.classList.add('toast--warn');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>毛菜验收秤 - 功能菜单 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.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); }
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* ---- 主内容:菜单按钮列居中 ---- */
|
||||
.menu-main {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: calc(100% - 44px); padding: 24px 32px;
|
||||
}
|
||||
|
||||
.menu-panel {
|
||||
width: 560px; display: flex; flex-direction: column; gap: 14px;
|
||||
}
|
||||
.menu-panel__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
|
||||
}
|
||||
.menu-panel__title {
|
||||
font-size: 14px; color: rgba(255,255,255,0.4); letter-spacing: 1px;
|
||||
}
|
||||
.menu-panel__user {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 16px 20px; border-radius: 12px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; text-decoration: none; color: #fff;
|
||||
}
|
||||
.menu-btn:hover {
|
||||
transform: translateX(4px); border-color: rgba(74,144,217,0.3);
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
.menu-btn:active { transform: translateX(2px) scale(0.99); }
|
||||
.menu-btn__icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 22px; flex-shrink: 0;
|
||||
}
|
||||
.menu-btn__icon--blue { background: rgba(74,144,217,0.15); }
|
||||
.menu-btn__icon--green { background: rgba(82,196,26,0.15); }
|
||||
.menu-btn__icon--orange { background: rgba(250,173,22,0.15); }
|
||||
.menu-btn__icon--purple { background: rgba(114,46,209,0.15); }
|
||||
.menu-btn__icon--gray { background: rgba(255,255,255,0.08); }
|
||||
.menu-btn__info { flex: 1; }
|
||||
.menu-btn__title { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
|
||||
.menu-btn__desc { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.menu-btn__arrow { font-size: 16px; color: rgba(255,255,255,0.25); flex-shrink: 0; }
|
||||
|
||||
/* 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;
|
||||
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; max-width: 90%;
|
||||
text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-login.html">◀ 返回</a>
|
||||
<span class="top-bar__title">毛菜验收秤 - 功能菜单</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="menu-main">
|
||||
<div class="menu-panel">
|
||||
<div class="menu-panel__header">
|
||||
<div class="menu-panel__title">📌 功能菜单</div>
|
||||
<div class="menu-panel__user" id="menuUser"></div>
|
||||
</div>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('01-verify-inspection.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">⚖️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">验收食材</div>
|
||||
<div class="menu-btn__desc">订单选择、称重验收、不合格处理</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('03-verify-history.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--green">📝</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">验收记录</div>
|
||||
<div class="menu-btn__desc">历史验收记录查看与筛选</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('04-ingredient-sampling.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--orange">🔍</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">食材采样</div>
|
||||
<div class="menu-btn__desc">采样操作、外观品级判定</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('05-sampling-history.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--purple">📋</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">采样历史</div>
|
||||
<div class="menu-btn__desc">历史采样记录查看与筛选</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" onclick="handleMenu('02-settings.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--gray">⚙️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">设置</div>
|
||||
<div class="menu-btn__desc">偏差率阈值配置、设备状态</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var currentUser = null;
|
||||
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
(function() {
|
||||
var userName = getUrlParam('user');
|
||||
var userId = getUrlParam('uid');
|
||||
if (!userName || !userId) {
|
||||
window.location.href = '00-login.html';
|
||||
return;
|
||||
}
|
||||
currentUser = { name: userName, id: userId };
|
||||
document.getElementById('topUser').textContent = '操作员:' + userName;
|
||||
document.getElementById('menuUser').textContent = '当前用户:' + userName;
|
||||
})();
|
||||
|
||||
function handleMenu(target) {
|
||||
if (!currentUser) {
|
||||
window.location.href = '00-login.html';
|
||||
return;
|
||||
}
|
||||
window.location.href = target + '?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
window.location.href = '00-login.html';
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>毛菜验收秤 - 设置 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
.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; }
|
||||
.page-title {
|
||||
flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
|
||||
}
|
||||
|
||||
.main-area {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 24px;
|
||||
padding: 32px 40px; height: calc(100% - 44px); align-content: start;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background: #1a2b4a; border-radius: 14px; padding: 24px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.settings-card__title {
|
||||
font-size: 16px; font-weight: 600; margin-bottom: 20px;
|
||||
padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.settings-card__title .icon { font-size: 20px; }
|
||||
|
||||
.setting-row {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 0; border-bottom: 1px solid rgba(255,255,255,0.04);
|
||||
}
|
||||
.setting-row:last-child { border-bottom: none; }
|
||||
.setting-row__label { font-size: 14px; color: rgba(255,255,255,0.8); }
|
||||
.setting-row__desc { font-size: 12px; color: rgba(255,255,255,0.4); margin-top: 2px; }
|
||||
.setting-row__right { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
.threshold-input {
|
||||
width: 80px; background: #121832; border: 1px solid rgba(255,255,255,0.15);
|
||||
border-radius: 6px; padding: 8px 10px; color: #fff; font-size: 16px;
|
||||
font-weight: 600; text-align: center; outline: none;
|
||||
}
|
||||
.threshold_input:focus { border-color: #4a90d9; }
|
||||
.threshold_unit { font-size: 14px; color: rgba(255,255,255,0.5); }
|
||||
|
||||
.save-btn {
|
||||
margin-top: 16px; width: 100%; padding: 12px; background: #4a90d9; color: #fff;
|
||||
border: none; border-radius: 8px; font-size: 14px; font-weight: 600;
|
||||
cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.save-btn:hover { background: #3d7fc0; }
|
||||
|
||||
.device-status {
|
||||
display: flex; align-items: center; gap: 8px; font-size: 13px;
|
||||
}
|
||||
.device-dot {
|
||||
width: 10px; height: 10px; border-radius: 50%;
|
||||
}
|
||||
.device-dot--online { background: #52c41a; box-shadow: 0 0 6px #52c41a; }
|
||||
.device-dot--offline { background: #ff4d4f; box-shadow: 0 0 6px #ff4d4f; }
|
||||
.device-name { color: rgba(255,255,255,0.7); }
|
||||
|
||||
.back-link {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
margin-top: 24px; padding: 10px 20px; border-radius: 8px;
|
||||
background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08);
|
||||
color: rgba(255,255,255,0.6); font-size: 13px; cursor: pointer;
|
||||
text-decoration: none; transition: all 0.2s;
|
||||
}
|
||||
.back-link:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
|
||||
/* 退出登录按钮 */
|
||||
.logout-btn {
|
||||
padding: 4px 10px; font-size: 11px;
|
||||
background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2);
|
||||
border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer;
|
||||
transition: all 0.2s; font-family: inherit;
|
||||
}
|
||||
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
|
||||
|
||||
.toast {
|
||||
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
|
||||
background: rgba(82,196,26,0.9); color: #fff; padding: 10px 24px; border-radius: 8px;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s; white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀</a>
|
||||
<span class="page-title">设置</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出</button>
|
||||
</div>
|
||||
|
||||
<div class="main-area">
|
||||
|
||||
<!-- 左卡片:偏差率阈值 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card__title"><span class="icon">⚖️</span> 验收标准</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div>
|
||||
<div class="setting-row__label">偏差率阈值</div>
|
||||
<div class="setting-row__desc">重量偏差率≤阈值时自动判定为验收通过</div>
|
||||
</div>
|
||||
<div class="setting-row__right">
|
||||
<input class="threshold-input" id="thresholdInput" type="number" value="5" min="0" max="50" step="0.5">
|
||||
<span class="threshold_unit">%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="save-btn" onclick="saveThreshold()">保存设置</button>
|
||||
</div>
|
||||
|
||||
<!-- 右卡片:设备状态 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card__title"><span class="icon">💻</span> 设备状态</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-row__label">人脸识别摄像头</div>
|
||||
<div class="device-status">
|
||||
<span class="device-dot device-dot--online"></span>
|
||||
<span class="device-name">已连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-row__label">食材识别摄像头</div>
|
||||
<div class="device-status">
|
||||
<span class="device-dot device-dot--online"></span>
|
||||
<span class="device-name">已连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-row__label">称重传感器</div>
|
||||
<div class="device-status">
|
||||
<span class="device-dot device-dot--online"></span>
|
||||
<span class="device-name">已连接</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-row__label">触摸屏</div>
|
||||
<div class="device-status">
|
||||
<span class="device-dot device-dot--online"></span>
|
||||
<span class="device-name">正常</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-row__label">网络连接</div>
|
||||
<div class="device-status">
|
||||
<span class="device-dot device-dot--online"></span>
|
||||
<span class="device-name">内网已连接</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="back-link" href="00-welcome.html">◀ 返回首页</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* 退出登录 */
|
||||
function handleLogout() {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
}
|
||||
|
||||
function saveThreshold() {
|
||||
var val = parseFloat(document.getElementById('thresholdInput').value);
|
||||
if (isNaN(val) || val < 0 || val > 50) {
|
||||
showToast('请输入 0~50 之间的数值', 2000);
|
||||
return;
|
||||
}
|
||||
showToast('✅ 偏差率阈值已保存为 ' + val + '%', 2000);
|
||||
}
|
||||
|
||||
function showToast(msg, duration) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,789 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>毛菜验收秤 - 验收记录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.top-bar {
|
||||
display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.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; }
|
||||
.page-title {
|
||||
flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
|
||||
}
|
||||
|
||||
/* 触控筛选栏 */
|
||||
.touch-filter {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 10px 20px; background: #1a2b4a; margin: 12px 20px 0; border-radius: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 模式切换(日视图 / 本周) */
|
||||
.mode-seg {
|
||||
flex-shrink: 0; display: flex; height: 52px; border-radius: 10px;
|
||||
overflow: hidden; border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.mode-btn {
|
||||
min-width: 72px; padding: 0 16px; border: none;
|
||||
background: rgba(255,255,255,0.04); color: rgba(255,255,255,0.5);
|
||||
font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.mode-btn:hover { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.mode-btn.active { background: #4a90d9; color: #fff; }
|
||||
|
||||
.filter-divider {
|
||||
width: 1px; height: 36px; background: rgba(255,255,255,0.1); flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 横向日期滚动条 */
|
||||
.date-strip {
|
||||
flex: 1; display: flex; align-items: stretch; gap: 6px;
|
||||
overflow-x: auto; overflow-y: hidden;
|
||||
padding: 4px 0;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
.date-strip::-webkit-scrollbar { display: none; }
|
||||
.date-strip.disabled { opacity: 0.25; pointer-events: none; }
|
||||
|
||||
/* 日期单元格 */
|
||||
.date-cell {
|
||||
flex-shrink: 0; width: 80px; height: 68px; border-radius: 10px;
|
||||
border: 2px solid rgba(255,255,255,0.08); background: rgba(255,255,255,0.03);
|
||||
cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 3px; transition: all 0.2s;
|
||||
}
|
||||
.date-cell:hover { background: rgba(74,144,217,0.1); border-color: rgba(74,144,217,0.3); }
|
||||
.date-cell.active { background: rgba(74,144,217,0.25); border-color: #4a90d9; }
|
||||
.date-cell__weekday { font-size: 12px; color: rgba(255,255,255,0.45); }
|
||||
.date-cell__day { font-size: 20px; font-weight: 700; color: rgba(255,255,255,0.9); }
|
||||
.date-cell.active .date-cell__weekday { color: #7ab8f5; }
|
||||
.date-cell.active .date-cell__day { color: #fff; }
|
||||
|
||||
/* 今天快速按钮 */
|
||||
.today-btn {
|
||||
flex-shrink: 0; height: 68px; padding: 0 16px; border-radius: 10px;
|
||||
border: 2px solid rgba(74,144,217,0.4); background: rgba(74,144,217,0.1);
|
||||
color: #7ab8f5; font-size: 15px; font-weight: 600; cursor: pointer; transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.today-btn:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
|
||||
.today-btn.active { background: #4a90d9; color: #fff; border-color: #4a90d9; }
|
||||
.today-btn.disabled { opacity: 0.25; pointer-events: none; }
|
||||
|
||||
/* 日历按钮 */
|
||||
.calendar-btn {
|
||||
flex-shrink: 0; width: 68px; height: 68px; border-radius: 10px;
|
||||
border: 2px solid rgba(255,255,255,0.12); background: rgba(255,255,255,0.04);
|
||||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.calendar-btn:hover { background: rgba(255,255,255,0.08); border-color: rgba(255,255,255,0.25); }
|
||||
.calendar-btn svg { width: 28px; height: 28px; }
|
||||
.calendar-btn.disabled { opacity: 0.25; pointer-events: none; }
|
||||
|
||||
/* 状态分段控件 */
|
||||
.status-seg { flex-shrink: 0; display: flex; height: 52px; border-radius: 10px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); }
|
||||
.status-btn {
|
||||
min-width: 80px; padding: 0 18px; border: none; background: rgba(255,255,255,0.04);
|
||||
color: rgba(255,255,255,0.5); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.status-btn:hover { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8); }
|
||||
.status-btn.active { background: #4a90d9; color: #fff; }
|
||||
.status-btn.active--green { background: #389e0d; color: #fff; }
|
||||
.status-btn.active--red { background: #cf1322; color: #fff; }
|
||||
|
||||
/* 搜索框 */
|
||||
.search-box {
|
||||
flex-shrink: 0; display: flex; align-items: center; gap: 6px;
|
||||
height: 40px; padding: 0 12px; border-radius: 10px;
|
||||
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1);
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.search-box:focus-within { border-color: #4a90d9; }
|
||||
.search-box__icon { font-size: 14px; color: rgba(255,255,255,0.35); flex-shrink: 0; }
|
||||
.search-box__input {
|
||||
width: 160px; border: none; outline: none; background: transparent;
|
||||
font-size: 13px; color: rgba(255,255,255,0.85);
|
||||
}
|
||||
.search-box__input::placeholder { color: rgba(255,255,255,0.25); }
|
||||
.search-box__clear {
|
||||
flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%;
|
||||
background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.5);
|
||||
font-size: 12px; cursor: pointer; display: none;
|
||||
align-items: center; justify-content: center; border: none;
|
||||
}
|
||||
.search-box__clear.show { display: flex; }
|
||||
.search-box__clear:hover { background: rgba(255,255,255,0.18); color: #fff; }
|
||||
|
||||
/* 维度切换 */
|
||||
.view-toggle {
|
||||
flex-shrink: 0; display: flex; height: 34px; border-radius: 8px; overflow: hidden;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.view-toggle__btn {
|
||||
padding: 0 14px; font-size: 12px; border: none; cursor: pointer;
|
||||
background: transparent; color: rgba(255,255,255,0.45); transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.view-toggle__btn.active {
|
||||
background: rgba(74,144,217,0.2); color: #4a90d9; font-weight: 600;
|
||||
}
|
||||
.view-toggle__btn:hover:not(.active) { color: rgba(255,255,255,0.7); }
|
||||
|
||||
/* 统计行 */
|
||||
.stats-row { display: flex; gap: 12px; padding: 12px 20px; flex-shrink: 0; }
|
||||
.stat-chip {
|
||||
flex: 1; background: #1a2b4a; border-radius: 8px; padding: 12px 16px;
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
}
|
||||
.stat-chip__icon {
|
||||
width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center;
|
||||
justify-content: center; font-size: 18px;
|
||||
}
|
||||
.stat-chip__icon--blue { background: rgba(74,144,217,0.2); }
|
||||
.stat-chip__icon--green { background: rgba(82,196,26,0.2); }
|
||||
.stat-chip__icon--red { background: rgba(255,77,79,0.2); }
|
||||
.stat-chip__icon--orange { background: rgba(250,173,22,0.2); }
|
||||
.stat-chip__value { font-size: 20px; font-weight: 700; }
|
||||
.stat-chip__label { font-size: 11px; color: rgba(255,255,255,0.4); }
|
||||
|
||||
/* 表格 */
|
||||
.table-wrap {
|
||||
margin: 12px 20px; background: #1a2b4a; border-radius: 10px; overflow: hidden;
|
||||
flex: 1; min-height: 0; display: flex; flex-direction: column;
|
||||
}
|
||||
.table-scroll { flex: 1; overflow-y: auto; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
thead th {
|
||||
background: rgba(255,255,255,0.04); padding: 12px 10px; text-align: left;
|
||||
color: rgba(255,255,255,0.5); font-weight: 500; border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||
white-space: nowrap; position: sticky; top: 0; background: #1a2b4a; z-index: 1;
|
||||
}
|
||||
tbody td {
|
||||
padding: 12px 10px; border-bottom: 1px solid rgba(255,255,255,0.04);
|
||||
color: rgba(255,255,255,0.8);
|
||||
}
|
||||
tbody tr:hover { background: rgba(74,144,217,0.08); }
|
||||
tbody tr:last-child td { border-bottom: none; }
|
||||
|
||||
/* 日期列标签(本周模式) */
|
||||
.date-tag {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 3px 8px; border-radius: 8px; font-size: 11px;
|
||||
background: rgba(74,144,217,0.12); color: #7ab8f5; white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 10px; font-size: 12px;
|
||||
}
|
||||
.tag--green { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.tag--red { background: rgba(255,77,79,0.15); color: #ff4d4f; }
|
||||
.tag--blue { background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
|
||||
/* 退出登录按钮 */
|
||||
.logout-btn {
|
||||
padding: 4px 10px; font-size: 11px;
|
||||
background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2);
|
||||
border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer;
|
||||
transition: all 0.2s; font-family: inherit;
|
||||
}
|
||||
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
|
||||
.tag--orange { background: rgba(250,173,22,0.15); color: #faad14; }
|
||||
|
||||
.detail-link {
|
||||
color: #4a90d9; cursor: pointer; text-decoration: none; font-size: 13px;
|
||||
padding: 6px 12px; border-radius: 6px; transition: all 0.2s;
|
||||
}
|
||||
.detail-link:hover { background: rgba(74,144,217,0.15); color: #6db3f8; }
|
||||
|
||||
/* 底栏 */
|
||||
.table-footer {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
padding: 10px 20px; font-size: 12px; color: rgba(255,255,255,0.35);
|
||||
border-top: 1px solid rgba(255,255,255,0.04); flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: none; flex: 1; align-items: center; justify-content: center;
|
||||
flex-direction: column; gap: 8px; color: rgba(255,255,255,0.3); font-size: 14px;
|
||||
}
|
||||
.empty-state.show { display: flex; }
|
||||
.empty-state__icon { font-size: 40px; opacity: 0.4; }
|
||||
|
||||
/* 右侧抽屉 */
|
||||
.drawer-mask {
|
||||
display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
|
||||
}
|
||||
.drawer-mask.show { display: block; }
|
||||
.drawer {
|
||||
position: fixed; top: 0; right: -520px; bottom: 0; width: 520px; background: #1a2b4a;
|
||||
z-index: 1001; transition: right 0.3s ease; box-shadow: -4px 0 20px rgba(0,0,0,0.3);
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.drawer.show { right: 0; }
|
||||
.drawer__header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 16px 24px; border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.drawer__title { font-size: 16px; font-weight: 600; }
|
||||
.drawer__close {
|
||||
width: 40px; height: 40px; font-size: 22px; cursor: pointer; color: rgba(255,255,255,0.5);
|
||||
background: rgba(255,255,255,0.06); border: none; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.drawer__close:hover { color: #fff; background: rgba(255,255,255,0.1); }
|
||||
.drawer__body { padding: 20px 24px; overflow-y: auto; flex: 1; }
|
||||
.drawer-section { margin-bottom: 20px; }
|
||||
.drawer-section__title {
|
||||
font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.6);
|
||||
padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.drawer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; }
|
||||
.drawer-cell__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 2px; }
|
||||
.drawer-cell__value { font-size: 14px; color: rgba(255,255,255,0.9); font-weight: 500; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="page-title">验收记录</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出</button>
|
||||
</div>
|
||||
|
||||
<!-- 触控筛选栏 -->
|
||||
<div class="touch-filter">
|
||||
<!-- 模式切换:日视图 / 本周 -->
|
||||
<div class="mode-seg">
|
||||
<button class="mode-btn active" id="modeDayBtn">日视图</button>
|
||||
<button class="mode-btn" id="modeWeekBtn">本周</button>
|
||||
</div>
|
||||
<div class="filter-divider"></div>
|
||||
<!-- 横向日期滚动条(日视图模式) -->
|
||||
<div class="date-strip" id="dateStrip"></div>
|
||||
<button class="today-btn active" id="todayBtn">今天</button>
|
||||
<label class="calendar-btn" id="calendarBtn" title="选择任意日期">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.6)" stroke-width="1.8">
|
||||
<rect x="3" y="4" width="18" height="18" rx="2"/>
|
||||
<line x1="16" y1="2" x2="16" y2="6"/>
|
||||
<line x1="8" y1="2" x2="8" y2="6"/>
|
||||
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||
<rect x="7" y="14" width="3" height="3" rx="0.5" fill="rgba(255,255,255,0.6)" stroke="none"/>
|
||||
</svg>
|
||||
<input type="date" id="calendarInput" style="position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;">
|
||||
</label>
|
||||
<div class="filter-divider"></div>
|
||||
<!-- 状态分段控件 -->
|
||||
<div class="status-seg">
|
||||
<button class="status-btn active" data-status="all">全部</button>
|
||||
<button class="status-btn" data-status="pass">通过</button>
|
||||
<button class="status-btn" data-status="fail">不通过</button>
|
||||
</div>
|
||||
<div class="filter-divider"></div>
|
||||
<!-- 搜索框 -->
|
||||
<div class="search-box">
|
||||
<span class="search-box__icon">🔍</span>
|
||||
<input class="search-box__input" id="searchInput" type="text" placeholder="搜索单号 / 食材 / 供应商" oninput="onSearchInput()">
|
||||
<button class="search-box__clear" id="searchClear" onclick="clearSearch()">×</button>
|
||||
</div>
|
||||
<div class="filter-divider"></div>
|
||||
<!-- 维度切换 -->
|
||||
<div class="view-toggle">
|
||||
<button class="view-toggle__btn active" data-view="food" onclick="setViewMode('food')">食材维度</button>
|
||||
<button class="view-toggle__btn" data-view="order" onclick="setViewMode('order')">订单维度</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计行 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-chip">
|
||||
<div class="stat-chip__icon stat-chip__icon--blue">📋</div>
|
||||
<div><div class="stat-chip__value" id="statTotal">3</div><div class="stat-chip__label" id="statTotalLabel">今日验收总数</div></div>
|
||||
</div>
|
||||
<div class="stat-chip">
|
||||
<div class="stat-chip__icon stat-chip__icon--green">✅</div>
|
||||
<div><div class="stat-chip__value" id="statPass">2</div><div class="stat-chip__label">验收通过</div></div>
|
||||
</div>
|
||||
<div class="stat-chip">
|
||||
<div class="stat-chip__icon stat-chip__icon--red">❌</div>
|
||||
<div><div class="stat-chip__value" id="statFail">1</div><div class="stat-chip__label">验收不通过</div></div>
|
||||
</div>
|
||||
<div class="stat-chip">
|
||||
<div class="stat-chip__icon stat-chip__icon--orange">📊</div>
|
||||
<div><div class="stat-chip__value" id="statRate">67%</div><div class="stat-chip__label">合格率</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="table-wrap">
|
||||
<div class="table-scroll">
|
||||
<table>
|
||||
<thead><tr id="tableHead">
|
||||
<th>订单号</th><th>食材</th><th>供应商</th>
|
||||
<th>支付方式</th><th>配送方式</th><th>时间</th><th>验收结果</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody id="tableBody"></tbody>
|
||||
</table>
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">📭</div>
|
||||
<div>暂无验收记录</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-footer" id="tableFooter">共 0 条记录</div>
|
||||
</div>
|
||||
|
||||
<!-- 详情抽屉 -->
|
||||
<div class="drawer-mask" id="drawerMask" onclick="closeDrawer()"></div>
|
||||
<div class="drawer" id="drawer">
|
||||
<div class="drawer__header">
|
||||
<span class="drawer__title" id="drawerTitle">验收详情</span>
|
||||
<button class="drawer__close" onclick="closeDrawer()">×</button>
|
||||
</div>
|
||||
<div class="drawer__body" id="drawerBody"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* 退出登录 */
|
||||
function handleLogout() {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
}
|
||||
|
||||
// 当天日期(原型基准日期)
|
||||
var TODAY = '2026-06-11';
|
||||
var WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
|
||||
// 验收记录数据
|
||||
var RECORDS = [
|
||||
{ no:'IN-20260611-001', orderId:'PO-20260612-001', time:'2026-06-11 10:30', operator:'张三', dish:'菠菜', supplier:'A区蔬菜基地', paymentMode:'pre', shippingType:'本地配送', orderW:300, actualW:286, diffRate:'4.7%', result:'pass', ngType:'', ngScope:'', reason:'' },
|
||||
{ no:'IN-20260611-002', orderId:'PO-20260612-002', time:'2026-06-11 09:15', operator:'张三', dish:'土豆', supplier:'绿源配送', paymentMode:'post', shippingType:'三方快递', orderW:500, actualW:420, diffRate:'16.0%', result:'fail', ngType:'退货退款', ngScope:'部分', reason:'发霉变色' },
|
||||
{ no:'IN-20260611-002b', orderId:'PO-20260612-002', time:'2026-06-11 09:20', operator:'张三', dish:'胡萝卜', supplier:'绿源配送', paymentMode:'post', shippingType:'三方快递', orderW:200, actualW:0, diffRate:'--', result:'pending', ngType:'', ngScope:'', reason:'' },
|
||||
{ no:'IN-20260611-003', orderId:'PO-20260612-003', time:'2026-06-11 08:45', operator:'李四', dish:'猪里脊', supplier:'鲜肉直供', paymentMode:'pre', shippingType:'本地配送', orderW:100, actualW:98, diffRate:'2.0%', result:'pass', ngType:'', ngScope:'', reason:'' },
|
||||
{ no:'IN-20260611-003b', orderId:'PO-20260612-003', time:'2026-06-11 08:50', operator:'李四', dish:'五花肉', supplier:'鲜肉直供', paymentMode:'pre', shippingType:'本地配送', orderW:200, actualW:195, diffRate:'2.5%', result:'pass', ngType:'', ngScope:'', reason:'' },
|
||||
{ no:'IN-20260610-004', orderId:'PO-20260612-004', time:'2026-06-10 14:20', operator:'王五', dish:'胡萝卜', supplier:'B区根茎基地', paymentMode:'post', shippingType:'本地配送', orderW:450, actualW:448, diffRate:'0.4%', result:'pass', ngType:'', ngScope:'', reason:'' },
|
||||
{ no:'IN-20260610-005', orderId:'', time:'2026-06-10 11:30', operator:'张三', dish:'草鱼', supplier:'海鲜配送', paymentMode:'pre', shippingType:'本地配送', orderW:80, actualW:0, diffRate:'100%', result:'fail', ngType:'换货', ngScope:'全部', reason:'死鱼不收' },
|
||||
{ no:'IN-20260610-006', orderId:'PO-20260612-005', time:'2026-06-10 10:00', operator:'李四', dish:'白菜', supplier:'A区蔬菜基地', paymentMode:'post', shippingType:'本地配送', orderW:200, actualW:195, diffRate:'2.5%', result:'pass', ngType:'', ngScope:'', reason:'' },
|
||||
{ no:'IN-20260609-007', orderId:'', time:'2026-06-09 15:10', operator:'王五', dish:'西兰花', supplier:'绿源配送', paymentMode:'pre', shippingType:'三方快递', orderW:150, actualW:120, diffRate:'20.0%', result:'fail', ngType:'仅退款', ngScope:'部分', reason:'枯萎严重' },
|
||||
{ no:'IN-20260609-008', orderId:'PO-20260612-000', time:'2026-06-09 09:30', operator:'张三', dish:'土豆', supplier:'B区根茎基地', paymentMode:'pre', shippingType:'本地配送', orderW:600, actualW:590, diffRate:'1.7%', result:'pass', ngType:'', ngScope:'', reason:'' }
|
||||
];
|
||||
|
||||
var selectedDate = TODAY;
|
||||
var selectedStatus = 'all';
|
||||
var isWeeklyMode = false;
|
||||
var searchKeyword = '';
|
||||
var viewMode = 'food'; // 'food' = 食材维度, 'order' = 订单维度
|
||||
|
||||
// 获取本周一日期
|
||||
function getMonday() {
|
||||
var today = new Date(TODAY + 'T00:00:00');
|
||||
var day = today.getDay();
|
||||
var diff = today.getDate() - day + (day === 0 ? -6 : 1);
|
||||
var monday = new Date(today);
|
||||
monday.setDate(diff);
|
||||
return monday;
|
||||
}
|
||||
|
||||
// 生成日期字符串
|
||||
function dateStr(d) {
|
||||
return d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
}
|
||||
|
||||
// 生成日期条(最近7天)
|
||||
function generateDateStrip() {
|
||||
var strip = document.getElementById('dateStrip');
|
||||
var today = new Date(TODAY + 'T00:00:00');
|
||||
var html = '';
|
||||
for (var i = 6; i >= 0; i--) {
|
||||
var d = new Date(today);
|
||||
d.setDate(d.getDate() - i);
|
||||
var ds = dateStr(d);
|
||||
var dayNum = d.getDate();
|
||||
var weekday = WEEKDAYS[d.getDay()];
|
||||
var label = (i === 0) ? '今天' : (i === 1 ? '昨天' : weekday);
|
||||
var isActive = (ds === selectedDate) ? ' active' : '';
|
||||
html += '<button class="date-cell' + isActive + '" data-date="' + ds + '">'
|
||||
+ '<span class="date-cell__weekday">' + label + '</span>'
|
||||
+ '<span class="date-cell__day">' + dayNum + '</span>'
|
||||
+ '</button>';
|
||||
}
|
||||
strip.innerHTML = html;
|
||||
strip.querySelectorAll('.date-cell').forEach(function(cell) {
|
||||
cell.addEventListener('click', function() {
|
||||
selectDate(cell.getAttribute('data-date'));
|
||||
});
|
||||
});
|
||||
strip.scrollLeft = strip.scrollWidth;
|
||||
}
|
||||
|
||||
// 选中日期
|
||||
function selectDate(ds) {
|
||||
if (isWeeklyMode) return;
|
||||
selectedDate = ds;
|
||||
|
||||
document.querySelectorAll('.date-cell').forEach(function(c) {
|
||||
c.classList.toggle('active', c.getAttribute('data-date') === ds);
|
||||
});
|
||||
document.getElementById('todayBtn').classList.toggle('active', ds === TODAY);
|
||||
|
||||
// 超出范围时扩展日期条
|
||||
var inRange = Array.from(document.querySelectorAll('.date-cell'))
|
||||
.some(function(c) { return c.getAttribute('data-date') === ds; });
|
||||
if (!inRange) appendDateToStrip(ds);
|
||||
|
||||
var activeCell = document.querySelector('.date-cell[data-date="' + ds + '"]');
|
||||
if (activeCell) activeCell.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
|
||||
|
||||
updateStats();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
// 扩展日期条(左侧追加)
|
||||
function appendDateToStrip(ds) {
|
||||
var strip = document.getElementById('dateStrip');
|
||||
var d = new Date(ds + 'T00:00:00');
|
||||
var cell = document.createElement('button');
|
||||
cell.className = 'date-cell active';
|
||||
cell.setAttribute('data-date', ds);
|
||||
cell.innerHTML = '<span class="date-cell__weekday">' + WEEKDAYS[d.getDay()] + '</span>'
|
||||
+ '<span class="date-cell__day">' + d.getDate() + '</span>';
|
||||
cell.addEventListener('click', function() { selectDate(ds); });
|
||||
strip.insertBefore(cell, strip.firstChild);
|
||||
}
|
||||
|
||||
// 切换模式:日视图 / 本周
|
||||
function setMode(weekly) {
|
||||
isWeeklyMode = weekly;
|
||||
|
||||
// 更新模式按钮
|
||||
document.getElementById('modeDayBtn').classList.toggle('active', !weekly);
|
||||
document.getElementById('modeWeekBtn').classList.toggle('active', weekly);
|
||||
|
||||
// 日期相关控件启用/禁用
|
||||
var disableClass = weekly ? 'add' : 'remove';
|
||||
document.getElementById('dateStrip').classList[disableClass]('disabled');
|
||||
document.getElementById('todayBtn').classList[disableClass]('disabled');
|
||||
document.getElementById('calendarBtn').classList[disableClass]('disabled');
|
||||
|
||||
updateStats();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
// 更新统计行
|
||||
function updateStats() {
|
||||
var records;
|
||||
var totalLabel;
|
||||
|
||||
if (searchKeyword) {
|
||||
// 搜索模式:跨全部记录
|
||||
records = RECORDS;
|
||||
totalLabel = '搜索结果';
|
||||
} else if (isWeeklyMode) {
|
||||
// 本周汇总
|
||||
var monday = getMonday();
|
||||
var mondayStr = dateStr(monday);
|
||||
records = RECORDS.filter(function(r) { return r.time.substring(0, 10) >= mondayStr; });
|
||||
totalLabel = '本周验收总数';
|
||||
} else {
|
||||
// 按天
|
||||
records = RECORDS.filter(function(r) { return r.time.substring(0, 10) === selectedDate; });
|
||||
totalLabel = (selectedDate === TODAY) ? '今日验收总数' : (selectedDate.substring(5).replace('-', '/') + ' 验收总数');
|
||||
}
|
||||
|
||||
var total = records.length;
|
||||
var pass = records.filter(function(r) { return r.result === 'pass'; }).length;
|
||||
var fail = records.filter(function(r) { return r.result === 'fail'; }).length;
|
||||
var rate = total > 0 ? Math.round(pass / total * 100) : 0;
|
||||
|
||||
document.getElementById('statTotal').textContent = total;
|
||||
document.getElementById('statTotalLabel').textContent = totalLabel;
|
||||
document.getElementById('statPass').textContent = pass;
|
||||
document.getElementById('statFail').textContent = fail;
|
||||
document.getElementById('statRate').textContent = rate + '%';
|
||||
}
|
||||
|
||||
// 打开详情抽屉
|
||||
function openDrawer(no) {
|
||||
var r = null;
|
||||
for (var i = 0; i < RECORDS.length; i++) { if (RECORDS[i].no === no) { r = RECORDS[i]; break; } }
|
||||
if (!r) return;
|
||||
|
||||
var html = '';
|
||||
html += '<div class="drawer-section"><div class="drawer-section__title">入库信息</div><div class="drawer-grid">'
|
||||
+ '<div><div class="drawer-cell__label">订单号</div><div class="drawer-cell__value">' + (r.orderId || '--') + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">验收时间</div><div class="drawer-cell__value">' + r.time + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">操作人</div><div class="drawer-cell__value">' + r.operator + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">支付方式</div><div class="drawer-cell__value">' + (r.paymentMode === 'pre' ? '前置付款' : '后置付款') + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">配送方式</div><div class="drawer-cell__value">' + (r.shippingType || '--') + '</div></div>'
|
||||
+ '</div></div>';
|
||||
|
||||
html += '<div class="drawer-section"><div class="drawer-section__title">食材信息</div><div class="drawer-grid">'
|
||||
+ '<div><div class="drawer-cell__label">食材名称</div><div class="drawer-cell__value">' + r.dish + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">供应商</div><div class="drawer-cell__value">' + r.supplier + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">订单重量</div><div class="drawer-cell__value">' + r.orderW + ' kg</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">实际重量</div><div class="drawer-cell__value">' + r.actualW + ' kg</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">偏差率</div><div class="drawer-cell__value" style="color:' + (r.result === 'pass' ? '#52c41a' : (r.result === 'pending' ? '#4a90d9' : '#ff4d4f')) + '">' + r.diffRate + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">验收结果</div><div class="drawer-cell__value">' + (r.result === 'pass' ? '通过' : (r.result === 'pending' ? '待验收' : '不通过')) + '</div></div>'
|
||||
+ '</div></div>';
|
||||
|
||||
if (r.result === 'fail') {
|
||||
html += '<div class="drawer-section"><div class="drawer-section__title">不合格处置</div><div class="drawer-grid">'
|
||||
+ '<div><div class="drawer-cell__label">处理方式</div><div class="drawer-cell__value">' + r.ngType + '</div></div>'
|
||||
+ '<div><div class="drawer-cell__label">处理范围</div><div class="drawer-cell__value">' + r.ngScope + '</div></div>'
|
||||
+ '<div style="grid-column:1/-1"><div class="drawer-cell__label">不合格原因</div><div class="drawer-cell__value">' + r.reason + '</div></div>'
|
||||
+ '</div></div>';
|
||||
}
|
||||
|
||||
document.getElementById('drawerTitle').textContent = '验收详情 - ' + r.no;
|
||||
document.getElementById('drawerBody').innerHTML = html;
|
||||
document.getElementById('drawerMask').classList.add('show');
|
||||
document.getElementById('drawer').classList.add('show');
|
||||
}
|
||||
|
||||
function closeDrawer() {
|
||||
document.getElementById('drawerMask').classList.remove('show');
|
||||
document.getElementById('drawer').classList.remove('show');
|
||||
}
|
||||
|
||||
// 搜索输入处理
|
||||
function onSearchInput() {
|
||||
var val = document.getElementById('searchInput').value.trim();
|
||||
searchKeyword = val;
|
||||
document.getElementById('searchClear').classList.toggle('show', val.length > 0);
|
||||
updateStats();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
searchKeyword = '';
|
||||
document.getElementById('searchClear').classList.remove('show');
|
||||
updateStats();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
/* 切换维度(食材/订单) */
|
||||
function setViewMode(mode) {
|
||||
viewMode = mode;
|
||||
document.querySelectorAll('.view-toggle__btn').forEach(function(btn) {
|
||||
btn.classList.toggle('active', btn.getAttribute('data-view') === mode);
|
||||
});
|
||||
renderTable();
|
||||
}
|
||||
|
||||
// 过滤记录
|
||||
function getFilteredRecords() {
|
||||
var monday = getMonday();
|
||||
var mondayStr = dateStr(monday);
|
||||
|
||||
return RECORDS.filter(function(r) {
|
||||
// 有搜索词时跳过日期过滤,跨全部记录查找
|
||||
if (!searchKeyword) {
|
||||
// 日期范围过滤
|
||||
if (isWeeklyMode) {
|
||||
if (r.time.substring(0, 10) < mondayStr) return false;
|
||||
} else {
|
||||
if (r.time.substring(0, 10) !== selectedDate) return false;
|
||||
}
|
||||
}
|
||||
// 状态过滤
|
||||
if (selectedStatus !== 'all' && r.result !== selectedStatus) return false;
|
||||
// 关键词搜索(匹配单号、食材、供应商)
|
||||
if (searchKeyword) {
|
||||
var kw = searchKeyword;
|
||||
if (r.no.indexOf(kw) === -1 && (r.orderId || '').indexOf(kw) === -1 && r.dish.indexOf(kw) === -1 && r.supplier.indexOf(kw) === -1) return false;
|
||||
}
|
||||
return true;
|
||||
}).sort(function(a, b) {
|
||||
// 按时间倒序(最新的在前)
|
||||
return b.time.localeCompare(a.time);
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染表格
|
||||
function renderTable() {
|
||||
var records = getFilteredRecords();
|
||||
var emptyEl = document.getElementById('emptyState');
|
||||
var tbody = document.getElementById('tableBody');
|
||||
var thead = document.getElementById('tableHead');
|
||||
|
||||
var dateCol = isWeeklyMode ? '<th>日期</th>' : '';
|
||||
|
||||
if (viewMode === 'order') {
|
||||
// 订单维度表头
|
||||
thead.innerHTML = dateCol + '<th>订单号</th><th>食材数量</th><th>供应商</th>'
|
||||
+ '<th>支付方式</th><th>配送方式</th><th>时间</th><th>验收状态</th><th>操作</th>';
|
||||
} else {
|
||||
// 食材维度表头
|
||||
thead.innerHTML = dateCol + '<th>订单号</th><th>食材</th><th>供应商</th>'
|
||||
+ '<th>支付方式</th><th>配送方式</th><th>时间</th><th>验收结果</th><th>操作</th>';
|
||||
}
|
||||
|
||||
if (records.length === 0) {
|
||||
tbody.innerHTML = '';
|
||||
emptyEl.classList.add('show');
|
||||
document.getElementById('tableFooter').textContent = '共 0 条记录';
|
||||
return;
|
||||
}
|
||||
|
||||
emptyEl.classList.remove('show');
|
||||
|
||||
var html;
|
||||
|
||||
if (viewMode === 'order') {
|
||||
// 订单维度:按 orderId 分组
|
||||
var orderGroups = {};
|
||||
var orderKeys = [];
|
||||
records.forEach(function(r) {
|
||||
var key = r.orderId || ('__no_order_' + r.no);
|
||||
if (!orderGroups[key]) {
|
||||
orderGroups[key] = [];
|
||||
orderKeys.push(key);
|
||||
}
|
||||
orderGroups[key].push(r);
|
||||
});
|
||||
|
||||
html = orderKeys.map(function(key) {
|
||||
var group = orderGroups[key];
|
||||
var first = group[0];
|
||||
var foodCount = group.length;
|
||||
var allPass = group.every(function(r) { return r.result === 'pass'; });
|
||||
var statusTag = allPass
|
||||
? '<span class="tag tag--green">验收完成</span>'
|
||||
: '<span class="tag tag--orange">待验收</span>';
|
||||
var payTag = first.paymentMode === 'pre'
|
||||
? '<span class="tag tag--green">前置</span>'
|
||||
: '<span class="tag tag--orange">后置</span>';
|
||||
var orderIdDisplay = first.orderId || '<span style="color:rgba(255,255,255,0.25)">--</span>';
|
||||
var timeShort = first.time.substring(11);
|
||||
|
||||
var cols = '';
|
||||
if (isWeeklyMode) {
|
||||
var rd = new Date(first.time.substring(0, 10) + 'T00:00:00');
|
||||
var weekday = WEEKDAYS[rd.getDay()];
|
||||
var mmdd = first.time.substring(5, 10).replace('-', '/');
|
||||
cols += '<td><span class="date-tag">' + weekday + ' ' + mmdd + '</span></td>';
|
||||
}
|
||||
|
||||
cols += '<td>' + orderIdDisplay + '</td>'
|
||||
+ '<td style="text-align:center;">' + foodCount + ' 种</td>'
|
||||
+ '<td>' + first.supplier + '</td>'
|
||||
+ '<td>' + payTag + '</td>'
|
||||
+ '<td>' + (first.shippingType || '--') + '</td>'
|
||||
+ '<td>' + timeShort + '</td>'
|
||||
+ '<td>' + statusTag + '</td>'
|
||||
+ '<td><span class="detail-link" onclick="openDrawer(\'' + first.no + '\')">详情</span></td>';
|
||||
|
||||
return '<tr>' + cols + '</tr>';
|
||||
}).join('');
|
||||
} else {
|
||||
// 食材维度
|
||||
html = records.map(function(r) {
|
||||
var resultTag;
|
||||
if (r.result === 'pass') resultTag = '<span class="tag tag--green">通过</span>';
|
||||
else if (r.result === 'pending') resultTag = '<span class="tag tag--blue">待验收</span>';
|
||||
else resultTag = '<span class="tag tag--red">不通过</span>';
|
||||
var payTag = r.paymentMode === 'pre'
|
||||
? '<span class="tag tag--green">前置</span>'
|
||||
: '<span class="tag tag--orange">后置</span>';
|
||||
var orderIdDisplay = r.orderId || '<span style="color:rgba(255,255,255,0.25)">--</span>';
|
||||
var timeShort = r.time.substring(11);
|
||||
|
||||
var cols = '';
|
||||
if (isWeeklyMode) {
|
||||
var rd = new Date(r.time.substring(0, 10) + 'T00:00:00');
|
||||
var weekday = WEEKDAYS[rd.getDay()];
|
||||
var mmdd = r.time.substring(5, 10).replace('-', '/');
|
||||
cols += '<td><span class="date-tag">' + weekday + ' ' + mmdd + '</span></td>';
|
||||
}
|
||||
|
||||
cols += '<td>' + orderIdDisplay + '</td>'
|
||||
+ '<td>' + r.dish + '</td>'
|
||||
+ '<td>' + r.supplier + '</td>'
|
||||
+ '<td>' + payTag + '</td>'
|
||||
+ '<td>' + (r.shippingType || '--') + '</td>'
|
||||
+ '<td>' + timeShort + '</td>'
|
||||
+ '<td>' + resultTag + '</td>'
|
||||
+ '<td><span class="detail-link" onclick="openDrawer(\'' + r.no + '\')">详情</span></td>';
|
||||
|
||||
return '<tr>' + cols + '</tr>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
tbody.innerHTML = html;
|
||||
|
||||
// 底栏:本周模式显示周范围
|
||||
var footerText;
|
||||
if (isWeeklyMode) {
|
||||
var monday = getMonday();
|
||||
var mondayStr = dateStr(monday).substring(5).replace('-', '/');
|
||||
var todayStr = TODAY.substring(5).replace('-', '/');
|
||||
footerText = '本周 ' + mondayStr + ' - ' + todayStr + ' 共 ' + records.length + ' 条记录';
|
||||
} else {
|
||||
footerText = '共 ' + records.length + ' 条记录';
|
||||
}
|
||||
document.getElementById('tableFooter').textContent = footerText;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
// 生成日期条
|
||||
generateDateStrip();
|
||||
|
||||
// 模式切换
|
||||
document.getElementById('modeDayBtn').addEventListener('click', function() { setMode(false); });
|
||||
document.getElementById('modeWeekBtn').addEventListener('click', function() { setMode(true); });
|
||||
|
||||
// 今天按钮
|
||||
document.getElementById('todayBtn').addEventListener('click', function() {
|
||||
selectDate(TODAY);
|
||||
});
|
||||
|
||||
// 日历按钮
|
||||
document.getElementById('calendarInput').addEventListener('change', function(e) {
|
||||
if (e.target.value) selectDate(e.target.value);
|
||||
});
|
||||
|
||||
// 状态分段控件
|
||||
document.querySelectorAll('.status-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.status-btn').forEach(function(b) {
|
||||
b.classList.remove('active', 'active--green', 'active--red');
|
||||
});
|
||||
var status = btn.getAttribute('data-status');
|
||||
if (status === 'pass') btn.classList.add('active--green');
|
||||
else if (status === 'fail') btn.classList.add('active--red');
|
||||
else btn.classList.add('active');
|
||||
selectedStatus = status;
|
||||
renderTable();
|
||||
});
|
||||
});
|
||||
|
||||
// 首次渲染
|
||||
updateStats();
|
||||
renderTable();
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,684 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>毛菜验收秤 - 食材采样 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.top-bar { display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px; }
|
||||
.top-bar__title { flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); }
|
||||
.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; }
|
||||
/* 退出登录按钮 */
|
||||
.logout-btn {
|
||||
padding: 4px 10px; font-size: 11px;
|
||||
background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2);
|
||||
border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer;
|
||||
transition: all 0.2s; font-family: inherit;
|
||||
}
|
||||
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
|
||||
|
||||
.main-content { display: flex; gap: 16px; padding: 16px 20px; height: calc(100% - 44px); }
|
||||
|
||||
/* ========== 左侧:食材选择区 ========== */
|
||||
.food-select-area {
|
||||
width: 400px; flex-shrink: 0; background: #1a2b4a; border-radius: 12px;
|
||||
padding: 20px; display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.area-title { font-size: 16px; font-weight: 600; margin-bottom: 16px; }
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar { margin-bottom: 10px; }
|
||||
.search-input {
|
||||
width: 100%; padding: 8px 12px; background: #121832;
|
||||
border: 1px solid rgba(255,255,255,0.1); border-radius: 6px;
|
||||
color: #fff; font-size: 13px; outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.search-input:focus { border-color: #4a90d9; }
|
||||
.search-input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* 类目Tab */
|
||||
.filter-tabs { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||
.filter-tab {
|
||||
padding: 5px 12px; font-size: 12px; border-radius: 4px;
|
||||
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.55);
|
||||
cursor: pointer; transition: all 0.2s; border: 1px solid transparent;
|
||||
}
|
||||
.filter-tab.active { background: #4a90d9; color: #fff; }
|
||||
.filter-tab:hover:not(.active) { border-color: rgba(255,255,255,0.15); color: rgba(255,255,255,0.75); }
|
||||
|
||||
/* 食材列表 */
|
||||
.food-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
|
||||
.food-list::-webkit-scrollbar { width: 4px; }
|
||||
.food-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }
|
||||
|
||||
.food-item {
|
||||
display: flex; align-items: center; padding: 12px;
|
||||
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 10px; gap: 12px; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.food-item:hover { background: rgba(74,144,217,0.08); border-color: rgba(74,144,217,0.25); }
|
||||
.food-item.selected { background: rgba(74,144,217,0.12); border-color: #4a90d9; }
|
||||
.food-item__thumb {
|
||||
width: 52px; height: 52px; border-radius: 8px; overflow: hidden; flex-shrink: 0;
|
||||
}
|
||||
.food-item__thumb svg { width: 100%; height: 100%; }
|
||||
.food-item__info { flex: 1; min-width: 0; }
|
||||
.food-item__name { font-size: 14px; font-weight: 600; margin-bottom: 3px; }
|
||||
.food-item__detail { font-size: 12px; color: rgba(255,255,255,0.45); }
|
||||
.food-item__count {
|
||||
font-size: 11px; color: #52c41a; background: rgba(82,196,26,0.1);
|
||||
padding: 2px 8px; border-radius: 4px; flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
padding: 40px 0; color: rgba(255,255,255,0.3);
|
||||
}
|
||||
.empty-state__icon { font-size: 40px; margin-bottom: 12px; }
|
||||
.empty-state__text { font-size: 13px; }
|
||||
|
||||
/* ========== 右侧:拍照采集区 ========== */
|
||||
.capture-area {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 20px;
|
||||
display: flex; flex-direction: column; gap: 16px; overflow-y: auto;
|
||||
}
|
||||
.capture-area::-webkit-scrollbar { width: 4px; }
|
||||
.capture-area::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }
|
||||
|
||||
/* 选中食材信息 */
|
||||
.selected-food-info {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 14px 16px; background: rgba(74,144,217,0.08);
|
||||
border: 1px solid rgba(74,144,217,0.2); border-radius: 10px;
|
||||
}
|
||||
.selected-food-info__thumb {
|
||||
width: 44px; height: 44px; border-radius: 8px; overflow: hidden; flex-shrink: 0;
|
||||
}
|
||||
.selected-food-info__thumb svg { width: 100%; height: 100%; }
|
||||
.selected-food-info__name { font-size: 15px; font-weight: 600; }
|
||||
.selected-food-info__desc { font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
|
||||
/* 拍照区域 */
|
||||
.camera-frame-wrap { display: flex; flex-direction: column; align-items: center; gap: 12px; }
|
||||
.camera-frame {
|
||||
width: 100%; height: 280px; background: #0D1117; border-radius: 12px;
|
||||
position: relative; border: 1px solid rgba(255,255,255,0.08);
|
||||
display: flex; align-items: center; justify-content: center; overflow: hidden;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
.camera-frame.active { border-color: #4a90d9; }
|
||||
.camera-frame__text { color: rgba(255,255,255,0.25); font-size: 14px; z-index: 1; }
|
||||
.camera-frame::before, .camera-frame::after {
|
||||
content: ""; position: absolute; pointer-events: none; z-index: 2;
|
||||
}
|
||||
.camera-frame::before {
|
||||
top: 0; bottom: 0; left: 50%; width: 1px;
|
||||
border-left: 1px dashed rgba(74,144,217,0.25);
|
||||
}
|
||||
.camera-frame::after {
|
||||
left: 0; right: 0; top: 50%; height: 1px;
|
||||
border-top: 1px dashed rgba(74,144,217,0.25);
|
||||
}
|
||||
.corner { position: absolute; width: 20px; height: 20px; z-index: 3; }
|
||||
.corner--tl { top: 12px; left: 12px; border-top: 2px solid #4a90d9; border-left: 2px solid #4a90d9; }
|
||||
.corner--tr { top: 12px; right: 12px; border-top: 2px solid #4a90d9; border-right: 2px solid #4a90d9; }
|
||||
.corner--bl { bottom: 12px; left: 12px; border-bottom: 2px solid #4a90d9; border-left: 2px solid #4a90d9; }
|
||||
.corner--br { bottom: 12px; right: 12px; border-bottom: 2px solid #4a90d9; border-right: 2px solid #4a90d9; }
|
||||
|
||||
/* 拍照闪烁效果 */
|
||||
.flash-overlay {
|
||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: #fff; opacity: 0; pointer-events: none; border-radius: 12px; z-index: 10;
|
||||
}
|
||||
.flash-overlay.active { animation: flash 0.5s ease-out; }
|
||||
@keyframes flash { 0% { opacity: 0; } 30% { opacity: 0.9; } 100% { opacity: 0; } }
|
||||
|
||||
.btn-photo {
|
||||
width: 200px; padding: 14px 8px; border: none; border-radius: 10px;
|
||||
background: #4a90d9; color: #fff; font-size: 14px; font-weight: 600;
|
||||
cursor: pointer; display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; gap: 4px; transition: all 0.2s;
|
||||
}
|
||||
.btn-photo:hover { background: #3d7fc0; transform: scale(1.03); }
|
||||
.btn-photo:active { transform: scale(0.97); background: #3570ad; }
|
||||
.btn-photo.shooting { background: #ff4d4f; pointer-events: none; }
|
||||
|
||||
/* 采集结果 */
|
||||
.result-section { flex: 1; display: flex; flex-direction: column; }
|
||||
.result-section__title {
|
||||
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.7);
|
||||
margin-bottom: 10px; display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.result-section__count { font-weight: 400; color: rgba(255,255,255,0.45); font-size: 12px; }
|
||||
.result-grid { display: grid; grid-template-columns: repeat(9, 1fr); gap: 8px; }
|
||||
.result-cell {
|
||||
aspect-ratio: 1; background: #121832; border: 1px dashed rgba(255,255,255,0.1);
|
||||
border-radius: 8px; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 11px; color: rgba(255,255,255,0.2); transition: all 0.2s;
|
||||
overflow: hidden; cursor: pointer; position: relative;
|
||||
}
|
||||
.result-cell:hover:not(.disabled) { border-color: rgba(74,144,217,0.3); background: rgba(74,144,217,0.06); }
|
||||
.result-cell.disabled { opacity: 0.3; cursor: not-allowed; }
|
||||
.result-cell.has-img { border-style: solid; border-color: #52c41a; }
|
||||
.result-cell .thumb-img-wrap {
|
||||
width: 100%; height: 100%; border-radius: 7px; position: relative; overflow: hidden;
|
||||
}
|
||||
.result-cell .thumb-img-wrap svg { width: 100%; height: 100%; }
|
||||
.result-cell .thumb-index {
|
||||
position: absolute; top: 2px; left: 2px;
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
background: rgba(74,144,217,0.9); color: #fff;
|
||||
font-size: 9px; font-weight: 700;
|
||||
display: flex; align-items: center; justify-content: center; z-index: 3;
|
||||
}
|
||||
.result-cell .check-mark {
|
||||
position: absolute; bottom: 2px; right: 2px;
|
||||
width: 16px; height: 16px; border-radius: 50%;
|
||||
background: rgba(82,196,26,0.9); color: #fff; font-size: 10px;
|
||||
display: flex; align-items: center; justify-content: center; z-index: 3;
|
||||
}
|
||||
@keyframes imgAppear {
|
||||
0% { transform: scale(0.5); opacity: 0; }
|
||||
60% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
.result-cell.just-added .thumb-img-wrap { animation: imgAppear 0.4s ease-out; }
|
||||
.result-cell .remove-btn {
|
||||
position: absolute; top: 2px; right: 2px;
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
background: rgba(255,77,79,0.85); color: #fff; font-size: 10px;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
cursor: pointer; border: none; line-height: 1; z-index: 4;
|
||||
}
|
||||
.result-cell.has-img:hover .remove-btn { display: flex; }
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.action-bar { display: flex; gap: 12px; margin-top: auto; }
|
||||
.action-bar .btn-cancel {
|
||||
flex: 1; padding: 10px; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
|
||||
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.7); font-size: 14px;
|
||||
font-weight: 500; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.action-bar .btn-cancel:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.action-bar .btn-resample {
|
||||
flex: 1; padding: 10px; border: 1px solid rgba(250,173,22,0.3); border-radius: 8px;
|
||||
background: rgba(250,173,22,0.08); color: #faad14; font-size: 14px; font-weight: 500;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.action-bar .btn-resample:hover { background: rgba(250,173,22,0.15); border-color: rgba(250,173,22,0.5); }
|
||||
.action-bar .btn-save {
|
||||
flex: 1; padding: 10px; border: none; border-radius: 8px;
|
||||
background: #4a90d9; color: #fff; font-size: 14px; font-weight: 600;
|
||||
cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.action-bar .btn-save:hover { background: #3d7fc0; }
|
||||
.action-bar .btn-save-continue {
|
||||
flex: 1; padding: 10px; border: none; border-radius: 8px;
|
||||
background: #52c41a; color: #fff; font-size: 14px; font-weight: 600;
|
||||
cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.action-bar .btn-save-continue:hover { background: #45a818; }
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
|
||||
background: rgba(0,0,0,0.8); color: #fff; padding: 10px 24px; border-radius: 8px;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.9); }
|
||||
.toast--warn { background: rgba(250,173,22,0.9); }
|
||||
.toast--error { background: rgba(255,77,79,0.9); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">食材采样</span>
|
||||
<a class="back-btn" href="05-sampling-history.html">📝 采样历史</a>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出</button>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- ========== 左侧:食材选择区 ========== -->
|
||||
<div class="food-select-area">
|
||||
<div class="area-title">🥬 食材选择</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<input type="text" class="search-input" id="searchInput" placeholder="搜索食材名称...">
|
||||
</div>
|
||||
|
||||
<!-- 类目Tab -->
|
||||
<div class="filter-tabs" id="catTabs">
|
||||
<span class="filter-tab active" data-cat="all">全部</span>
|
||||
<span class="filter-tab" data-cat="叶菜类">叶菜类</span>
|
||||
<span class="filter-tab" data-cat="花菜类">花菜类</span>
|
||||
<span class="filter-tab" data-cat="瓜果类">瓜果类</span>
|
||||
<span class="filter-tab" data-cat="根茎类">根茎类</span>
|
||||
<span class="filter-tab" data-cat="肉禽类">肉禽类</span>
|
||||
<span class="filter-tab" data-cat="水产类">水产类</span>
|
||||
</div>
|
||||
|
||||
<!-- 食材列表 -->
|
||||
<div class="food-list" id="foodList"></div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 右侧:拍照采集区 ========== -->
|
||||
<div class="capture-area">
|
||||
<!-- 选中食材信息 -->
|
||||
<div class="selected-food-info" id="selectedFoodInfo">
|
||||
<div class="selected-food-info__thumb" id="selectedThumb"></div>
|
||||
<div>
|
||||
<div class="selected-food-info__name" id="selectedName">请选择一个食材</div>
|
||||
<div class="selected-food-info__desc" id="selectedDesc">点击左侧列表中的食材进行采样</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 摄像头区域 -->
|
||||
<div class="camera-frame-wrap">
|
||||
<div class="camera-frame" id="cameraFrame">
|
||||
<span class="camera-frame__text">摄像头预览区域</span>
|
||||
<div class="flash-overlay" id="flashOverlay"></div>
|
||||
<div class="corner corner--tl"></div>
|
||||
<div class="corner corner--tr"></div>
|
||||
<div class="corner corner--bl"></div>
|
||||
<div class="corner corner--br"></div>
|
||||
</div>
|
||||
<button class="btn-photo" id="btnPhoto">📸<br>拍照采集</button>
|
||||
</div>
|
||||
|
||||
<!-- 采集结果 -->
|
||||
<div class="result-section">
|
||||
<div class="result-section__title">
|
||||
<span>📋 采集结果</span>
|
||||
<span class="result-section__count" id="resultCount">0/9</span>
|
||||
</div>
|
||||
<div class="result-grid" id="resultGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<div class="action-bar">
|
||||
<button class="btn-cancel" id="btnCancel">取消</button>
|
||||
<button class="btn-resample" id="btnResample">↻ 重新采样</button>
|
||||
<button class="btn-save" id="btnSave">保存采集</button>
|
||||
<button class="btn-save-continue" id="btnSaveContinue">保存并继续</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* 退出登录 */
|
||||
function handleLogout() {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 食材数据 ===== */
|
||||
var FOOD_DATA = [
|
||||
{ id: 1, name: '菠菜', catLabel: '叶菜类' },
|
||||
{ id: 2, name: '大白菜', catLabel: '叶菜类' },
|
||||
{ id: 3, name: '圆白菜', catLabel: '叶菜类' },
|
||||
{ id: 4, name: '油菜', catLabel: '叶菜类' },
|
||||
{ id: 5, name: '生菜', catLabel: '叶菜类' },
|
||||
{ id: 6, name: '芹菜', catLabel: '叶菜类' },
|
||||
{ id: 7, name: '小白菜', catLabel: '叶菜类' },
|
||||
{ id: 8, name: '西兰花', catLabel: '花菜类' },
|
||||
{ id: 9, name: '花菜', catLabel: '花菜类' },
|
||||
{ id: 10, name: '黄瓜', catLabel: '瓜果类' },
|
||||
{ id: 11, name: '西红柿', catLabel: '瓜果类' },
|
||||
{ id: 12, name: '茄子', catLabel: '瓜果类' },
|
||||
{ id: 13, name: '青椒', catLabel: '瓜果类' },
|
||||
{ id: 14, name: '豆角', catLabel: '瓜果类' },
|
||||
{ id: 15, name: '土豆', catLabel: '根茎类' },
|
||||
{ id: 16, name: '胡萝卜', catLabel: '根茎类' },
|
||||
{ id: 17, name: '猪里脊', catLabel: '肉禽类' },
|
||||
{ id: 18, name: '五花肉', catLabel: '肉禽类' },
|
||||
{ id: 19, name: '草鱼', catLabel: '水产类' },
|
||||
{ id: 20, name: '虾仁', catLabel: '水产类' }
|
||||
];
|
||||
|
||||
/* ===== 拟真食材图片配色 ===== */
|
||||
var FOOD_IMG_STYLES = {
|
||||
1: { bg: ['#2d6a1e', '#4caf50', '#388e3c'] },
|
||||
2: { bg: ['#558b2f', '#8bc34a', '#689f38'] },
|
||||
3: { bg: ['#33691e', '#7cb342', '#558b2f'] },
|
||||
4: { bg: ['#1b5e20', '#43a047', '#2e7d32'] },
|
||||
5: { bg: ['#4caf50', '#81c784', '#66bb6a'] },
|
||||
6: { bg: ['#2e7d32', '#66bb6a', '#4caf50'] },
|
||||
7: { bg: ['#388e3c', '#81c784', '#4caf50'] },
|
||||
8: { bg: ['#2e7d32', '#66bb6a', '#43a047'] },
|
||||
9: { bg: ['#f9a825', '#fdd835', '#fbc02d'] },
|
||||
10: { bg: ['#2e7d32', '#4caf50', '#388e3c'] },
|
||||
11: { bg: ['#c62828', '#ef5350', '#e53935'] },
|
||||
12: { bg: ['#4a148c', '#7b1fa2', '#6a1b9a'] },
|
||||
13: { bg: ['#558b2f', '#8bc34a', '#689f38'] },
|
||||
14: { bg: ['#33691e', '#689f38', '#558b2f'] },
|
||||
15: { bg: ['#8d6e63', '#d7ccc8', '#a1887f'] },
|
||||
16: { bg: ['#e65100', '#ff9800', '#f57c00'] },
|
||||
17: { bg: ['#b71c1c', '#ef5350', '#c62828'] },
|
||||
18: { bg: ['#c62828', '#ef9a9a', '#e57373'] },
|
||||
19: { bg: ['#0d47a1', '#42a5f5', '#1e88e5'] },
|
||||
20: { bg: ['#bf360c', '#ff8a65', '#e64a19'] }
|
||||
};
|
||||
|
||||
function foodSVG(foodId, w, h) {
|
||||
var style = FOOD_IMG_STYLES[foodId] || { bg: ['#304050', '#506870', '#405060'] };
|
||||
var gid = 'fg' + foodId;
|
||||
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '">'
|
||||
+ '<defs><linearGradient id="' + gid + '" x1="0" y1="0" x2="1" y2="1">'
|
||||
+ '<stop offset="0%" stop-color="' + style.bg[0] + '"/>'
|
||||
+ '<stop offset="50%" stop-color="' + style.bg[1] + '"/>'
|
||||
+ '<stop offset="100%" stop-color="' + style.bg[2] + '"/>'
|
||||
+ '</linearGradient></defs>'
|
||||
+ '<rect width="' + w + '" height="' + h + '" fill="url(#' + gid + ')"/>'
|
||||
+ '<ellipse cx="' + (w*0.4) + '" cy="' + (h*0.45) + '" rx="' + (w*0.3) + '" ry="' + (h*0.25) + '" fill="rgba(255,255,255,0.08)"/>'
|
||||
+ '<ellipse cx="' + (w*0.6) + '" cy="' + (h*0.55) + '" rx="' + (w*0.25) + '" ry="' + (h*0.2) + '" fill="rgba(0,0,0,0.1)"/>'
|
||||
+ '<ellipse cx="' + (w*0.35) + '" cy="' + (h*0.35) + '" rx="' + (w*0.12) + '" ry="' + (h*0.1) + '" fill="rgba(255,255,255,0.15)"/>'
|
||||
+ '</svg>';
|
||||
}
|
||||
|
||||
/* ===== 状态变量 ===== */
|
||||
var selectedFood = null;
|
||||
var capturedImages = [];
|
||||
var lastAddedIndex = -1;
|
||||
var currentCat = 'all';
|
||||
var searchKeyword = '';
|
||||
var MAX_IMAGES = 9;
|
||||
|
||||
/* ===== DOM 引用 ===== */
|
||||
var foodListEl = document.getElementById('foodList');
|
||||
var searchInputEl = document.getElementById('searchInput');
|
||||
var catTabsEl = document.getElementById('catTabs');
|
||||
var selectedThumbEl = document.getElementById('selectedThumb');
|
||||
var selectedNameEl = document.getElementById('selectedName');
|
||||
var selectedDescEl = document.getElementById('selectedDesc');
|
||||
var cameraFrameEl = document.getElementById('cameraFrame');
|
||||
var flashOverlayEl = document.getElementById('flashOverlay');
|
||||
var btnPhotoEl = document.getElementById('btnPhoto');
|
||||
var resultGridEl = document.getElementById('resultGrid');
|
||||
var resultCountEl = document.getElementById('resultCount');
|
||||
var toastEl = document.getElementById('toast');
|
||||
|
||||
/* ===== 解析 URL 参数 ===== */
|
||||
function getQueryParam(key) {
|
||||
var query = window.location.search.substring(1);
|
||||
var pairs = query.split('&');
|
||||
for (var i = 0; i < pairs.length; i++) {
|
||||
var parts = pairs[i].split('=');
|
||||
if (parts[0] === key) return decodeURIComponent(parts[1] || '');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* ===== 初始化 ===== */
|
||||
function init() {
|
||||
renderFoodList();
|
||||
renderResultGrid();
|
||||
bindEvents();
|
||||
|
||||
/* 处理从采样历史进入的补充采集 */
|
||||
var resampleFoodId = getQueryParam('resampleFoodId');
|
||||
if (resampleFoodId) {
|
||||
var fid = parseInt(resampleFoodId);
|
||||
for (var i = 0; i < FOOD_DATA.length; i++) {
|
||||
if (FOOD_DATA[i].id === fid) {
|
||||
selectedFood = FOOD_DATA[i];
|
||||
renderFoodList();
|
||||
updateSelectedFoodInfo();
|
||||
showToast('已自动选中:' + selectedFood.name + ',可直接追加采集', 2000, 'success');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 筛选 ===== */
|
||||
function getFilteredFoods() {
|
||||
return FOOD_DATA.filter(function(food) {
|
||||
if (currentCat !== 'all' && food.catLabel !== currentCat) return false;
|
||||
if (searchKeyword && food.name.indexOf(searchKeyword) === -1) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/* ===== 渲染食材列表 ===== */
|
||||
function renderFoodList() {
|
||||
var filtered = getFilteredFoods();
|
||||
|
||||
if (filtered.length === 0) {
|
||||
foodListEl.innerHTML = '<div class="empty-state"><div class="empty-state__icon">🔍</div><div class="empty-state__text">未找到匹配的食材</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
for (var i = 0; i < filtered.length; i++) {
|
||||
var food = filtered[i];
|
||||
var isSelected = selectedFood && selectedFood.id === food.id;
|
||||
html += '<div class="food-item' + (isSelected ? ' selected' : '') + '" data-id="' + food.id + '">'
|
||||
+ '<div class="food-item__thumb">' + foodSVG(food.id, 52, 52) + '</div>'
|
||||
+ '<div class="food-item__info">'
|
||||
+ '<div class="food-item__name">' + food.name + '</div>'
|
||||
+ '<div class="food-item__detail">' + food.catLabel + '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
}
|
||||
foodListEl.innerHTML = html;
|
||||
}
|
||||
|
||||
/* ===== 渲染采集结果网格 ===== */
|
||||
function renderResultGrid() {
|
||||
var html = '';
|
||||
for (var i = 0; i < MAX_IMAGES; i++) {
|
||||
if (i < capturedImages.length) {
|
||||
var isJustAdded = (i === lastAddedIndex);
|
||||
html += '<div class="result-cell has-img' + (isJustAdded ? ' just-added' : '') + '" data-index="' + i + '">'
|
||||
+ '<div class="thumb-img-wrap">'
|
||||
+ '<span class="thumb-index">' + (i + 1) + '</span>'
|
||||
+ foodSVG(capturedImages[i].id, 120, 120)
|
||||
+ '<span class="check-mark">✔</span>'
|
||||
+ '</div>'
|
||||
+ '<button class="remove-btn" data-index="' + i + '">✖</button>'
|
||||
+ '</div>';
|
||||
} else if (i === capturedImages.length) {
|
||||
html += '<div class="result-cell" data-index="' + i + '">+添加</div>';
|
||||
} else {
|
||||
html += '<div class="result-cell disabled" data-index="' + i + '"></div>';
|
||||
}
|
||||
}
|
||||
resultGridEl.innerHTML = html;
|
||||
resultCountEl.textContent = capturedImages.length + '/' + MAX_IMAGES;
|
||||
lastAddedIndex = -1;
|
||||
}
|
||||
|
||||
/* ===== 更新选中食材信息 ===== */
|
||||
function updateSelectedFoodInfo() {
|
||||
if (!selectedFood) {
|
||||
selectedThumbEl.innerHTML = '';
|
||||
selectedNameEl.textContent = '请选择一个食材';
|
||||
selectedDescEl.textContent = '点击左侧列表中的食材进行采样';
|
||||
return;
|
||||
}
|
||||
selectedThumbEl.innerHTML = foodSVG(selectedFood.id, 44, 44);
|
||||
selectedNameEl.textContent = selectedFood.name;
|
||||
selectedDescEl.textContent = selectedFood.catLabel + ' · 样本采集';
|
||||
}
|
||||
|
||||
/* ===== Toast ===== */
|
||||
function showToast(msg, duration, type) {
|
||||
toastEl.textContent = msg;
|
||||
toastEl.className = 'toast';
|
||||
if (type === 'success') toastEl.classList.add('toast--success');
|
||||
else if (type === 'warn') toastEl.classList.add('toast--warn');
|
||||
else if (type === 'error') toastEl.classList.add('toast--error');
|
||||
toastEl.classList.add('show');
|
||||
clearTimeout(toastEl._timer);
|
||||
var t = duration || 2000;
|
||||
toastEl._timer = setTimeout(function() { toastEl.classList.remove('show'); }, t);
|
||||
}
|
||||
|
||||
/* ===== 事件绑定 ===== */
|
||||
function bindEvents() {
|
||||
|
||||
/* 食材列表点击 */
|
||||
foodListEl.addEventListener('click', function(e) {
|
||||
var item = e.target.closest('.food-item');
|
||||
if (!item) return;
|
||||
var id = parseInt(item.getAttribute('data-id'));
|
||||
var food = null;
|
||||
for (var i = 0; i < FOOD_DATA.length; i++) {
|
||||
if (FOOD_DATA[i].id === id) { food = FOOD_DATA[i]; break; }
|
||||
}
|
||||
if (!food) return;
|
||||
selectedFood = food;
|
||||
renderFoodList();
|
||||
updateSelectedFoodInfo();
|
||||
showToast('已选中:' + food.name, 1200, 'success');
|
||||
});
|
||||
|
||||
/* 类目Tab切换 */
|
||||
catTabsEl.addEventListener('click', function(e) {
|
||||
var tab = e.target.closest('.filter-tab');
|
||||
if (!tab) return;
|
||||
currentCat = tab.getAttribute('data-cat');
|
||||
var tabs = catTabsEl.querySelectorAll('.filter-tab');
|
||||
for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove('active');
|
||||
tab.classList.add('active');
|
||||
renderFoodList();
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
var searchTimer = null;
|
||||
searchInputEl.addEventListener('input', function() {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(function() {
|
||||
searchKeyword = searchInputEl.value.replace(/^\s+|\s+$/g, '');
|
||||
renderFoodList();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
/* 拍照按钮 */
|
||||
btnPhotoEl.addEventListener('click', function() {
|
||||
if (!selectedFood) {
|
||||
showToast('请先选择一个食材', 1500, 'warn');
|
||||
return;
|
||||
}
|
||||
if (capturedImages.length >= MAX_IMAGES) {
|
||||
showToast('已达采集上限(9张)', 1500, 'warn');
|
||||
return;
|
||||
}
|
||||
btnPhotoEl.classList.add('shooting');
|
||||
cameraFrameEl.classList.add('active');
|
||||
|
||||
flashOverlayEl.classList.remove('active');
|
||||
void flashOverlayEl.offsetWidth;
|
||||
flashOverlayEl.classList.add('active');
|
||||
|
||||
setTimeout(function() {
|
||||
btnPhotoEl.classList.remove('shooting');
|
||||
cameraFrameEl.classList.remove('active');
|
||||
capturedImages.push(selectedFood);
|
||||
lastAddedIndex = capturedImages.length - 1;
|
||||
renderResultGrid();
|
||||
showToast('采集成功!已采集 ' + capturedImages.length + '/' + MAX_IMAGES + ' 张', 1500, 'success');
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
/* 结果网格交互 */
|
||||
resultGridEl.addEventListener('click', function(e) {
|
||||
var removeBtn = e.target.closest('.remove-btn');
|
||||
if (removeBtn) {
|
||||
e.stopPropagation();
|
||||
var idx = parseInt(removeBtn.getAttribute('data-index'));
|
||||
capturedImages.splice(idx, 1);
|
||||
renderResultGrid();
|
||||
showToast('已删除采集图片', 1200);
|
||||
return;
|
||||
}
|
||||
var cell = e.target.closest('.result-cell');
|
||||
if (!cell || cell.classList.contains('disabled')) return;
|
||||
if (!selectedFood) {
|
||||
showToast('请先选择一个食材', 1500, 'warn');
|
||||
return;
|
||||
}
|
||||
if (capturedImages.length >= MAX_IMAGES) {
|
||||
showToast('已达采集上限', 1500, 'warn');
|
||||
return;
|
||||
}
|
||||
btnPhotoEl.click();
|
||||
});
|
||||
|
||||
/* 保存采集 → 返回欢迎页 */
|
||||
document.getElementById('btnSave').addEventListener('click', function() {
|
||||
if (!selectedFood) { showToast('请先选择一个食材', 1500, 'warn'); return; }
|
||||
if (capturedImages.length === 0) { showToast('请至少采集1张图片', 1500, 'warn'); return; }
|
||||
showToast('采集数据已保存', 1500, 'success');
|
||||
setTimeout(function() {
|
||||
window.location.href = '00-welcome.html';
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
/* 保存并继续 → 重置表单继续采集 */
|
||||
document.getElementById('btnSaveContinue').addEventListener('click', function() {
|
||||
if (!selectedFood) { showToast('请先选择一个食材', 1500, 'warn'); return; }
|
||||
if (capturedImages.length === 0) { showToast('请至少采集1张图片', 1500, 'warn'); return; }
|
||||
showToast('采集数据已保存,请继续采集下一个食材', 1500, 'success');
|
||||
selectedFood = null;
|
||||
capturedImages = [];
|
||||
renderFoodList();
|
||||
renderResultGrid();
|
||||
updateSelectedFoodInfo();
|
||||
});
|
||||
|
||||
/* 取消 */
|
||||
document.getElementById('btnCancel').addEventListener('click', function() {
|
||||
if (capturedImages.length === 0 && !selectedFood) {
|
||||
window.location.href = '00-welcome.html';
|
||||
return;
|
||||
}
|
||||
selectedFood = null;
|
||||
capturedImages = [];
|
||||
renderFoodList();
|
||||
renderResultGrid();
|
||||
updateSelectedFoodInfo();
|
||||
showToast('已取消当前采集', 1000);
|
||||
});
|
||||
|
||||
/* 重新采样 */
|
||||
document.getElementById('btnResample').addEventListener('click', function() {
|
||||
if (capturedImages.length === 0 && !selectedFood) {
|
||||
showToast('当前无采样数据', 1500, 'warn');
|
||||
return;
|
||||
}
|
||||
selectedFood = null;
|
||||
capturedImages = [];
|
||||
renderFoodList();
|
||||
renderResultGrid();
|
||||
updateSelectedFoodInfo();
|
||||
showToast('已清空,请重新选择食材开始采样', 1500);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/* 启动 */
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,521 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>毛菜验收秤 - 采样历史 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.top-bar {
|
||||
display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.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); }
|
||||
/* 退出登录按钮 */
|
||||
.logout-btn {
|
||||
padding: 4px 10px; font-size: 11px;
|
||||
background: rgba(255,77,79,0.08); border: 1px solid rgba(255,77,79,0.2);
|
||||
border-radius: 5px; color: rgba(255,77,79,0.7); cursor: pointer;
|
||||
transition: all 0.2s; font-family: inherit;
|
||||
}
|
||||
.logout-btn:hover { background: rgba(255,77,79,0.18); color: #ff6b6d; border-color: rgba(255,77,79,0.4); }
|
||||
|
||||
/* ---- 主内容区:左右两列 ---- */
|
||||
.main-content { display: flex; gap: 16px; padding: 16px 20px; height: calc(100% - 44px); }
|
||||
|
||||
/* ===== 左侧:快捷查找面板 ===== */
|
||||
.search-panel {
|
||||
width: 260px; flex-shrink: 0; background: #1a2b4a; border-radius: 12px;
|
||||
padding: 16px; display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
.search-panel__title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.7); }
|
||||
.search-panel__input {
|
||||
width: 100%; padding: 10px 14px; background: #121832;
|
||||
border: 1px solid rgba(255,255,255,0.1); border-radius: 8px;
|
||||
color: #fff; font-size: 13px; outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.search-panel__input:focus { border-color: #4a90d9; }
|
||||
.search-panel__input::placeholder { color: rgba(255,255,255,0.3); }
|
||||
.cat-tabs { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.cat-tab {
|
||||
padding: 5px 10px; font-size: 12px; border-radius: 4px;
|
||||
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.55);
|
||||
cursor: pointer; transition: all 0.2s; border: 1px solid transparent;
|
||||
}
|
||||
.cat-tab.active { background: #4a90d9; color: #fff; }
|
||||
.cat-tab:hover:not(.active) { border-color: rgba(255,255,255,0.15); color: rgba(255,255,255,0.75); }
|
||||
|
||||
.search-panel__stats {
|
||||
margin-top: auto; padding-top: 12px;
|
||||
border-top: 1px solid rgba(255,255,255,0.06);
|
||||
font-size: 12px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
.search-panel__stats em { font-style: normal; color: #4a90d9; font-weight: 600; }
|
||||
|
||||
/* ===== 右侧:瀑布流 ===== */
|
||||
.waterfall-area { flex: 1; overflow-y: auto; }
|
||||
.waterfall-area::-webkit-scrollbar { width: 6px; }
|
||||
.waterfall-area::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 3px; }
|
||||
.waterfall { column-count: 3; column-gap: 16px; }
|
||||
.waterfall-card {
|
||||
break-inside: avoid; background: #1a2b4a; border-radius: 12px;
|
||||
padding: 16px; margin-bottom: 16px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.waterfall-card:hover {
|
||||
border-color: rgba(74,144,217,0.3);
|
||||
background: rgba(74,144,217,0.06);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
||||
.card-thumb { width: 56px; height: 56px; border-radius: 10px; overflow: hidden; flex-shrink: 0; }
|
||||
.card-thumb svg { width: 100%; height: 100%; }
|
||||
.card-name { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
|
||||
.card-category { font-size: 12px; color: rgba(255,255,255,0.45); }
|
||||
|
||||
/* 卡片缩略图预览 */
|
||||
.card-previews { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-bottom: 12px; }
|
||||
.card-preview { aspect-ratio: 1; border-radius: 6px; overflow: hidden; }
|
||||
.card-preview svg { width: 100%; height: 100%; }
|
||||
.card-preview.hidden { visibility: hidden; }
|
||||
|
||||
/* 卡片底部信息 */
|
||||
.card-footer { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.card-count { color: #4a90d9; font-weight: 500; }
|
||||
|
||||
/* ---- 详情弹窗 ---- */
|
||||
.modal-overlay {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 999;
|
||||
opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
.modal-overlay.active { opacity: 1; visibility: visible; }
|
||||
|
||||
.modal-container {
|
||||
background: #1a2b4a; border-radius: 16px; padding: 28px;
|
||||
width: 700px; max-height: 80vh; overflow-y: auto;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||||
transform: translateY(-20px); transition: transform 0.3s;
|
||||
}
|
||||
.modal-overlay.active .modal-container { transform: translateY(0); }
|
||||
|
||||
.modal-header { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; }
|
||||
.modal-header__left { display: flex; align-items: center; gap: 14px; flex: 1; }
|
||||
.modal-header__right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
|
||||
.modal-thumb { width: 52px; height: 52px; border-radius: 10px; overflow: hidden; flex-shrink: 0; }
|
||||
.modal-thumb svg { width: 100%; height: 100%; }
|
||||
.modal-title { font-size: 20px; font-weight: 600; }
|
||||
.modal-subtitle { font-size: 13px; color: rgba(255,255,255,0.45); margin-top: 4px; }
|
||||
.modal-time { font-size: 12px; color: rgba(255,255,255,0.35); text-align: right; line-height: 1.5; }
|
||||
.modal-count {
|
||||
display: flex; align-items: baseline; gap: 3px;
|
||||
font-size: 14px; color: #52c41a; font-weight: 700;
|
||||
background: rgba(82,196,26,0.1); border-radius: 6px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.modal-count .count-label { font-size: 10px; font-weight: 400; color: rgba(82,196,26,0.6); }
|
||||
|
||||
/* 图片网格 */
|
||||
.image-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
||||
.image-cell {
|
||||
aspect-ratio: 1; border-radius: 10px; overflow: hidden;
|
||||
position: relative; border: 2px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.image-cell svg { width: 100%; height: 100%; }
|
||||
.image-cell__time {
|
||||
position: absolute; bottom: 0; left: 0; right: 0;
|
||||
background: rgba(0,0,0,0.6); color: rgba(255,255,255,0.75);
|
||||
font-size: 11px; text-align: center; padding: 3px 6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.image-cell .remove-btn {
|
||||
position: absolute; top: 4px; right: 4px;
|
||||
width: 22px; height: 22px; border-radius: 50%;
|
||||
background: rgba(255,77,79,0.9); color: #fff; font-size: 12px;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
cursor: pointer; border: none; line-height: 1;
|
||||
}
|
||||
.image-cell:hover .remove-btn { display: flex; }
|
||||
|
||||
/* 弹窗信息行 */
|
||||
.modal-info {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 8px 16px;
|
||||
margin-bottom: 20px; padding: 12px 16px;
|
||||
background: rgba(255,255,255,0.03); border-radius: 8px;
|
||||
}
|
||||
.modal-info__label { font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 2px; }
|
||||
.modal-info__value { font-size: 14px; font-weight: 500; }
|
||||
|
||||
/* 底部操作 */
|
||||
.modal-actions { display: flex; gap: 12px; margin-top: 24px; }
|
||||
.modal-actions button {
|
||||
flex: 1; padding: 10px; border-radius: 8px; font-size: 14px; font-weight: 500;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.modal-actions .btn-close {
|
||||
background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.7);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.modal-actions .btn-close:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
||||
.modal-actions .btn-resample {
|
||||
background: rgba(255,193,7,0.1); color: #ffc107;
|
||||
border: 1px solid rgba(255,193,7,0.3);
|
||||
}
|
||||
.modal-actions .btn-resample:hover { background: rgba(255,193,7,0.2); border-color: #ffc107; }
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
position: fixed; top: 60px; left: 50%; transform: translateX(-50%) translateY(-20px);
|
||||
background: rgba(0,0,0,0.8); color: #fff; padding: 10px 24px; border-radius: 8px;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">采样历史</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出</button>
|
||||
</div>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<div class="main-content">
|
||||
|
||||
<!-- 左侧:快捷查找面板 -->
|
||||
<div class="search-panel">
|
||||
<div class="search-panel__title">🔍 快捷查找</div>
|
||||
|
||||
<input type="text" class="search-panel__input" id="searchInput" placeholder="输入食材名称...">
|
||||
|
||||
<div>
|
||||
<div class="search-panel__title" style="margin-bottom:8px;">📁 按类目筛选</div>
|
||||
<div class="cat-tabs" id="catTabs">
|
||||
<span class="cat-tab active" data-cat="all">全部</span>
|
||||
<span class="cat-tab" data-cat="叶菜类">叶菜类</span>
|
||||
<span class="cat-tab" data-cat="花菜类">花菜类</span>
|
||||
<span class="cat-tab" data-cat="瓜果类">瓜果类</span>
|
||||
<span class="cat-tab" data-cat="根茎类">根茎类</span>
|
||||
<span class="cat-tab" data-cat="肉禽类">肉禽类</span>
|
||||
<span class="cat-tab" data-cat="水产类">水产类</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="search-panel__stats">
|
||||
共 <em id="totalCount">0</em> 条记录
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:瀑布流 -->
|
||||
<div class="waterfall-area">
|
||||
<div class="waterfall" id="waterfall"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<div class="modal-overlay" id="detailModal">
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header__left">
|
||||
<div class="modal-thumb" id="modalThumb"></div>
|
||||
<div>
|
||||
<div class="modal-title" id="modalTitle"></div>
|
||||
<div class="modal-subtitle" id="modalSubtitle"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-header__right">
|
||||
<div class="modal-count" id="modalCount"></div>
|
||||
<div class="modal-time" id="modalTime"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 采样信息 -->
|
||||
<div class="modal-info" id="modalInfo"></div>
|
||||
<!-- 图片网格 -->
|
||||
<div class="image-grid" id="imageGrid"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-close" onclick="closeModal()">✖ 关闭</button>
|
||||
<button class="btn-resample" id="btnResample" onclick="resampleFood()">📸 补充采集</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
/* 退出登录 */
|
||||
function handleLogout() {
|
||||
if (confirm('确定要退出登录吗?')) {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 采样历史数据 ===== */
|
||||
var SAMPLING_HISTORY = [
|
||||
{ id: 1, foodId: 1, name: '菠菜', catLabel: '叶菜类', note: '色泽新鲜,根部完整', count: 5, time: '2026-06-11 10:30', images: [1,1,1,1,1] },
|
||||
{ id: 2, foodId: 15, name: '土豆', catLabel: '根茎类', note: '表皮完整,无发芽', count: 3, time: '2026-06-11 09:15', images: [15,15,15] },
|
||||
{ id: 3, foodId: 17, name: '猪里脊', catLabel: '肉禽类', note: '肉质鲜红,纹理清晰', count: 4, time: '2026-06-11 08:45', images: [17,17,17,17] },
|
||||
{ id: 4, foodId: 16, name: '胡萝卜', catLabel: '根茎类', note: '颜色橙红,形状均匀', count: 6, time: '2026-06-10 14:20', images: [16,16,16,16,16,16] },
|
||||
{ id: 5, foodId: 19, name: '草鱼', catLabel: '水产类', note: '鱼体完整,鳞片光泽', count: 2, time: '2026-06-10 11:30', images: [19,19] },
|
||||
{ id: 6, foodId: 2, name: '大白菜', catLabel: '叶菜类', note: '叶片紧实,无虫害', count: 4, time: '2026-06-10 10:00', images: [2,2,2,2] },
|
||||
{ id: 7, foodId: 8, name: '西兰花', catLabel: '花菜类', note: '花球紧实,颜色翠绿', count: 3, time: '2026-06-09 15:10', images: [8,8,8] },
|
||||
{ id: 8, foodId: 11, name: '西红柿', catLabel: '瓜果类', note: '颜色均匀,无破损', count: 7, time: '2026-06-09 09:30', images: [11,11,11,11,11,11,11] },
|
||||
{ id: 9, foodId: 10, name: '黄瓜', catLabel: '瓜果类', note: '鲜嫩,顶花带刺', count: 5, time: '2026-06-08 14:00', images: [10,10,10,10,10] },
|
||||
{ id: 10, foodId: 18, name: '五花肉', catLabel: '肉禽类', note: '肥瘦分层均匀', count: 3, time: '2026-06-08 10:20', images: [18,18,18] },
|
||||
{ id: 11, foodId: 20, name: '虾仁', catLabel: '水产类', note: '虾体完整,弹性好', count: 4, time: '2026-06-07 16:30', images: [20,20,20,20] },
|
||||
{ id: 12, foodId: 9, name: '花菜', catLabel: '花菜类', note: '花球紧实,颜色洁白', count: 2, time: '2026-06-07 09:00', images: [9,9] }
|
||||
];
|
||||
|
||||
var currentDetailId = null;
|
||||
var currentCat = 'all';
|
||||
var searchKeyword = '';
|
||||
|
||||
/* ===== 拟真食材图片 ===== */
|
||||
var FOOD_IMG_STYLES = {
|
||||
1: { bg: ['#2d6a1e', '#4caf50', '#388e3c'] },
|
||||
2: { bg: ['#558b2f', '#8bc34a', '#689f38'] },
|
||||
8: { bg: ['#2e7d32', '#66bb6a', '#43a047'] },
|
||||
9: { bg: ['#f9a825', '#fdd835', '#fbc02d'] },
|
||||
10: { bg: ['#2e7d32', '#4caf50', '#388e3c'] },
|
||||
11: { bg: ['#c62828', '#ef5350', '#e53935'] },
|
||||
15: { bg: ['#8d6e63', '#d7ccc8', '#a1887f'] },
|
||||
16: { bg: ['#e65100', '#ff9800', '#f57c00'] },
|
||||
17: { bg: ['#b71c1c', '#ef5350', '#c62828'] },
|
||||
18: { bg: ['#c62828', '#ef9a9a', '#e57373'] },
|
||||
19: { bg: ['#0d47a1', '#42a5f5', '#1e88e5'] },
|
||||
20: { bg: ['#bf360c', '#ff8a65', '#e64a19'] }
|
||||
};
|
||||
|
||||
function foodSVG(foodId, w, h) {
|
||||
var style = FOOD_IMG_STYLES[foodId] || { bg: ['#304050', '#506870', '#405060'] };
|
||||
var gid = 'fg' + foodId;
|
||||
return '<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '">'
|
||||
+ '<defs><linearGradient id="' + gid + '" x1="0" y1="0" x2="1" y2="1">'
|
||||
+ '<stop offset="0%" stop-color="' + style.bg[0] + '"/>'
|
||||
+ '<stop offset="50%" stop-color="' + style.bg[1] + '"/>'
|
||||
+ '<stop offset="100%" stop-color="' + style.bg[2] + '"/>'
|
||||
+ '</linearGradient></defs>'
|
||||
+ '<rect width="' + w + '" height="' + h + '" fill="url(#' + gid + ')"/>'
|
||||
+ '<ellipse cx="' + (w*0.4) + '" cy="' + (h*0.45) + '" rx="' + (w*0.3) + '" ry="' + (h*0.25) + '" fill="rgba(255,255,255,0.08)"/>'
|
||||
+ '<ellipse cx="' + (w*0.6) + '" cy="' + (h*0.55) + '" rx="' + (w*0.25) + '" ry="' + (h*0.2) + '" fill="rgba(0,0,0,0.1)"/>'
|
||||
+ '<ellipse cx="' + (w*0.35) + '" cy="' + (h*0.35) + '" rx="' + (w*0.12) + '" ry="' + (h*0.1) + '" fill="rgba(255,255,255,0.15)"/>'
|
||||
+ '</svg>';
|
||||
}
|
||||
|
||||
/* ===== DOM 引用 ===== */
|
||||
var waterfallEl = document.getElementById('waterfall');
|
||||
var modalEl = document.getElementById('detailModal');
|
||||
var modalThumbEl = document.getElementById('modalThumb');
|
||||
var modalTitleEl = document.getElementById('modalTitle');
|
||||
var modalSubtitleEl = document.getElementById('modalSubtitle');
|
||||
var modalCountEl = document.getElementById('modalCount');
|
||||
var modalTimeEl = document.getElementById('modalTime');
|
||||
var modalInfoEl = document.getElementById('modalInfo');
|
||||
var imageGridEl = document.getElementById('imageGrid');
|
||||
var toastEl = document.getElementById('toast');
|
||||
var searchInputEl = document.getElementById('searchInput');
|
||||
var catTabsEl = document.getElementById('catTabs');
|
||||
var totalCountEl = document.getElementById('totalCount');
|
||||
|
||||
/* ===== 筛选 ===== */
|
||||
function getFilteredRecords() {
|
||||
return SAMPLING_HISTORY.filter(function(record) {
|
||||
if (currentCat !== 'all' && record.catLabel !== currentCat) return false;
|
||||
if (searchKeyword && record.name.indexOf(searchKeyword) === -1) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/* ===== 渲染瀑布流 ===== */
|
||||
function renderWaterfall() {
|
||||
var filtered = getFilteredRecords();
|
||||
totalCountEl.textContent = filtered.length;
|
||||
|
||||
if (filtered.length === 0) {
|
||||
waterfallEl.innerHTML = '<div style="text-align:center;padding:80px 0;color:rgba(255,255,255,0.3);">未找到匹配的采样记录</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '';
|
||||
for (var i = 0; i < filtered.length; i++) {
|
||||
var record = filtered[i];
|
||||
var previewCount = Math.min(record.count, 3);
|
||||
var previews = '';
|
||||
for (var j = 0; j < 3; j++) {
|
||||
if (j < previewCount) {
|
||||
previews += '<div class="card-preview">' + foodSVG(record.foodId, 60, 60) + '</div>';
|
||||
} else {
|
||||
previews += '<div class="card-preview hidden"></div>';
|
||||
}
|
||||
}
|
||||
html += '<div class="waterfall-card" data-id="' + record.id + '" onclick="openDetail(' + record.id + ')">'
|
||||
+ '<div class="card-header">'
|
||||
+ '<div class="card-thumb">' + foodSVG(record.foodId, 56, 56) + '</div>'
|
||||
+ '<div>'
|
||||
+ '<div class="card-name">' + record.name + '</div>'
|
||||
+ '<div class="card-category">' + record.catLabel + '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="card-previews">' + previews + '</div>'
|
||||
+ '<div class="card-footer">'
|
||||
+ '<span class="card-count">已采 ' + record.count + ' 张</span>'
|
||||
+ '<span>' + record.time.substring(5) + '</span>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
}
|
||||
waterfallEl.innerHTML = html;
|
||||
}
|
||||
|
||||
/* ===== 打开详情弹窗 ===== */
|
||||
function openDetail(id) {
|
||||
var record = null;
|
||||
for (var i = 0; i < SAMPLING_HISTORY.length; i++) {
|
||||
if (SAMPLING_HISTORY[i].id === id) { record = SAMPLING_HISTORY[i]; break; }
|
||||
}
|
||||
if (!record) return;
|
||||
|
||||
currentDetailId = id;
|
||||
modalThumbEl.innerHTML = foodSVG(record.foodId, 52, 52);
|
||||
modalTitleEl.textContent = record.name;
|
||||
modalSubtitleEl.textContent = record.catLabel + ' · 采样记录';
|
||||
modalCountEl.innerHTML = record.count + '<span class="count-label">张</span>';
|
||||
var parts = record.time.split(' ');
|
||||
modalTimeEl.innerHTML = '<div>' + (parts[0] || '') + '</div><div>' + (parts[1] || '') + '</div>';
|
||||
|
||||
/* 采样信息 */
|
||||
modalInfoEl.innerHTML = ''
|
||||
+ '<div class="modal-info__item"><div class="modal-info__label">采样备注</div><div class="modal-info__value">' + (record.note || '无') + '</div></div>';
|
||||
|
||||
/* 已满9张时隐藏补充采集按钮 */
|
||||
var btnResample = document.getElementById('btnResample');
|
||||
btnResample.style.display = record.count >= 9 ? 'none' : '';
|
||||
|
||||
renderImageGrid(record);
|
||||
modalEl.classList.add('active');
|
||||
}
|
||||
|
||||
/* ===== 渲染图片网格 ===== */
|
||||
function renderImageGrid(record) {
|
||||
var MAX_IMAGES = 9;
|
||||
var html = '';
|
||||
|
||||
for (var i = 0; i < record.images.length; i++) {
|
||||
var canRemove = record.images.length > 1;
|
||||
html += '<div class="image-cell">'
|
||||
+ foodSVG(record.foodId, 120, 120)
|
||||
+ '<span class="image-cell__time">' + record.time + '</span>'
|
||||
+ (canRemove ? '<button class="remove-btn" data-idx="' + i + '">✖</button>' : '')
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
var remaining = MAX_IMAGES - record.images.length;
|
||||
for (var i = 0; i < remaining; i++) {
|
||||
html += '<div class="image-cell" style="opacity:0.3;"></div>';
|
||||
}
|
||||
|
||||
imageGridEl.innerHTML = html;
|
||||
}
|
||||
|
||||
/* ===== 删除图片 ===== */
|
||||
function removeImage(idx) {
|
||||
var record = null;
|
||||
for (var i = 0; i < SAMPLING_HISTORY.length; i++) {
|
||||
if (SAMPLING_HISTORY[i].id === currentDetailId) { record = SAMPLING_HISTORY[i]; break; }
|
||||
}
|
||||
if (!record || record.images.length <= 1) return;
|
||||
|
||||
record.images.splice(idx, 1);
|
||||
record.count = record.images.length;
|
||||
renderImageGrid(record);
|
||||
renderWaterfall();
|
||||
showToast('删除成功', 1200);
|
||||
}
|
||||
|
||||
/* ===== 关闭弹窗 ===== */
|
||||
function closeModal() {
|
||||
modalEl.classList.remove('active');
|
||||
currentDetailId = null;
|
||||
}
|
||||
|
||||
/* ===== 补充采集 ===== */
|
||||
function resampleFood() {
|
||||
if (!currentDetailId) return;
|
||||
var record = null;
|
||||
for (var i = 0; i < SAMPLING_HISTORY.length; i++) {
|
||||
if (SAMPLING_HISTORY[i].id === currentDetailId) { record = SAMPLING_HISTORY[i]; break; }
|
||||
}
|
||||
if (!record) return;
|
||||
closeModal();
|
||||
setTimeout(function() {
|
||||
window.location.href = '04-ingredient-sampling.html?resampleFoodId=' + record.foodId;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/* ===== Toast ===== */
|
||||
function showToast(msg, duration) {
|
||||
toastEl.textContent = msg;
|
||||
toastEl.classList.add('show');
|
||||
clearTimeout(toastEl._timer);
|
||||
var t = duration || 2000;
|
||||
toastEl._timer = setTimeout(function() { toastEl.classList.remove('show'); }, t);
|
||||
}
|
||||
|
||||
/* ===== 事件绑定 ===== */
|
||||
imageGridEl.addEventListener('click', function(e) {
|
||||
var removeBtn = e.target.closest('.remove-btn');
|
||||
if (removeBtn) {
|
||||
e.stopPropagation();
|
||||
var idx = parseInt(removeBtn.getAttribute('data-idx'));
|
||||
removeImage(idx);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
modalEl.addEventListener('click', function(e) {
|
||||
if (e.target === modalEl) closeModal();
|
||||
});
|
||||
|
||||
catTabsEl.addEventListener('click', function(e) {
|
||||
var tab = e.target.closest('.cat-tab');
|
||||
if (!tab) return;
|
||||
currentCat = tab.getAttribute('data-cat');
|
||||
var tabs = catTabsEl.querySelectorAll('.cat-tab');
|
||||
for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove('active');
|
||||
tab.classList.add('active');
|
||||
renderWaterfall();
|
||||
});
|
||||
|
||||
var searchTimer = null;
|
||||
searchInputEl.addEventListener('input', function() {
|
||||
clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(function() {
|
||||
searchKeyword = searchInputEl.value.replace(/^\s+|\s+$/g, '');
|
||||
renderWaterfall();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
/* ===== 初始化 ===== */
|
||||
renderWaterfall();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
# 毛菜验收秤 - 变更记录
|
||||
|
||||
## [2026-06-15] feat: 新增重新识别按钮 + 验收状态模型重构 + 暂时隐藏验收不通过逻辑
|
||||
|
||||
### 新增
|
||||
- **重新识别按钮**(`01-verify-inspection.html`):Step 2 食材识别区新增「🔄 重新识别」按钮,点击后清空候选列表并重新触发 AI 识别,重置识别状态与摄像头画面。
|
||||
|
||||
### 变更
|
||||
- **订单卡片状态标签重构**(`01-verify-inspection.html`):
|
||||
- 原"发货状态"(全量发货 / 部分发货)改为"验收状态"(未验收 / 部分验收)
|
||||
- 数据字段 `shipStatus` 字面量改为 `未验收` / `部分验收`
|
||||
- 验收状态与发货无关,始终只判断是否全部验收通过,未全部通过即为"部分验收"
|
||||
- 标签颜色:未验收(绿) / 部分验收(橙)
|
||||
- **PRD.md**:同步更新订单卡片字段表、验收流程、验收汇总弹窗、mermaid 流程图等相关章节
|
||||
- **UI-SPEC.md**:同步更新标签颜色定义、订单卡片字段布局、Step2 布局图、操作列按钮说明
|
||||
- **flow.md**:同步更新 Step1 卡片结构、Step2 布局图、流程步骤描述
|
||||
|
||||
### 暂时隐藏(代码保留,后期可恢复)
|
||||
- **验收不通过按钮**(`01-verify-inspection.html`):食材列表操作列中的「✗ 不通过」按钮隐藏(JS 注释 + 空字符串替换)
|
||||
- **不合格处置弹窗**:验收不通过时唤起的处置策略面板(折叠式)整体隐藏
|
||||
- **支付类型标签**(`01-verify-inspection.html`):订单卡片底部「前置/后置」付款类型 pill 通过 `display:none` 隐藏
|
||||
- **验收汇总弹窗中的不通过数量/不合格明细/付款模式**:汇总弹窗中相关统计字段隐藏
|
||||
- PRD / UI-SPEC / flow.md 中对应内容均以 `~~删除线~~` + `**暂时隐藏**` 标注
|
||||
|
||||
---
|
||||
|
||||
## [2026-06-13] feat: 首次创建毛菜验收秤模块
|
||||
|
||||
### 新增
|
||||
- 首次创建毛菜验收秤模块(6 个页面)
|
||||
- `00-welcome.html` — 欢迎页:人脸识别 + 账号密码登录 + 功能菜单
|
||||
- `01-verify-inspection.html` — 验收食材:Step1 订单卡片选择 + Step2 食材识别称重验收
|
||||
- `02-settings.html` — 设置:偏差率阈值配置 + 设备状态监控
|
||||
- `03-verify-history.html` — 验收记录:食材/订单维度切换 + 日期条筛选
|
||||
- `04-ingredient-sampling.html` — 食材采样:拍照采集 + 品级判定
|
||||
- `05-sampling-history.html` — 采样历史:瀑布流卡片 + 详情弹窗 + 补充采集
|
||||
Binary file not shown.
@@ -0,0 +1,710 @@
|
||||
# 毛菜验收秤 — 产品需求文档
|
||||
|
||||
> 原型目录:`other-module/canteen-device/raw-veg-inspection-scale/`
|
||||
> 创建时间:2026-06-13 | 最后更新:2026-06-13
|
||||
|
||||
---
|
||||
|
||||
## 一、模块概述
|
||||
|
||||
毛菜验收秤是食堂毛菜入库流程中的核心硬件终端,承担食材验收、采样、记录查询三大职能。设备配备触摸屏、称重传感器、食材识别摄像头、人脸识别摄像头和场景监控摄像头,通过分步向导在单页面内完成从选单到提交的全流程。
|
||||
|
||||
### 页面导航结构
|
||||
|
||||
```
|
||||
00 欢迎页(人脸识别 / 账号密码登录)
|
||||
├── 验收食材 → 01-verify-inspection
|
||||
│ ├── Step 1:选择订单(含无订单验收入口)
|
||||
│ └── Step 2:食材识别 + 称重验收 + 不合格处置
|
||||
├── 验收记录 → 03-verify-history
|
||||
│ ├── 食材维度视图
|
||||
│ └── 订单维度视图
|
||||
├── 食材采样 → 04-ingredient-sampling
|
||||
├── 采样历史 → 05-sampling-history
|
||||
└── 设置 → 02-settings
|
||||
```
|
||||
|
||||
#### 页面间跳转关系
|
||||
|
||||
| 来源页 | 触发操作 | 目标页 | 携带参数 |
|
||||
|--------|----------|--------|----------|
|
||||
| 根导航(index.html) | 进入毛菜验收秤 | 00 欢迎页 | — |
|
||||
| 00 欢迎页 | 登录成功后点击菜单 | 01 / 02 / 03 / 04 / 05 | `?user=姓名&uid=工号` |
|
||||
| 01 Step2 | 提交验收结果 | 00 欢迎页 | `?user=...&uid=...` |
|
||||
| 01 汇总弹窗 | 提交并关闭 | 00 欢迎页 | `?user=...&uid=...` |
|
||||
| 01 汇总弹窗 | 提交并继续 | 01 Step1(当前页重置) | — |
|
||||
| 01 Step2 | 提交并继续(无订单模式) | 01 Step2(当前页清空数据) | — |
|
||||
| 01 | 顶栏 ← 返回 | 来源页(动态检测) | — |
|
||||
| 02 / 03 / 05 | 顶栏 ← 返回 | 00 欢迎页 | — |
|
||||
| 04 | 顶栏 ← 返回 | 00 欢迎页 | — |
|
||||
| 04 | 顶栏「采样历史」 | 05 采样历史 | — |
|
||||
| 05 | 详情弹窗「补充采集」 | 04 食材采样 | `?resampleFoodId=...` |
|
||||
| 01 ~ 05 | 顶栏「退出」按钮 | 00 欢迎页 | — |
|
||||
|
||||
> **返回按钮动态检测**:01 页面根据 `document.referrer` 判断来源页(00/04/05),设置返回目标;若无法判断则默认返回 00。
|
||||
|
||||
### 核心状态流转
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[欢迎页] -->|人脸识别/账号登录| B{功能菜单}
|
||||
B -->|验收食材| C[Step1: 选择订单]
|
||||
B -->|验收记录| H[验收记录页]
|
||||
B -->|食材采样| S1[采样流程]
|
||||
B -->|采样历史| S2[采样历史页]
|
||||
B -->|设置| SET[设置页]
|
||||
|
||||
C -->|点击订单卡片| D[Step2: 食材验收]
|
||||
C -->|无订单验收| D2[无订单验收弹窗 → Step2]
|
||||
|
||||
D --> E{称重完成}
|
||||
E -->|拿起食材| F[自动通过]
|
||||
%% E -->|点击✗不通过| G[不合格处置弹窗] 暂时隐藏,后期恢复
|
||||
%% G -->|确认处置| F2[标记不合格]
|
||||
|
||||
F --> I{还有食材?}
|
||||
%% F2 --> I 暂时隐藏
|
||||
I -->|是| E
|
||||
I -->|否| J[提交验收]
|
||||
J -->|提交验收结果| K[Toast成功 → 返回首页]
|
||||
J -->|提交并继续| L[清空数据 → 继续当前订单]
|
||||
|
||||
H -->|食材维度| H1[逐条食材记录]
|
||||
H -->|订单维度| H2[按订单分组汇总]
|
||||
|
||||
S1 --> S3[称重 + 识别 + 采样]
|
||||
S3 --> S4[保存采样]
|
||||
S4 -->|继续采样| S1
|
||||
S4 -->|进入验收| C
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、页面清单
|
||||
|
||||
| 编号 | 文件名 | 页面名称 | 类型 | 原型链接 |
|
||||
|------|--------|----------|------|----------|
|
||||
| 00 | `00-welcome.html` | 欢迎页 | 功能页 | [打开原型](../00-welcome.html) |
|
||||
| 01 | `01-verify-inspection.html` | 验收食材 | 功能页(分步向导) | [打开原型](../01-verify-inspection.html) |
|
||||
| 02 | `02-settings.html` | 设置 | 功能页 | [打开原型](../02-settings.html) |
|
||||
| 03 | `03-verify-history.html` | 验收记录 | 功能页 | [打开原型](../03-verify-history.html) |
|
||||
| 04 | `04-ingredient-sampling.html` | 食材采样 | 功能页 | [打开原型](../04-ingredient-sampling.html) |
|
||||
| 05 | `05-sampling-history.html` | 采样历史 | 功能页 | [打开原型](../05-sampling-history.html) |
|
||||
|
||||
**弹窗清单**:
|
||||
|
||||
| 弹窗名称 | 所在页面 | 触发条件 |
|
||||
|----------|----------|----------|
|
||||
| 无订单验收弹窗 | 01 Step1 | 点击右上角「无订单验收」按钮 |
|
||||
| 人工选择食材弹窗 | 01 Step2 | 点击识别区手动选择食材 |
|
||||
| ~~不合格处置弹窗~~ | 01 Step2 | ~~点击操作列「✗」按钮~~ **暂时隐藏** |
|
||||
| 验收汇总弹窗 | 01 Step2 | 全部食材验收完成(仅订单模式) |
|
||||
| 返回确认弹窗 | 01 Step2 | 验收过程中点击返回 |
|
||||
|
||||
---
|
||||
|
||||
## 三、逐页说明
|
||||
|
||||
### 3.1 欢迎页(00-welcome.html)
|
||||
|
||||
[打开原型](../00-welcome.html)
|
||||
|
||||
#### 功能说明
|
||||
|
||||
设备开机后的入口页面。左侧为人脸识别区域(调用人脸识别摄像头),右侧为功能菜单。支持两种登录方式:人脸识别登录、账号密码登录。
|
||||
|
||||
#### 页面布局
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 左侧(440px) │ 右侧(flex:1) │
|
||||
│ │ │
|
||||
│ 人脸识别摄像头画面 │ 功能按钮: │
|
||||
│ · 点击激活识别 │ · 验收食材 │
|
||||
│ · 识别中动画 │ · 验收记录 │
|
||||
│ · 识别成功(显示姓名+工号) │ · 食材采样 │
|
||||
│ │ · 采样历史 │
|
||||
│ ── 或 ── │ · 设置 │
|
||||
│ 账号密码登录 │ │
|
||||
│ · Tab切换 │ 登录状态提示 │
|
||||
│ · 账号输入框 │ │
|
||||
│ · 密码输入框 │ │
|
||||
│ · 登录按钮 │ │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 交互说明
|
||||
|
||||
- **人脸识别登录**:点击识别区域激活摄像头 → 识别中(动画)→ 识别成功显示姓名和工号 → 解锁右侧菜单
|
||||
- **账号密码登录**:点击 Tab 切换到账号登录 → 输入账号密码 → 点击登录 → 验证通过后解锁菜单
|
||||
- **权限拦截**:未登录时点击任意菜单按钮 → Toast 提示「您无权使用该设备,请联系管理员添加权限」
|
||||
- **登录成功**:携带 `user` + `uid` 参数跳转目标页面
|
||||
|
||||
#### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| 账号 | 文本输入 | 用户账号 |
|
||||
| 密码 | 密码输入 | 用户密码 |
|
||||
|
||||
---
|
||||
|
||||
### 3.2 验收食材(01-verify-inspection.html)
|
||||
|
||||
[打开原型](../01-verify-inspection.html)
|
||||
|
||||
本页面采用**分步向导**形式,在单页面内完成从选单到提交的全过程。
|
||||
|
||||
#### 3.2.1 Step 1:选择订单
|
||||
|
||||
##### 功能说明
|
||||
|
||||
展示当前操作人关联的待验收订单列表,采用横向滑动卡片布局。页面右上角常驻「无订单验收」入口。
|
||||
|
||||
##### 页面布局
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 顶部栏:← 返回 | 标题 | 操作人 | [退出] | [无订单验收] │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 订单卡片1 │ │ 订单卡片2 │ │ 订单卡片3 │ ←横滑 │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
##### 订单卡片字段
|
||||
|
||||
| 区域 | 字段 | 说明 |
|
||||
|------|------|------|
|
||||
| 顶部 | 供应商名称 | 订单供应商 |
|
||||
| 顶部 | 订单编号 | PO 编号 |
|
||||
| 中间 | 食材种类数 + 食材标签 | 合并显示(如「13种食材」),食材过多时显示「+N项」可点击弹窗查看全部 |
|
||||
| 日期区 | 采购日期 | 虚线框内左右排列 |
|
||||
| 日期区 | 预计到货 | 虚线框内左右排列,蓝色字体 |
|
||||
| 配送行 | 配送方式 | 本地配送 / 三方快递 |
|
||||
| 配送行 | 配送码/物流单号 | — |
|
||||
| 底部 | 验收状态标签 | 未验收(绿)/ 部分验收(橙) |
|
||||
| ~~底部~~ | ~~付款类型~~ | ~~前置付款 / 后置付款~~ **暂时隐藏** |
|
||||
| 底部 | 采购人 | — |
|
||||
| CTA | 进入验收按钮 | 点击整张卡片或按钮进入 Step2 |
|
||||
|
||||
##### 无订单验收弹窗
|
||||
|
||||
点击右上角「无订单验收」按钮弹出:
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 供应商 | 下拉选择 | 是 | 外采供应商 |
|
||||
| 来源类型 | 只读 | — | 固定为「外采」 |
|
||||
|
||||
确认后进入 Step2 无订单模式。
|
||||
|
||||
---
|
||||
|
||||
#### 3.2.2 Step 2:食材识别 + 验收判定
|
||||
|
||||
##### 功能说明
|
||||
|
||||
两列布局。左列为食材识别区(摄像头画面 + 候选食材卡片),右列为订单信息 + 食材列表 + 操作区。
|
||||
|
||||
##### 页面布局
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ 顶部栏:← 返回 | 标题 | 操作人头像+姓名 | [退出] │
|
||||
├──────────────┬─────────────────────────────────────────┤
|
||||
│ 左列(440px) │ 右列(flex:1) │
|
||||
│ │ │
|
||||
│ 食材识别摄像头 │ ┌─────────────────────────────────────┐│
|
||||
│ · 十字准线 │ │ 订单信息卡片 ││
|
||||
│ · 四角标记 │ │ 入库单号|订单号|供应商|发货人|来源类型 ││
|
||||
│ · 点击手动选择 │ │ 预计到货|食材数量|(应付总金额) ││
|
||||
│ │ └─────────────────────────────────────┘│
|
||||
│ 识别状态栏 │ │
|
||||
│ · 就绪/称重中 │ ┌─────────────────────────────────────┐│
|
||||
│ · 称重完成 │ │ 食材列表 ││
|
||||
│ │ │ 食材|采购规格|采购量|采购单价|应付金额 ││
|
||||
│ [🔄 重新识别] │ │ |实收规格|实收量|操作 ││
|
||||
│ 验收进度条 │ │ ││
|
||||
│ │ │ ││
|
||||
│ 候选食材 2×N │ │ [行数据...] ││
|
||||
│ · 图片+名称 │ └─────────────────────────────────────┘│
|
||||
│ · 点击选中 │ │
|
||||
│ │ ┌─────────────────────────────────────┐│
|
||||
│ │ │ 操作区 ││
|
||||
│ │ │ [↻ 称重清零(重新放置)] ││
|
||||
│ │ │ [提交并继续] [提交验收结果] ││
|
||||
│ │ └─────────────────────────────────────┘│
|
||||
└──────────────┴─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
##### 订单信息卡片字段
|
||||
|
||||
| 字段 | 显示条件 | 说明 |
|
||||
|------|----------|------|
|
||||
| 入库单号 | 始终 | 系统生成(IN-xxxx) |
|
||||
| 订单号 | 有订单模式 | PO 编号;无订单模式隐藏 |
|
||||
| 供应商 | 有订单模式 | 订单供应商;无订单模式隐藏 |
|
||||
| 发货人 | 有订单模式 | 订单发货人;无订单模式隐藏 |
|
||||
| 来源类型 | 有订单模式 | 自产 / 外采;无订单模式隐藏 |
|
||||
| 预计到货 | 有订单模式 | 预计到货时间;无订单模式隐藏 |
|
||||
| 食材数量 | 始终 | 订单中食材种类数(如「13 种」) |
|
||||
| 应付总金额 | 后置付款订单 / 无订单模式 | 所有食材应付金额汇总,黄色高亮 |
|
||||
|
||||
##### 食材列表字段(8列)
|
||||
|
||||
| 列名 | 说明 | 交互 |
|
||||
|------|------|------|
|
||||
| 食材 | 食材名称 + 分类标签(蔬菜/调料等)+ 溯源码 | 只读 |
|
||||
| 采购规格 | 采购单位 | **下拉单选**(斤/公斤/克/千克/袋/瓶/箱/桶/罐/份/个/只/条/把) |
|
||||
| 采购量 | 订单采购数量/重量 | 只读;**无订单模式显示"--"** |
|
||||
| 采购单价 | 单价 | **可编辑输入框**(数字) |
|
||||
| 应付金额 | 单价 × 采购量 | 自动计算,黄色显示 |
|
||||
| 实收规格 | 实际验收单位 | **下拉单选**(同采购规格选项),默认跟随采购规格 |
|
||||
| 实收量 | 实际验收数量/重量 | 调料显示数量选择器(±按钮);普通食材显示称重值 |
|
||||
| 操作 | 删除(~~+ 验收不通过~~ 暂时隐藏) | 见下方交互说明 |
|
||||
|
||||
##### 操作列交互
|
||||
|
||||
| 按钮 | 有订单模式 | 无订单模式 |
|
||||
|------|-----------|-----------|
|
||||
| 🗑 删除 | 有验收记录时可用,清零该食材记录 | **始终可用**,从列表彻底移除 |
|
||||
| ~~✗ 验收不通过~~ | ~~有验收记录时可用,打开处置弹窗~~ **暂时隐藏** | **不显示** |
|
||||
|
||||
##### 验收流程
|
||||
|
||||
```
|
||||
食材放上秤盘
|
||||
↓
|
||||
自动称重(食材识别摄像头同步识别品种)
|
||||
↓
|
||||
重量稳定(约3秒)
|
||||
↓
|
||||
拿起食材 → 自动判定通过 ✓
|
||||
↓
|
||||
称重归零,进入下一个食材
|
||||
↓
|
||||
全部食材完成 → 提交
|
||||
```
|
||||
|
||||
- 同一食材多次放上秤盘 → 重量累计增加
|
||||
- 称重稳定后,操作区显示提示「称重完成 · 拿起食材即视为通过」
|
||||
- 拿起食材后自动判定通过,无需手动确认
|
||||
- ~~不拿起食材时可点击「✗」打开不合格处置弹窗~~ **暂时隐藏,后期恢复**
|
||||
- 点击识别区下方的「🔄 重新识别」按钮可清除当前结果,重新触发 AI 识别
|
||||
|
||||
##### 候选食材区域
|
||||
|
||||
- AI 识别摄像头扫描后推送相似度最高的候选食材(2列网格卡片)
|
||||
- 每张卡片显示:食材图片/emoji + 名称
|
||||
- 点击卡片 → 选中该食材 → 自动开始称重
|
||||
- 无订单模式:识别到什么就添加什么,同名食材累加重量
|
||||
- 有订单模式:优先匹配订单内食材,匹配不到则新增
|
||||
- **重新识别**:点击识别状态条下方的「🔄 重新识别」按钮,可清空当前候选结果,重新触发 AI 识别流程
|
||||
|
||||
---
|
||||
|
||||
#### 3.2.3 不合格处置弹窗 **(暂时隐藏,后期恢复)**
|
||||
|
||||
> ⚠️ 当前版本已隐藏此弹窗入口,操作列中的「✗ 验收不通过」按钮不显示。以下为原始设计文档,保留备后期恢复参考。
|
||||
|
||||
##### 触发条件
|
||||
|
||||
~~点击操作列「✗ 验收不通过」按钮。~~
|
||||
|
||||
##### 弹窗字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 食材信息 | 只读 | — | 显示当前食材名称和重量 |
|
||||
| 处理方式 | 单选按钮 | 是 | 退货退款 / 仅退款 / 换货 |
|
||||
| 处理范围 | 单选按钮 | 是 | 部分 / 全部 |
|
||||
| 退款金额 | 数值 | 前置付款可编辑,后置付款只读 | 根据处置自动计算 |
|
||||
| 计算说明 | 只读文字 | — | 常驻显示:退款重量 = 应收 − 实收 |
|
||||
| 不合格原因 | 下拉选择 | 是 | 原因列表(如发霉变色、枯萎严重、死鱼不收等) |
|
||||
| 备注说明 | 文本输入 | 否 | 补充说明 |
|
||||
|
||||
##### 处理方式与付款模式关系
|
||||
|
||||
| 付款模式 | 可选处理方式 | 退款相关 |
|
||||
|----------|-------------|----------|
|
||||
| 前置付款 | 退货退款、仅退款、换货 | 退款金额**可编辑** |
|
||||
| 后置付款 | 仅换货 | 无退款,仅换货 |
|
||||
|
||||
##### 处理范围自动判定
|
||||
|
||||
| 条件 | 自动判定 | 退款重量计算 |
|
||||
|------|----------|-------------|
|
||||
| 秤体有重量(实重 > 0) | 部分 | 退款重量 = 应收重量 − 当前秤上实收重量 |
|
||||
| 秤体无重量(实重 = 0) | 全部 | 退款重量 = 应收重量 |
|
||||
|
||||
用户可手动切换处理范围(部分 ↔ 全部),切换后退款重量重新计算。
|
||||
|
||||
##### 不合格原因选项
|
||||
|
||||
| 选项 |
|
||||
|------|
|
||||
| 重量不符(实收与订单差异超标) |
|
||||
| 发错货(实物与订单品种不一致) |
|
||||
| 新鲜度不足/腐烂变质(萎蔫/变色/异味/腐烂) |
|
||||
| 存在异物/杂质 |
|
||||
| 包装破损/污染 |
|
||||
| 数量短缺 |
|
||||
| 其他原因 |
|
||||
|
||||
---
|
||||
|
||||
#### 3.2.4 验收汇总弹窗
|
||||
|
||||
##### 触发条件
|
||||
|
||||
全部食材验收完成后**自动弹出**(**仅订单模式**,无订单模式不弹出)。
|
||||
|
||||
> 弹窗为全屏遮罩层,弹出后底部按钮不可点击,用户必须从弹窗中选择操作。
|
||||
|
||||
##### 弹窗内容
|
||||
|
||||
| 内容 | 说明 |
|
||||
|------|------|
|
||||
| 订单信息 | 订单号 \| 供应商 \| ~~付款模式(前置/后置)~~ 暂时隐藏 |
|
||||
| 通过数量 | 绿色高亮 |
|
||||
| ~~不通过数量~~ | ~~红色高亮~~ **暂时隐藏** |
|
||||
| ~~不合格明细~~ | ~~按不合格类型汇总项数~~ **暂时隐藏** |
|
||||
| 金额汇总 | 应付金额、扣减金额(有扣减时显示)、最终金额(前置显示"应退"、后置显示"实付") |
|
||||
|
||||
##### 弹窗操作
|
||||
|
||||
| 按钮 | 说明 |
|
||||
|------|------|
|
||||
| 提交并继续 | 关闭弹窗 → Toast「验收数据已提交」→ **清空验收数据,回到 Step 1** 选择下一张订单 |
|
||||
| 提交并关闭 | 关闭弹窗 → Toast「验收数据已提交」→ **跳回欢迎页**(携带 user + uid 参数) |
|
||||
|
||||
---
|
||||
|
||||
#### 3.2.5 操作与提交
|
||||
|
||||
##### 常驻按钮
|
||||
|
||||
| 按钮 | 位置 | 行为 |
|
||||
|------|------|------|
|
||||
| 🔄 重新识别 | 左列识别状态条下方 | 清除当前 AI 识别结果和候选区,重新触发 AI 识别(Toast「正在重新识别...」→ 1.2 秒后重新生成候选) |
|
||||
| 称重清零 | 右侧操作区底部 | 清空当前秤上食材的称重数据,允许重新放置同一食材再次称重 |
|
||||
| 提交验收结果 | 右下角(全部完成后激活) | 见下方提交流程 |
|
||||
| 提交并继续 | 右下角(全部完成后激活) | 见下方提交流程 |
|
||||
|
||||
##### 提交流程(两条路径)
|
||||
|
||||
```
|
||||
全部食材验收完成
|
||||
↓
|
||||
┌─ 有订单模式 → 自动弹出「验收汇总弹窗」(见 3.2.4)
|
||||
│ ├─ 弹窗内「提交并继续」→ 回 Step 1 选新订单
|
||||
│ └─ 弹窗内「提交并关闭」→ 回欢迎页
|
||||
│
|
||||
└─ 无订单模式 → 不弹窗,直接点击底部按钮
|
||||
├─「提交验收结果」→ Toast「提交成功」→ 回欢迎页
|
||||
└─「提交并继续」→ Toast「提交成功,继续验收」→ 清空验收数据,停留在 Step 2
|
||||
```
|
||||
|
||||
> 无订单模式下金额可直接在食材行编辑单价,不需要汇总弹窗确认,因此跳过弹窗直接提交。
|
||||
|
||||
> 注:食材列表下方的付款明细区域(应付金额 + 扣减金额)已取消独立展示,相关金额信息统一在订单卡片中呈现。
|
||||
|
||||
---
|
||||
|
||||
### 3.3 验收记录(03-verify-history.html)
|
||||
|
||||
[打开原型](../03-verify-history.html)
|
||||
|
||||
#### 功能说明
|
||||
|
||||
验收历史数据查询页面。支持日期筛选、状态筛选、关键词搜索,以及**食材维度/订单维度**切换。
|
||||
|
||||
#### 页面布局
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 顶部栏:← 返回 | 标题 | [退出] │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ 日期条(横滑)| 模式切换(日/周)| 状态筛选 | 搜索 | 维度切换 │
|
||||
│ [今天][昨天][周一]... [日视图][本周] [全部][通过][不通过] │ 🔍 | [食材][订单] │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ 统计行:今日验收总数 | 验收通过 | 验收不通过 | 合格率 │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ 数据表格 │
|
||||
│ 入库单号|订单号|食材/数量|供应商|支付方式|配送方式 │
|
||||
│ |时间|验收结果/状态|操作 │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ 底栏:共 X 条记录 │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 维度切换
|
||||
|
||||
| 维度 | 说明 | 食材列 | 结果列 |
|
||||
|------|------|--------|--------|
|
||||
| 食材维度 | 按食材逐条展示 | 食材名称 | 验收结果(通过/不通过/待验收) |
|
||||
| 订单维度 | 按订单号分组汇总 | 食材数量(N 种) | 验收状态(验收完成/待验收) |
|
||||
|
||||
- 订单维度下,同一订单的多条食材记录合并为一行
|
||||
- 无订单号的记录(无订单验收)各自独立成组
|
||||
- 订单维度下,若该订单所有食材均通过 → 「验收完成」;否则 → 「待验收」
|
||||
|
||||
#### 表格字段
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| 入库单号 | IN-xxxx 格式 |
|
||||
| 订单号 | PO 编号;无订单验收显示"--" |
|
||||
| 食材(食材维度) | 食材名称 |
|
||||
| 食材数量(订单维度) | 该订单包含的食材种类数 |
|
||||
| 供应商 | 供应商名称 |
|
||||
| 支付方式 | 前置(绿标签)/ 后置(橙标签) |
|
||||
| 配送方式 | 本地配送 / 三方快递 |
|
||||
| 时间 | 验收时间(HH:mm) |
|
||||
| 验收结果(食材维度) | 通过(绿)/ 不通过(红)/ 待验收(蓝) |
|
||||
| 验收状态(订单维度) | 验收完成(绿)/ 待验收(橙) |
|
||||
| 操作 | 「详情」链接 → 打开详情抽屉 |
|
||||
|
||||
#### 详情抽屉
|
||||
|
||||
点击「详情」后从右侧滑出,展示该条记录的完整信息:
|
||||
|
||||
| 分组 | 字段 |
|
||||
|------|------|
|
||||
| 入库信息 | 入库单号、验收时间、操作人、来源类型 |
|
||||
| 食材信息 | 食材名称、供应商、订单重量、实际重量、偏差率、验收结果 |
|
||||
| 订单信息 | 订单号、支付方式、配送方式 |
|
||||
| 不合格处置(仅不通过时) | 处理方式、处理范围、不合格原因 |
|
||||
|
||||
---
|
||||
|
||||
### 3.4 食材采样(04-ingredient-sampling.html)
|
||||
|
||||
[打开原型](../04-ingredient-sampling.html)
|
||||
|
||||
#### 功能说明
|
||||
|
||||
采样是验收的前置环节,用于在正式验收前采集食材样本数据,为后续验收提供对比基准。
|
||||
|
||||
#### 采样流程
|
||||
|
||||
```
|
||||
选择待采样订单 → 食材放上秤盘 → 自动称重 + AI识别
|
||||
→ 系统生成溯源码 + 拉取配送信息
|
||||
→ 外观品级判定(合格/不合格)
|
||||
→ 填写采样备注(可选)
|
||||
→ [保存采集] 或 [保存并继续]
|
||||
```
|
||||
|
||||
#### 采样字段
|
||||
|
||||
| 字段 | 来源 | 说明 |
|
||||
|------|------|------|
|
||||
| 采样编号 | 系统自动生成 | SP-xxxx 格式 |
|
||||
| 溯源码 | 系统自动 | 自产:唯一匹配;外采:自动生成 |
|
||||
| 来源类型 | 系统自动 | 自产 / 外采 |
|
||||
| 供应商 | 系统自动 | 从订单获取 |
|
||||
| 食材名称 | AI识别(可修正) | 食材识别摄像头输出 |
|
||||
| 采样重量(kg) | 秤体传感器 | 核心产出 |
|
||||
| 采样时间 | 系统自动 | — |
|
||||
| 操作人 | 人脸识别自动 | — |
|
||||
| 外观品级 | 采样人选择 | 合格 / 不合格 |
|
||||
| 采样备注 | 采样人输入(可选) | 文字描述 |
|
||||
|
||||
---
|
||||
|
||||
### 3.5 采样历史(05-sampling-history.html)
|
||||
|
||||
[打开原型](../05-sampling-history.html)
|
||||
|
||||
#### 功能说明
|
||||
|
||||
食材样本库,独立于验收流程。展示所有历史采样记录,供验收时对比参考。
|
||||
|
||||
#### 页面布局
|
||||
|
||||
卡片式列表,每张卡片展示一条采样记录。
|
||||
|
||||
#### 卡片字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| 食材名称 | 采样食材 |
|
||||
| 分类标签 | 蔬菜/调料等 |
|
||||
| 预览图片 | 采样时拍摄 |
|
||||
| 采样数量 | 该食材采样次数 |
|
||||
| 采样时间 | 最近一次采样时间 |
|
||||
|
||||
点击卡片 → 弹窗展示采样详情(采样备注等)。
|
||||
|
||||
> 注:采样历史不涉及验收合格/不合格概念,不涉及供应商信息,是独立的食材样本库。
|
||||
|
||||
---
|
||||
|
||||
### 3.6 设置(02-settings.html)
|
||||
|
||||
[打开原型](../02-settings.html)
|
||||
|
||||
#### 功能说明
|
||||
|
||||
设备参数配置页面。
|
||||
|
||||
#### 配置项
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| 偏差率阈值(%) | 重量偏差率 ≤ 阈值 → 自动判定通过;> 阈值 → 自动判定不通过 |
|
||||
|
||||
> 配置全局生效,所有验收秤共用同一阈值。
|
||||
|
||||
---
|
||||
|
||||
## 四、表单字段表
|
||||
|
||||
### 4.1 无订单验收弹窗
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|----------|
|
||||
| 供应商 | 下拉选择 | 是 | 必须选择一项 |
|
||||
| 来源类型 | 只读 | — | 固定「外采」 |
|
||||
|
||||
### 4.2 不合格处置弹窗
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|----------|
|
||||
| 处理方式 | 单选 | 是 | 退货退款/仅退款/换货(后置付款仅换货) |
|
||||
| 处理范围 | 单选 | 是 | 部分/全部 |
|
||||
| 退款金额 | 数值 | 前置付款必填 | ≥ 0,最多两位小数(后置付款只读) |
|
||||
| 不合格原因 | 下拉选择 | 是 | 必须选择一项(重量不符/发错货/新鲜度不足/存在异物/包装破损/数量短缺/其他原因) |
|
||||
| 备注说明 | 文本 | 否 | 最多 200 字 |
|
||||
|
||||
### 4.3 采样表单
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验规则 |
|
||||
|------|------|------|----------|
|
||||
| 食材名称 | AI识别 + 可修正 | 是 | 必须为食材库中存在的食材 |
|
||||
| 外观品级 | 单选 | 是 | 合格/不合格 |
|
||||
| 采样备注 | 文本 | 否 | 最多 200 字 |
|
||||
|
||||
---
|
||||
|
||||
## 五、核心业务规则
|
||||
|
||||
### 5.1 付款模式
|
||||
|
||||
| 模式 | 说明 | 订单卡片 | 处置规则 |
|
||||
|------|------|----------|----------|
|
||||
| 前置付款 | 下单时已付款 | 不显示应付总金额 | 支持退货退款/仅退款/换货,退款金额可编辑 |
|
||||
| 后置付款 | 验收后付款 | 显示应付总金额 | 仅支持换货,无退款 |
|
||||
|
||||
### 5.2 有订单 vs 无订单验收
|
||||
|
||||
| 对比项 | 有订单模式 | 无订单模式 |
|
||||
|--------|-----------|-----------|
|
||||
| 食材来源 | 来自订单预设列表 | AI识别到什么添加什么 |
|
||||
| 采购量 | 显示订单采购量 | 显示"--" |
|
||||
| 采购规格/单价 | 来自订单 | 来自系统食材目录 |
|
||||
| ~~验收不通过~~ | ~~可用~~ **暂时隐藏** | **不可用**(不渲染按钮) |
|
||||
| 删除 | 有记录时可用 | **始终可用** |
|
||||
| 验收汇总弹窗 | 全部完成后弹出 | **不弹出** |
|
||||
| 应付总金额 | 后置付款显示 | 始终显示 |
|
||||
|
||||
### 5.3 人脸验证策略
|
||||
|
||||
不持续监控人脸,仅在关键节点校验:
|
||||
|
||||
| 时机 | 本人 | 换人 |
|
||||
|------|------|------|
|
||||
| 欢迎页进入系统 | 进入菜单 | 无权使用,Toast 提示 |
|
||||
| 切换功能页面 | 携带身份参数跳转 | 跳回欢迎页重新验证 |
|
||||
| 提交确认操作 | 正常提交 | Toast「请本人操作」,阻止提交 |
|
||||
|
||||
### 5.4 摄像头职责
|
||||
|
||||
| 摄像头 | 前端展示 | 用途 | 交互 |
|
||||
|--------|----------|------|------|
|
||||
| 食材识别摄像头 | 01/04 页面左列识别区 | AI 识别食材品种 | 是 |
|
||||
| 人脸识别摄像头 | 仅验证时调用,不常驻 | 关键节点身份校验 | 是 |
|
||||
| 场景监控摄像头 | 不展示,后台静默录制 | 全程录像 | 否 |
|
||||
|
||||
### 5.5 不合格处理方式 **(暂时隐藏,后期恢复)**
|
||||
|
||||
> ⚠️ 当前版本已隐藏验收不通过入口,以下规则暂不启用,保留备后期恢复参考。
|
||||
|
||||
| 处理方式 | 范围 | 含义 |
|
||||
|----------|------|------|
|
||||
| 退货退款 | 全部/部分 | 退回供应商,全额或按比例退款 |
|
||||
| 仅退款 | 全部/部分 | 不退货,全额或按比例退款 |
|
||||
| 换货 | 全部/部分 | 退回不合格品,供应商补发 |
|
||||
|
||||
---
|
||||
|
||||
## 六、全局交互约定
|
||||
|
||||
### 6.1 退出登录
|
||||
|
||||
所有子页面(01 ~ 05)顶栏右侧均放置「退出」按钮:
|
||||
|
||||
- 样式:淡红色底 + 红色细边框,hover 变亮
|
||||
- 点击后弹出 `confirm` 确认对话框:「确定要退出登录吗?」
|
||||
- 确认后跳转至 `00-welcome.html`,清除当前会话状态
|
||||
- 欢迎页(00)无退出按钮(用户尚未登录)
|
||||
|
||||
### 6.2 用户身份参数传递
|
||||
|
||||
| 环节 | 说明 |
|
||||
|------|------|
|
||||
| 登录成功 | 欢迎页将 `user`(姓名)+ `uid`(工号)编码为 URL 参数 |
|
||||
| 跳转子页面 | 格式:`目标页.html?user=张三&uid=EMP-20260001` |
|
||||
| 子页面读取 | 各页面通过 `getQueryParam('user')` 获取并展示操作人信息 |
|
||||
| 提交返回 | 验收提交后返回首页时同样携带参数,首页可识别已登录状态 |
|
||||
|
||||
### 6.3 Toast 提示规范
|
||||
|
||||
| 类型 | 颜色 | 使用场景 |
|
||||
|------|------|----------|
|
||||
| 成功(success) | 绿色背景 | 提交成功、保存成功 |
|
||||
| 警告(warn) | 黄色背景 | 权限不足、必填项为空、已达上限 |
|
||||
| 错误(error) | 红色背景 | 操作失败、网络异常 |
|
||||
| 普通(默认) | 深色半透明背景 | 一般提示(删除成功、已取消等) |
|
||||
|
||||
- 位置:屏幕顶部居中,固定定位
|
||||
- 自动消失,持续时间根据内容 1200 ~ 3000ms
|
||||
- 同一时刻只显示一条,新 Toast 覆盖旧 Toast
|
||||
|
||||
### 6.4 返回按钮行为
|
||||
|
||||
| 页面 | 返回目标 | 特殊处理 |
|
||||
|------|----------|----------|
|
||||
| 00 欢迎页 | `../index.html`(根导航) | — |
|
||||
| 01 Step1 | 来源页(动态检测) | — |
|
||||
| 01 Step2 | 有验收数据 → 弹出返回确认弹窗;无数据 → 回退到 Step1 | 确认弹窗防止误操作丢失数据 |
|
||||
| 02 ~ 05 | `00-welcome.html` | — |
|
||||
|
||||
---
|
||||
|
||||
## 七、秤端与后台职责划分
|
||||
|
||||
### 秤端
|
||||
|
||||
- 人脸识别 / 账号密码登录
|
||||
- 订单选择、食材识别、称重计量
|
||||
- AI 识别食材品种(可手动修正)
|
||||
- 验收结果自动判定 + 手动修改
|
||||
- 不合格处置策略录入
|
||||
- 采样数据保存
|
||||
- 场景监控全程录像
|
||||
|
||||
### 后台
|
||||
|
||||
- 订单数据拉取(从绿色种采-采供管理)
|
||||
- 溯源码分配(自产唯一匹配,外采自动生成)
|
||||
- 入库单号生成
|
||||
- 偏差率阈值配置
|
||||
- 供应商类型映射
|
||||
- 库存联动(入库成功后更新仓储)
|
||||
- 验收/采样记录查询导出
|
||||
Binary file not shown.
@@ -0,0 +1,623 @@
|
||||
# 毛菜验收秤 — UI 设计规范
|
||||
|
||||
> 本文档面向 UI 设计师,用于独立理解项目全貌并完成视觉还原。
|
||||
> 配套文件:`PRD.md`(产品需求)、HTML 原型(可交互参考)
|
||||
> 创建时间:2026-06-13
|
||||
|
||||
---
|
||||
|
||||
## 一、项目背景
|
||||
|
||||
### 1.1 产品定位
|
||||
|
||||
毛菜验收秤是「蚁熊智能餐养平台」中 **食堂毛菜入库流程** 的核心硬件终端,部署在仓库收货区。
|
||||
|
||||
### 1.2 上下游模块关系
|
||||
|
||||
```
|
||||
根导航(index.html)
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
毛菜验收秤 档口营养秤 餐厅终端
|
||||
(本模块) (其他团队) (其他团队)
|
||||
│
|
||||
┌────┴────┐
|
||||
│ 本模块页面 │
|
||||
│ 00 ~ 05 │
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
- **根导航**:设备启动后进入的总入口,列出该设备上所有可用应用
|
||||
- **毛菜验收秤**:本模块,负责食材验收、采样、记录查询
|
||||
- **后台系统**:订单数据来源于「绿色种采-采供管理」,验收结果回传至库存系统
|
||||
|
||||
### 1.3 核心用户
|
||||
|
||||
| 角色 | 使用场景 |
|
||||
|------|----------|
|
||||
| 仓库管理员 | 收货验收、称重、不合格处置 |
|
||||
| 食堂主管 | 查看验收记录、审批 |
|
||||
| 质检员 | 食材采样、品级判定 |
|
||||
|
||||
---
|
||||
|
||||
## 二、设备形态
|
||||
|
||||
### 2.1 硬件参数
|
||||
|
||||
| 项目 | 规格 |
|
||||
|------|------|
|
||||
| 屏幕尺寸 | 工业触摸屏一体机 |
|
||||
| 分辨率 | **1280 × 720**(固定,不可缩放) |
|
||||
| 输入方式 | 触控(操作者可能戴手套,触控目标 ≥ 36px) |
|
||||
| 外设 | 称重传感器、食材识别摄像头、人脸识别摄像头、场景监控摄像头 |
|
||||
| 运行环境 | 内置 Android/Windows 终端,浏览器全屏运行 |
|
||||
|
||||
### 2.2 使用场景
|
||||
|
||||
- **环境**:仓库收货区,光线可能偏暗 → 深色主题
|
||||
- **姿态**:操作者站立,单手操作触摸屏,另一手搬运食材
|
||||
- **操作节奏**:快速、连续 → 减少弹窗层级,关键操作一键完成
|
||||
- **干扰**:手湿/戴手套 → 按钮面积充足,间距宽松
|
||||
|
||||
### 2.3 设计约束
|
||||
|
||||
| 约束 | 说明 |
|
||||
|------|------|
|
||||
| 固定视口 | 1280×720,`overflow: hidden`,整页无滚动(局部区域可滚动) |
|
||||
| 深色主题 | 唯一主题,无亮色模式 |
|
||||
| 自包含 | 每个 HTML 文件独立,不引用外部 CSS/JS/字体/图片 |
|
||||
| 墨刀兼容 | 禁止外部引用、简洁 DOM、内联 SVG |
|
||||
| 字体 | 系统字体栈,不可使用 Web Font |
|
||||
|
||||
---
|
||||
|
||||
## 三、设计约束补充
|
||||
|
||||
### 3.1 交互限制
|
||||
|
||||
- 所有可点击元素最小尺寸 **36 × 36px**
|
||||
- 弹窗最多 **2 层**(主弹窗 + 确认弹窗),禁止嵌套更深
|
||||
- 同一时刻只显示 **1 个 Toast**,新 Toast 覆盖旧 Toast
|
||||
- 全屏遮罩弹窗(z-index ≥ 200)出现后,底层按钮不可点击
|
||||
|
||||
### 3.2 技术约束
|
||||
|
||||
| 项 | 值 |
|
||||
|------|------|
|
||||
| 视口 | `<meta name="viewport" content="width=1280, initial-scale=1.0">` |
|
||||
| body | `width: 1280px; height: 720px; overflow: hidden;` |
|
||||
| 动画 | 仅用 CSS transition/animation,时长 ≤ 400ms |
|
||||
| 滚动条 | 自定义细滚动条(宽 3~6px,半透明) |
|
||||
|
||||
---
|
||||
|
||||
## 四、视觉令牌
|
||||
|
||||
### 4.1 色板
|
||||
|
||||
#### 背景色
|
||||
|
||||
| 令牌 | 色值 | 用途 |
|
||||
|------|------|------|
|
||||
| `--bg-page` | `#0e1326` | 页面底色(最深) |
|
||||
| `--bg-card` | `#1a2b4a` | 卡片、面板、顶栏 |
|
||||
| `--bg-input` | `#121832` | 输入框、选择器内部 |
|
||||
| `--bg-camera` | `#0D1117` | 摄像头预览区 |
|
||||
|
||||
#### 主色 & 语义色
|
||||
|
||||
| 令牌 | 色值 | 用途 |
|
||||
|------|------|------|
|
||||
| `--color-primary` | `#4a90d9` | 主操作、链接、高亮、头像背景 |
|
||||
| `--color-success` | `#52c41a` | 通过、在线、完成、正向数值 |
|
||||
| `--color-warning` | `#faad14` | 警告、后置付款标签 |
|
||||
| `--color-error` | `#ff4d4f` | 不通过、离线、删除、错误 |
|
||||
| `--color-danger-hover` | `#ff6b6d` | 退出按钮 hover 文字色 |
|
||||
|
||||
#### 文字色
|
||||
|
||||
| 令牌 | 色值 | 用途 |
|
||||
|------|------|------|
|
||||
| `--text-title` | `rgba(255,255,255,0.85)` | 页面标题、卡片标题 |
|
||||
| `--text-primary` | `#ffffff` | 正文、按钮文字 |
|
||||
| `--text-secondary` | `rgba(255,255,255,0.7)` | 次要文字、返回按钮 |
|
||||
| `--text-tertiary` | `rgba(255,255,255,0.5)` | 辅助说明、用户信息 |
|
||||
| `--text-placeholder` | `rgba(255,255,255,0.2)` | 输入框占位符 |
|
||||
| `--text-disabled` | `rgba(255,255,255,0.25)` | 禁用状态文字 |
|
||||
|
||||
#### 边框色
|
||||
|
||||
| 令牌 | 色值 | 用途 |
|
||||
|------|------|------|
|
||||
| `--border-default` | `rgba(255,255,255,0.06~0.1)` | 卡片、输入框默认边框 |
|
||||
| `--border-focus` | `#4a90d9` | 输入框聚焦 |
|
||||
| `--border-primary` | `rgba(74,144,217,0.25)` | 主色按钮边框 |
|
||||
| `--border-danger` | `rgba(255,77,79,0.2)` | 危险按钮边框 |
|
||||
|
||||
#### 透明度用法
|
||||
|
||||
```
|
||||
0.85 标题文字
|
||||
0.7 次要文字 / hover 状态
|
||||
0.5 辅助说明
|
||||
0.4 标签文字 / 提示
|
||||
0.2 占位符 / 禁用
|
||||
0.1 边框 / 分割线
|
||||
0.06 卡片边框(最弱)
|
||||
0.04 背景叠加(统计卡片)
|
||||
```
|
||||
|
||||
### 4.2 字号层级
|
||||
|
||||
| 级别 | 大小 | 字重 | 用途 |
|
||||
|------|------|------|------|
|
||||
| H1 | 18px | 700 | 弹窗标题 |
|
||||
| H2 | 16px | 600 | 区域标题、步骤标题 |
|
||||
| H3 | 15px | 600 | 顶栏页面标题 |
|
||||
| BODY | 14px | 400~500 | 正文、按钮文字、返回按钮 |
|
||||
| CAPTION | 13px | 400~500 | 输入框文字、次按钮 |
|
||||
| SMALL | 12px | 400~600 | 辅助说明、标签、统计数字、状态 |
|
||||
| MINI | 11px | 400~500 | 字段标签、进度文字、极小注释 |
|
||||
|
||||
> 字体栈:`-apple-system, "Microsoft YaHei", "PingFang SC", sans-serif`
|
||||
|
||||
### 4.3 圆角
|
||||
|
||||
| 令牌 | 值 | 用途 |
|
||||
|------|------|------|
|
||||
| `--radius-xs` | 2px | 滚动条滑块 |
|
||||
| `--radius-sm` | 4~5px | 退出按钮、下拉选择器 |
|
||||
| `--radius-md` | 6~8px | 输入框、按钮、标签、进度条、状态栏 |
|
||||
| `--radius-lg` | 10~12px | 卡片、摄像头框 |
|
||||
| `--radius-xl` | 14~16px | 弹窗 |
|
||||
| `--radius-full` | 50% | 头像、状态圆点 |
|
||||
|
||||
### 4.4 间距系统
|
||||
|
||||
| 级别 | 值 | 用途 |
|
||||
|------|------|------|
|
||||
| xs | 4px | 图标与文字间距、标签内间距 |
|
||||
| sm | 8px | 紧凑元素间距、候选卡片间距 |
|
||||
| md | 12~16px | 表单字段间距、面板内间距 |
|
||||
| lg | 20~24px | 区域间距、弹窗内间距 |
|
||||
| xl | 28~32px | 页面边距、大面板内间距 |
|
||||
|
||||
### 4.5 阴影 & 发光
|
||||
|
||||
| 效果 | CSS | 用途 |
|
||||
|------|------|------|
|
||||
| 状态点发光 | `box-shadow: 0 0 6px #52c41a` | 就绪/在线状态圆点 |
|
||||
| 弹窗阴影 | `box-shadow: 0 20px 60px rgba(0,0,0,0.5)` | 详情弹窗 |
|
||||
| 按钮 hover | `box-shadow: 0 4px 14px rgba(74,144,217,0.3)` | 主按钮悬停 |
|
||||
| 遮罩层 | `background: rgba(0,0,0,0.7)` | 弹窗背景遮罩 |
|
||||
|
||||
---
|
||||
|
||||
## 五、通用组件
|
||||
|
||||
### 5.1 按钮
|
||||
|
||||
#### 主操作按钮
|
||||
|
||||
```
|
||||
背景:#4a90d9 文字:#fff 圆角:8px 字号:13~15px 字重:600
|
||||
hover:背景 #3a7bc8 + 阴影
|
||||
active:scale(0.98)
|
||||
disabled:背景 rgba(255,255,255,0.08) 文字 rgba(255,255,255,0.25)
|
||||
```
|
||||
|
||||
#### 次要按钮
|
||||
|
||||
```
|
||||
背景:rgba(255,255,255,0.06) 文字:rgba(255,255,255,0.5~0.7)
|
||||
边框:1px solid rgba(255,255,255,0.08~0.1) 圆角:8px
|
||||
hover:背景 rgba(255,255,255,0.1) 文字 #fff
|
||||
```
|
||||
|
||||
#### 危险按钮(退出)
|
||||
|
||||
```
|
||||
背景:rgba(255,77,79,0.08) 文字:rgba(255,77,79,0.7)
|
||||
边框:1px solid rgba(255,77,79,0.2) 圆角:5px 字号:11px
|
||||
hover:背景 rgba(255,77,79,0.18) 文字 #ff6b6d 边框 rgba(255,77,79,0.4)
|
||||
```
|
||||
|
||||
#### 文字按钮(链接样式)
|
||||
|
||||
```
|
||||
无背景无边框 文字:#4a90d9 字号:12~13px
|
||||
hover:文字加亮 / 下划线
|
||||
```
|
||||
|
||||
### 5.2 标签(Tag)
|
||||
|
||||
```
|
||||
基础:padding 2px 8px border-radius 8px font-size 11px
|
||||
|
||||
绿色(未验收/通过/前置/在线):背景 rgba(82,196,26,0.15) 文字 #52c41a
|
||||
橙色(部分验收/警告/后置):背景 rgba(250,173,22,0.15) 文字 #faad14
|
||||
红色(不通过/离线):背景 rgba(255,77,79,0.15) 文字 #ff4d4f
|
||||
蓝色(待验收):背景 rgba(74,144,217,0.15) 文字 #4a90d9
|
||||
```
|
||||
|
||||
### 5.3 Toast 提示
|
||||
|
||||
```
|
||||
位置:fixed 顶部 60px 水平居中
|
||||
背景:rgba(0,0,0,0.8) 文字:#fff 圆角:8px
|
||||
padding:10~12px 24~28px font-size:13px
|
||||
最大宽度:90% 单行不换行
|
||||
|
||||
入场:translateY(-20px) → translateY(0) duration 300ms
|
||||
出场:opacity 1 → 0 duration 300ms
|
||||
|
||||
类型变体:
|
||||
success → 背景 rgba(82,196,26,0.9)
|
||||
warn → 背景 rgba(250,173,22,0.92)
|
||||
error → 背景 rgba(255,77,79,0.9)
|
||||
默认 → 背景 rgba(0,0,0,0.8)
|
||||
```
|
||||
|
||||
### 5.4 弹窗(Modal / Dialog)
|
||||
|
||||
```
|
||||
遮罩:position fixed inset 0 background rgba(0,0,0,0.7) z-index 200~250
|
||||
面板:background #1a2b4a border-radius 14~16px padding 24~28px
|
||||
border 1px solid rgba(255,255,255,0.08)
|
||||
box-shadow 0 20px 60px rgba(0,0,0,0.5)
|
||||
宽度:480~520px(常规弹窗) / 700px(详情弹窗)
|
||||
最大高度:80~90vh,超出区域内部滚动
|
||||
|
||||
入场:opacity 0→1 + translateY(-20px)→0 duration 300ms
|
||||
```
|
||||
|
||||
### 5.5 输入框
|
||||
|
||||
```
|
||||
背景:#121832 边框:1px solid rgba(255,255,255,0.1) 圆角:6~8px
|
||||
文字:#fff 13~14px 占位符:rgba(255,255,255,0.2)
|
||||
高度:34~42px padding:0 10~14px
|
||||
|
||||
focus:border-color #4a90d9
|
||||
disabled:背景 rgba(255,255,255,0.04) 文字 rgba(255,255,255,0.25)
|
||||
```
|
||||
|
||||
### 5.6 下拉选择器(Select)
|
||||
|
||||
```
|
||||
外观:appearance auto(使用系统原生下拉箭头)
|
||||
背景:rgba(255,255,255,0.05) 边框:1px solid rgba(255,255,255,0.1)
|
||||
圆角:4px padding:2px 4px 字号:11px 文字居中
|
||||
focus:border-color #4a90d9 背景 rgba(74,144,217,0.08)
|
||||
option 背景:#1a2b4a 文字:#fff
|
||||
```
|
||||
|
||||
### 5.7 表格
|
||||
|
||||
```
|
||||
表头:background rgba(255,255,255,0.04) 字号 12px 字重 600
|
||||
文字 rgba(255,255,255,0.6)
|
||||
表体行:background transparent 字号 13px 文字 rgba(255,255,255,0.8)
|
||||
hover:background rgba(74,144,217,0.06)
|
||||
行高:36~40px
|
||||
分割线:border-bottom 1px solid rgba(255,255,255,0.06)
|
||||
```
|
||||
|
||||
### 5.8 卡片
|
||||
|
||||
```
|
||||
背景:#1a2b4a 圆角:12~14px
|
||||
边框:1px solid rgba(255,255,255,0.06)
|
||||
padding:16~24px
|
||||
hover(可点击卡片):border-color rgba(74,144,217,0.3)
|
||||
background rgba(74,144,217,0.06)
|
||||
transform translateY(-2px)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、页面逐一说明
|
||||
|
||||
### 6.1 欢迎页(00-welcome.html)
|
||||
|
||||
**功能目标**:设备启动后的身份验证入口,登录成功后解锁功能菜单。
|
||||
|
||||
**布局**:左右两栏
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← 返回(回根导航) 毛菜验收秤 │ 44px
|
||||
├──────────────────┬─────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ 左侧(420px) │ 右侧(flex:1) │
|
||||
│ │ │
|
||||
│ ┌──────────┐ │ [功能菜单] │
|
||||
│ │ 人脸识别 │ │ ┌───────────────────────────┐ │
|
||||
│ │ 圆形区域 │ │ │ ⚖️ 验收食材 │ │
|
||||
│ │ 220×220 │ │ │ 订单选择、称重验收... │ │
|
||||
│ │ 虚线边框 │ │ └───────────────────────────┘ │
|
||||
│ └──────────┘ │ ┌───────────────────────────┐ │
|
||||
│ 识别状态信息 │ │ 📝 验收记录 │ │
|
||||
│ ── 或 ── │ └───────────────────────────┘ │
|
||||
│ 账号密码表单 │ ...更多菜单按钮 │
|
||||
│ │ │
|
||||
└──────────────────┴─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**交互状态**:
|
||||
|
||||
| 状态 | 视觉变化 |
|
||||
|------|----------|
|
||||
| 未登录 | 菜单按钮 `.disabled`(opacity 0.4,不可点击) |
|
||||
| 识别中 | 圆形区域边框变实线 + 脉冲动画,图标变 🔍 |
|
||||
| 识别成功 | 边框变绿色,图标变 ✅,显示姓名+工号,菜单解锁 |
|
||||
| 权限不足 | 点击菜单 → Toast 黄色警告 |
|
||||
|
||||
**无退出按钮**(用户尚未登录)。
|
||||
|
||||
---
|
||||
|
||||
### 6.2 验收食材(01-verify-inspection.html)
|
||||
|
||||
**功能目标**:核心业务页面,分两步完成订单选择 → 食材称重验收。
|
||||
|
||||
#### Step 1:选择订单
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← | 食材验收识别屏 | 头像 张三 | [退出] | [无订单验收] │ 52px
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ 选择验收订单 提示文字 │
|
||||
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ │订单卡1│ │订单卡2│ │订单卡3│ │订单卡4│ ← 横向滑动 │
|
||||
│ │供应商 │ │供应商 │ │供应商 │ │供应商 │ │
|
||||
│ │PO编号 │ │PO编号 │ │PO编号 │ │PO编号 │ │
|
||||
│ │13种食材│ │食材标签│ │食材标签│ │食材标签│ │
|
||||
│ │采购/到货│ │采购/到货│ │采购/到货│ │采购/到货│ │
|
||||
│ │配送方式│ │配送方式│ │配送方式│ │配送方式│ │
|
||||
│ │[前置] │ │[后置] │ │[前置] │ │[前置] │ │
|
||||
│ └──────┘ └──────┘ └──────┘ └──────┘ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**订单卡片尺寸**:约 240×260px,间距 16px
|
||||
|
||||
**卡片字段布局**:
|
||||
|
||||
| 区域 | 字段 | 样式 |
|
||||
|------|------|------|
|
||||
| 顶部 | 供应商名称 | 14px 600 |
|
||||
| 顶部 | 订单编号 | 12px rgba(0.45) |
|
||||
| 中间 | 食材种类数 + 标签 | 合并显示,过多时「+N项」可点击 |
|
||||
| 日期区 | 采购日期 / 预计到货 | 虚线框内左右排列,到货日期蓝色 |
|
||||
| 底部 | 验收状态标签 | 未验收(绿) / 部分验收(橙)(~~原发货状态~~,验收与发货无关,仅判断是否全部验收通过) |
|
||||
| ~~底部 | 付款类型 | 前置(绿tag) / 后置(橙tag)~~ **暂时隐藏,后期恢复** |
|
||||
| CTA | 进入验收 | 整张卡片可点击 |
|
||||
|
||||
#### Step 2:食材验收(两列布局)
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← | 食材验收识别屏 | 头像 张三 | [退出] │ 52px
|
||||
├───────────────┬────────────────────────────────────────┤
|
||||
│ 左列(440px) │ 右列(flex:1) │
|
||||
│ │ │
|
||||
│ [摄像头画面] │ ┌────────────────────────────────────┐ │
|
||||
│ 200px高 │ │ 订单信息卡片 │ │
|
||||
│ 十字准线 │ │ 入库单号 | 订单号 | 供应商 | 发货人 │ │
|
||||
│ 四角蓝色标记 │ │ 来源类型 | 预计到货 | 食材数量 | 金额 │ │
|
||||
│ │ └────────────────────────────────────┘ │
|
||||
│ [识别状态栏] │ ┌────────────────────────────────────┐ │
|
||||
│ ● 就绪/称重中 │ │ 食材列表(8列表格) │ │
|
||||
│ │ │ 食材|采购规格|采购量|单价|应付|实收规格 │ │
|
||||
│ [🔄 重新识别] │ │ |实收量|操作 │ │
|
||||
│ │ └────────────────────────────────────┘ │
|
||||
│ [进度条] │ │
|
||||
│ ████░░ 3/13 │ [↻ 称重清零(重新放置)] │
|
||||
│ │ │
|
||||
│ [候选食材2×N] │ [提交验收结果] [提交并继续] │
|
||||
│ ┌────┐ ┌────┐ │ │
|
||||
│ │🥬 │ │🥕 │ │ │
|
||||
│ │菠菜│ │胡萝卜│ │ │
|
||||
│ └────┘ └────┘ │ │
|
||||
└───────────────┴────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**食材列表 8 列宽度**:`1fr 56px 50px 56px 58px 50px 50px 52px`
|
||||
|
||||
**操作列按钮**:
|
||||
|
||||
| 按钮 | 样式 | 显示条件 |
|
||||
|------|------|----------|
|
||||
| 🗑 删除 | 文字按钮,红色 | 有订单:有记录时可用;无订单:始终可用 |
|
||||
| ~~✗ 不通过~~ | ~~文字按钮,红色~~ | **暂时隐藏,后期恢复**(原有订单:有记录时可用;无订单:**不显示**) |
|
||||
|
||||
---
|
||||
|
||||
### 6.3 验收记录(03-verify-history.html)
|
||||
|
||||
**功能目标**:历史数据查询,支持多维度筛选。
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← 返回 | 验收记录 | [退出] │ 44px
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ [日视图|本周] | [全部|通过|不通过] | 日期条(横滑) | 🔍 | [食材|订单] │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ 📋 3 今日验收总数 | ✅ 2 验收通过 | ❌ 1 不通过 | 📊 66% │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ 入库单号 | 订单号 | 食材/数量 | 供应商 | 支付方式 │
|
||||
│ | 配送方式 | 时间 | 验收结果 | 操作 │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ 共 8 条记录 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**维度切换**:
|
||||
|
||||
| 维度 | 差异列 | 结果展示 |
|
||||
|------|--------|----------|
|
||||
| 食材维度 | 食材名称 | 通过(绿) / 不通过(红) / 待验收(蓝) |
|
||||
| 订单维度 | 食材数量(N种) | 验收完成(绿) / 待验收(橙) |
|
||||
|
||||
**日期条**:7 天横滑,今天高亮,点击切换
|
||||
|
||||
---
|
||||
|
||||
### 6.4 食材采样(04-ingredient-sampling.html)
|
||||
|
||||
**功能目标**:验收前采集食材样本数据。
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← 返回 | 食材采样 📝 采样历史 | [退出] │ 44px
|
||||
├─────────────────┬──────────────────────────────────────┤
|
||||
│ 左侧(400px) │ 右侧(flex:1) │
|
||||
│ │ │
|
||||
│ 搜索框 │ [摄像头预览区] │
|
||||
│ [全部][叶菜]... │ 拍照采集按钮 │
|
||||
│ 食材列表 │ │
|
||||
│ · 菠菜 > │ 采集结果(最多9张) │
|
||||
│ · 大白菜 > │ ┌──┐ ┌──┐ ┌──┐ │
|
||||
│ · 圆白菜 > │ │图1│ │图2│ │图3│ │
|
||||
│ │ └──┘ └──┘ └──┘ │
|
||||
│ │ │
|
||||
│ │ [取消] [↻ 重新采样] [保存采集] [保存并继续]│
|
||||
└─────────────────┴──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.5 采样历史(05-sampling-history.html)
|
||||
|
||||
**功能目标**:食材样本库,独立于验收流程。
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← 返回 | 采样历史 | [退出] │ 44px
|
||||
├───────────┬────────────────────────────────────────────┤
|
||||
│ 快捷查找 │ 瀑布流卡片(3列) │
|
||||
│ 搜索框 │ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ [全部] │ │🥬菠菜 │ │🥕土豆 │ │🥩猪排 │ │
|
||||
│ [叶菜类] │ │5张 │ │3张 │ │4张 │ │
|
||||
│ [花菜类] │ └──────┘ └──────┘ └──────┘ │
|
||||
│ [瓜果类] │ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ ... │ │... │ │... │ │... │ │
|
||||
│ 共12条记录 │ └──────┘ └──────┘ └──────┘ │
|
||||
└───────────┴────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
点击卡片 → 详情弹窗(采样备注、图片网格、补充采集按钮)
|
||||
|
||||
---
|
||||
|
||||
### 6.6 设置(02-settings.html)
|
||||
|
||||
**功能目标**:设备参数配置。
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ ← | 设置 | [退出] │ 44px
|
||||
├──────────────────────────┬─────────────────────────────┤
|
||||
│ ⚖️ 验收标准 │ 💻 设备状态 │
|
||||
│ │ │
|
||||
│ 偏差率阈值 │ 人脸识别摄像头 ● 已连接 │
|
||||
│ [ 5 ] % │ 食材识别摄像头 ● 已连接 │
|
||||
│ │ 称重传感器 ● 已连接 │
|
||||
│ [保存设置] │ 触摸屏 ● 正常 │
|
||||
│ │ 网络连接 ● 已连接 │
|
||||
│ │ │
|
||||
│ │ [← 返回首页] │
|
||||
└──────────────────────────┴─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、异常与边界
|
||||
|
||||
### 7.1 硬件异常
|
||||
|
||||
| 异常 | 表现 | UI 处理 |
|
||||
|------|------|---------|
|
||||
| 称重传感器断连 | 重量始终为 0 | 识别状态栏显示红色提示「称重传感器未连接」 |
|
||||
| 摄像头遮挡 | 无法识别食材 | 摄像头区域显示「摄像头被遮挡,请清理」 |
|
||||
| 人脸识别失败 | 3 秒内未识别到人脸 | 提示「未检测到人脸,请正对摄像头」 |
|
||||
| 网络断开 | 无法拉取订单 | Toast「网络连接已断开」,订单列表为空 |
|
||||
|
||||
### 7.2 数据边界
|
||||
|
||||
| 场景 | UI 处理 |
|
||||
|------|---------|
|
||||
| 订单列表为空 | 居中显示空状态文字「暂无待验收订单」 |
|
||||
| 食材全部验收完成 | 进度条满,状态变「全部食材验收完成,请提交结果」,提交按钮激活 |
|
||||
| 食材名称超长 | 单行截断,`text-overflow: ellipsis` |
|
||||
| 数值极大 | 金额保留 2 位小数,重量保留 1 位小数 |
|
||||
| 候选食材为 0 | 不显示候选区域,提示「未识别到食材,请手动选择」 |
|
||||
| 采样图片达上限(9张) | 拍照按钮禁用,隐藏补充采集按钮 |
|
||||
| 表格数据为空 | 居中显示「暂无数据」,底栏显示「共 0 条记录」 |
|
||||
|
||||
### 7.3 操作边界
|
||||
|
||||
| 场景 | UI 处理 |
|
||||
|------|---------|
|
||||
| Step2 中点击返回 | 有验收数据 → 弹出确认弹窗「验收数据将丢失,确认返回?」;无数据 → 直接返回 |
|
||||
| 未登录点击菜单 | Toast 黄色警告「您无权使用该设备,请联系管理员添加权限」 |
|
||||
| 未选食材点击保存 | Toast「请先选择一个食材」 |
|
||||
| 重复提交 | 提交后按钮立即 disabled,防止重复点击 |
|
||||
| 无订单模式删除食材 | Toast「已删除「XXX」」;有订单模式删除 → Toast「已清空「XXX」验收记录」 |
|
||||
|
||||
---
|
||||
|
||||
## 八、原型访问指南
|
||||
|
||||
### 8.1 本地打开原型
|
||||
|
||||
原型为独立 HTML 文件,直接在浏览器中打开即可:
|
||||
|
||||
```
|
||||
文件路径:other-module/canteen-device/raw-veg-inspection-scale/
|
||||
├── 00-welcome.html ← 从这里开始
|
||||
├── 01-verify-inspection.html
|
||||
├── 02-settings.html
|
||||
├── 03-verify-history.html
|
||||
├── 04-ingredient-sampling.html
|
||||
├── 05-sampling-history.html
|
||||
├── index.html ← 模块导航页
|
||||
└── doc/
|
||||
├── PRD.md ← 产品需求文档
|
||||
├── UI-SPEC.md ← 本文档
|
||||
└── CHANGELOG.md ← 变更记录
|
||||
```
|
||||
|
||||
### 8.2 页面跳转路径
|
||||
|
||||
**推荐浏览顺序**:
|
||||
|
||||
```
|
||||
00-welcome.html(登录)
|
||||
→ 点击「验收食材」
|
||||
→ 01 Step1(选订单)
|
||||
→ 点击订单卡片
|
||||
→ 01 Step2(验收)
|
||||
→ 全部完成后弹窗 → 提交
|
||||
→ 返回点击「验收记录」
|
||||
→ 03(切换食材/订单维度)
|
||||
→ 返回点击「食材采样」
|
||||
→ 04(选食材 → 拍照 → 保存)
|
||||
→ 返回点击「采样历史」
|
||||
→ 05(瀑布流 → 点击卡片看详情)
|
||||
→ 返回点击「设置」
|
||||
→ 02(调整阈值)
|
||||
```
|
||||
|
||||
### 8.3 演示账号
|
||||
|
||||
| 账号 | 密码 | 角色 |
|
||||
|------|------|------|
|
||||
| zhangsan | 123456 | 仓库管理员 |
|
||||
| lisi | 123456 | 食堂主管 |
|
||||
| wangwu | 123456 | 质检员 |
|
||||
|
||||
也支持姓名、工号、手机号登录。或直接点击人脸识别区域随机登录。
|
||||
@@ -0,0 +1,504 @@
|
||||
# 毛菜验收秤 — 系统流程与功能边界
|
||||
|
||||
> 创建时间:2026-06-11 | 最后更新:2026-06-12(候选卡片改造 + 无订单验收)
|
||||
|
||||
---
|
||||
|
||||
## 一、硬件配置
|
||||
|
||||
| 组件 | 用途 |
|
||||
|---|---|
|
||||
| 触摸屏(尺寸待定) | 交互操作、信息确认、不合格处理 |
|
||||
| 称重传感器 + 秤盘 | 自动读取重量(kg) |
|
||||
| 食材识别摄像头 | AI 模型自动识别食材品种 |
|
||||
| 人脸识别摄像头 | 操作人身份验证 |
|
||||
| 场景监控摄像头 | 后台录像,全程静默监控,不参与前端交互 |
|
||||
|
||||
> 无扫码器、无工牌读取器。设备共三个摄像头,各司其职。
|
||||
|
||||
---
|
||||
|
||||
## 一.1、人脸验证策略(关键逻辑)
|
||||
|
||||
### 验证原则
|
||||
|
||||
**不持续监控人脸,仅在关键节点进行身份校验。**
|
||||
|
||||
### 验证时机
|
||||
|
||||
| 时机 | 校验方式 | 本人 → | 换人 → |
|
||||
|---|---|---|---|
|
||||
| 欢迎页进入系统 | 人脸识别摄像头激活 | 进入功能菜单 | 无权使用,toast 提示 |
|
||||
| 切换功能页面 | 从 A 功能页面点击跳转到 B 功能页面时触发 | 携带身份参数跳转 | 跳回 00 欢迎页重新验证 |
|
||||
| 提交确认操作 | 点击「确认入库」等提交按钮时触发 | 正常提交 | toast 提示"请本人操作",阻止提交 |
|
||||
|
||||
### 身份校验流程
|
||||
|
||||
```
|
||||
用户触发跳转/提交
|
||||
↓
|
||||
调用摄像头捕获当前人脸
|
||||
↓
|
||||
与进入系统时的身份对比
|
||||
├─ 同一人 → 放行(继续操作)
|
||||
└─ 不同人 → 拦截
|
||||
├─ 跳转场景 → 跳回 00 欢迎页
|
||||
└─ 提交场景 → toast 提示"请本人操作"
|
||||
```
|
||||
|
||||
### 摄像头职责分工
|
||||
|
||||
| 摄像头 | 显示位置 | 用途 | 是否交互 |
|
||||
|---|---|---|---|
|
||||
| 食材识别摄像头 | 验收/采样页面左列识别区 | AI 识别食材品种 | 是,展示识别结果可修正 |
|
||||
| 人脸识别摄像头 | 仅在验证时调用,不常驻显示 | 关键节点身份校验 | 是,校验时短暂激活 |
|
||||
| 场景监控摄像头 | 前端不展示,后台静默运行 | 全过程录像,记录所有操作行为 | 否,不参与前端交互 |
|
||||
|
||||
### 页面摄像头展示
|
||||
|
||||
- **00 欢迎页**:仅展示人脸识别摄像头画面
|
||||
- **01 验收食材 / 04 食材采样**:左列展示食材识别画面(AI 识别中 → 显示识别结果)
|
||||
- **场景监控摄像头不显示在页面上**,后台静默录制全程
|
||||
|
||||
### 操作行为记录(后台)
|
||||
|
||||
所有操作行为均存储至后台,记录内容包括:
|
||||
|
||||
| 记录内容 | 说明 |
|
||||
|---|---|
|
||||
| 操作人信息 | 姓名、工号、部门、职位 |
|
||||
| 操作时间 | 每次操作的时间戳 |
|
||||
| 操作类型 | 验收、采样、设置修改、提交等 |
|
||||
| 操作数据 | 订单信息、食材名称、重量、验收结果、处置策略等 |
|
||||
| 视频内容 | 场景监控摄像头全程录像,按操作时间索引关联 |
|
||||
|
||||
---
|
||||
|
||||
## 二、食材采样流程(验收前置环节)
|
||||
|
||||
> 采样是验收的前置环节,用于在正式验收前采集食材样本数据,为后续验收提供对比基准。
|
||||
> 从欢迎页点击「食材采样」进入(需先完成人脸识别)。
|
||||
|
||||
```
|
||||
Step S1: 从欢迎页点击「食材采样」进入(需先完成人脸识别)
|
||||
↓
|
||||
Step S2: 触摸屏展示该操作人关联的待采样订单列表 → 采样人选择目标订单
|
||||
↓
|
||||
Step S3: 食材放上秤盘 → 秤体自动称重
|
||||
↓
|
||||
同时食材识别摄像头自动识别食材品种
|
||||
↓
|
||||
Step S4: 系统自动匹配/生成溯源码 + 拉取订单配送信息
|
||||
↓
|
||||
Step S5: 外观品级判定:
|
||||
- 采样人选择外观品级(合格 / 不合格)
|
||||
- AI 识别结果展示(可手动修正)
|
||||
- 采样备注(可选填写)
|
||||
↓
|
||||
Step S6: 点击「保存采样」→ 记录采样数据
|
||||
- 采样编号(系统自动生成)
|
||||
- 采样时间、操作人、食材品种
|
||||
- 采样重量(kg)
|
||||
- 外观品级
|
||||
↓
|
||||
Step S7: 采样完成 → 可选操作:
|
||||
├─ 继续采样(返回 Step S2)
|
||||
└─ 进入验收流程 → 跳转至「三、验收流程」
|
||||
```
|
||||
|
||||
### 2.1 采样字段清单
|
||||
|
||||
| 字段 | 来源 | 说明 |
|
||||
|---|---|---|
|
||||
| 采样编号 | 系统自动生成 | 统一编号规则(SP-xxxx) |
|
||||
| 溯源码 | 系统自动 | 自产:唯一/随机匹配;外采:自动生成 |
|
||||
| 来源类型 | 系统自动 | 自产 / 外采 |
|
||||
| 供应商 | 系统自动 | 从订单获取 |
|
||||
| 食材名称 | AI识别(可手动修正) | 食材识别摄像头输出结果 |
|
||||
| 采样重量(kg) | 秤体传感器 | 核心产出 |
|
||||
| 采样时间 | 系统自动 | — |
|
||||
| 操作人 | 人脸识别自动 | — |
|
||||
| 外观品级 | 采样人选择 | 合格 / 不合格 |
|
||||
| 采样备注 | 采样人输入(可选) | 文字描述 |
|
||||
|
||||
---
|
||||
|
||||
## 三、验收食材流程
|
||||
|
||||
> 「验收食材」和「验收记录」为两个独立菜单。验收食材是验收操作流程,验收记录是历史数据查询。
|
||||
|
||||
### 流程概述
|
||||
|
||||
验收食材采用**分步向导**形式,在单页面内完成从选单到提交的全过程。
|
||||
|
||||
```
|
||||
Step 1: 选择订单
|
||||
卡片布局,按预计到货时间从近到远排列 → 横滑浏览 → 点击卡片直接进入验收
|
||||
页面右上角常驻「无订单验收」入口
|
||||
↓
|
||||
Step 2: 食材识别 + 验收判定(同一页面)
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 左列 │ 中列 │ 右列 │
|
||||
│ ·食材识别摄像头 │ ·订单信息(编号排第一) │ ·称重读数 │
|
||||
│ ·AI识别状态 │ ·当前验收食材展示 │ ·偏差信息 │
|
||||
│ ·识别结果(可修改) │ ·搜索框(常驻) │ ·验收结果 │
|
||||
│ │ ·候选食材卡片 3×2=6个 │ ·不合格处置 │
|
||||
│ │ (识别率88%~99%) │ ·操作按钮 │
|
||||
│ │ ·已验收食材列表(紧凑行) │ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
注:场景监控摄像头后台静默录制,页面不展示
|
||||
交互:
|
||||
· AI摄像头扫描 → 展示6个相似度最高候选食材(带识别率百分比)
|
||||
· 操作人点击卡片选中食材 → 自动开始称重
|
||||
· 搜索框支持手动搜索食材(回车触发,结果替换候选区)
|
||||
· 重量稳定 → 显示实重、偏差率、自动判定通过/不通过
|
||||
· 验收结果可手动修改(通过 ↔ 不通过)
|
||||
· 同一食材多次选中 → 重量累计增加
|
||||
· 称重完成 → 自动进入下一轮候选展示
|
||||
· ~~不合格 → 唤起处置策略面板(折叠式)~~ **(暂时隐藏,后期恢复)**
|
||||
· 重新识别 → 点击「🔄 重新识别」按钮可重置当前识别状态,清空候选列表并重新触发 AI 识别
|
||||
↓
|
||||
~~Step 3: 处置策略(仅不合格时展开,折叠面板形式)~~ **(暂时隐藏,后期恢复)**
|
||||
~~处理方式 × 处理范围 × 退款重量 × 不合格原因~~
|
||||
↓
|
||||
完成提交:
|
||||
├─ 提交并继续 → 保存后回到 Step 1 选下一个订单
|
||||
└─ 提交 → 保存后返回欢迎页
|
||||
```
|
||||
|
||||
### 核心规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| **称重逻辑** | 操作人从候选卡片中选中食材后触发称重,不自动重置 |
|
||||
| **同食材累计** | 同一食材可多次选中,重量累计增加 |
|
||||
| **候选识别** | AI 摄像头扫描后推送 6 个相似度最高候选食材(识别率 88%~99%),3×2 网格卡片展示 |
|
||||
| **手动搜索** | 搜索框常驻中列,输入关键词回车搜索食材库,结果替换候选区并标注"手动搜索" |
|
||||
| **AI 编辑** | 可手动修改识别结果,修改时自动采集食材图片作为凭证 |
|
||||
| **自动判定** | 称重稳定后对比偏差率阈值,自动判定通过/不通过,可手动修改 |
|
||||
| **无订单验收** | 弹窗选择供应商(外采限定),食材不在弹窗中添加;进入验收后 AI 识别到什么就添加什么,同名食材累加重量,验收结果默认通过需手动切换 |
|
||||
| **页面锁定** | 验收过程中禁止跳转其他功能,仅保留返回和提交入口 |
|
||||
| **提交前提** | 订单中所有食材必须已验收完毕或有处置策略,否则提交按钮禁用;无订单模式下食材列表非空即可提交 |
|
||||
| **删除** | 有订单模式:清空该食材验收记录回到未开始状态;无订单模式:从动态列表中彻底移除 |
|
||||
| **卡片选择** | Step 1 采用卡片横向滑动布局,3 列显示,点击卡片或「进入验收」按钮直接进入验收 |
|
||||
|
||||
### 订单-食材关系
|
||||
|
||||
一个订单包含多个食材(1对多)。订单选择后固定展示在页面顶部,食材按行显示验收状态:
|
||||
|
||||
| 状态 | 含义 |
|
||||
|---|---|
|
||||
| 未开始 | 该食材尚未放上称重 |
|
||||
| 部分完成 | 食材已称重但重量未达订单量 |
|
||||
| 已完成 | 重量累计完成,验收结果已确定 |
|
||||
|
||||
### 提交条件
|
||||
|
||||
所有食材必须满足以下条件之一才能提交:
|
||||
- 已验收通过(重量验收完成 + 偏差率在阈值内)
|
||||
- 已验收不通过(已填写处置策略)
|
||||
|
||||
存在「未开始」或「部分完成」状态的食材时,提交按钮禁用。
|
||||
|
||||
### Step 1 订单卡片布局
|
||||
|
||||
| 排序规则 | 说明 |
|
||||
|---|---|
|
||||
| 按预计到货时间 | 从近到远排列(最近到货的排最前) |
|
||||
|
||||
**卡片结构**(上下排列):
|
||||
1. 顶部:供应商名称 + 订单编号(前置第一位)
|
||||
2. 食材种类与具体名称(食材过多时点击标签弹窗展示)
|
||||
3. 采购日期 + 预计到货日期
|
||||
4. ~~发货状态(全量发货 / 部分发货)~~ → **验收状态(未验收(绿) / 部分验收(橙))**(验收与发货无关,始终只判断是否全部验收通过)+ 采购人
|
||||
5. 底部:「进入验收」按钮(或点击整张卡片)
|
||||
|
||||
**页面右上角**:常驻「无订单验收」按钮
|
||||
|
||||
**布局**:3 列卡片横向排列,支持横向滑动浏览。
|
||||
|
||||
### 验收页面布局
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ 顶部栏:返回 | 标题(验收食材) | 操作人 │
|
||||
├──────┬──────────────────────────────────┬─────────────────────────┤
|
||||
│ 左列 │ 中列 │ 右列 │
|
||||
│ │ │ │
|
||||
│ 食材 │ 订单卡片(编号·供应商·发货人·来源·到货)│ 实时重量 XX.XX kg │
|
||||
│ 识别 │ │ 称重稳定指示灯 │
|
||||
│ 摄像 │ 当前验收食材(选中后显示) │ │
|
||||
│ 头画 │ 🥬 菠菜 实重 286.00 kg │ 偏差信息 │
|
||||
│ 面 │ │ 订单重量·累计实重·偏差率 │
|
||||
│ │ 🔍 搜索食材名称... │ │
|
||||
│ [预 │ │ 验收结果切换 │
|
||||
│ 览] │ │ [✓通过] [~~✗不通过~~] │ ← ✗ 暂时隐藏,后期恢复
|
||||
│ │ │ │
|
||||
│ [🔄 │ 智能识别候选(3×2网格卡片) │ ~~▼不合格处置(折叠)~~ │ ← 暂时隐藏,后期恢复
|
||||
│ 重新 │ ┌─────┬─────┬─────┐ │ ~~·处理方式 ·范围~~ │
|
||||
│ 识别] │ │菠菜 │土豆 │白菜 │ │ ~~·退款重量 ·原因~~ │
|
||||
│ │ │96% │93% │91% │ │ │
|
||||
│ │ ├─────┼─────┼─────┤ │ [称重清零] │
|
||||
│ │ │胡萝卜│猪里脊│西兰花│ │ │
|
||||
│ │ │89% │88% │90% │ │ [提交并继续] [提交] │
|
||||
│ │ └─────┴─────┴─────┘ │ │
|
||||
│ │ ▼已验收食材(紧凑行) │ │
|
||||
│ │ 菠菜·286.00·✓完成·🗑 │ │
|
||||
│ │ 白菜·210.00·✓完成·🗑 │ │
|
||||
│ │ │ │
|
||||
│ │ 注:场景监控后台静默录制,不展示 │ │
|
||||
└──────┴──────────────────────────────────┴─────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 四、字段清单(秤端产出 → 对接毛菜入库)
|
||||
|
||||
### 4.1 系统自动字段
|
||||
|
||||
| 字段 | 来源 | 说明 |
|
||||
|---|---|---|
|
||||
| 入库单号 | 后台生成 | 统一编号规则 |
|
||||
| 溯源码 | 系统自动 | 自产:唯一/随机匹配;外采:自动生成 |
|
||||
| 来源类型 | 系统自动 | 自产 / 外采(= 外部供应商) |
|
||||
| 供应商 | 系统自动 | 从订单获取 |
|
||||
| 入库规格 | 系统从订单获取 | — |
|
||||
| 入库数量 | 系统从订单获取 | — |
|
||||
| 入库时间 | 系统自动 | — |
|
||||
| 操作人 | 人脸识别自动 | — |
|
||||
| 订单重量(kg) | 系统从订单获取 | 采购订单中的配送重量 |
|
||||
| 入库重量(kg) | 秤体传感器 | 核心产出 |
|
||||
| 订单偏差(kg) | 系统计算 | \|订单重量 - 实重\| |
|
||||
| 偏差率(%) | 系统计算 | 偏差 / 订单重量 × 100% |
|
||||
| 偏差判定 | 系统判定 | 正常 / 超差(对比阈值) |
|
||||
| 自动验收结果 | 系统判定 | 通过 / 不通过 |
|
||||
|
||||
### 4.2 验收人录入字段
|
||||
|
||||
| 字段 | 来源 | 说明 |
|
||||
|---|---|---|
|
||||
| 食材名称 | AI识别(可手动修正) | 食材识别摄像头输出结果;手动修改时自动采集食材图片凭证 |
|
||||
| 验收结果 | 系统自动 + 可手动修改 | 通过 / 不通过 |
|
||||
| 外观品级 | 验收人选择 | 合格 / 不合格 |
|
||||
| 食材图片 | 手动修改AI识别结果时自动采集 | 仅修改时拍摄,作为人工修正凭证 |
|
||||
| 处理方式 | 验收人选择(仅不合格时) | 退货退款 / 仅退款 / 换货 |
|
||||
| 处理范围 | 系统自动 + 可手动修改 | 部分 / 全部 |
|
||||
| 退款重量(kg) | 系统自动计算 | 订单重量 - 秤体重量(部分时) |
|
||||
| 不合格原因 | 验收人输入(仅不合格时) | 文字描述 |
|
||||
| 处理说明 | 验收人输入(仅不合格时) | 补充备注 |
|
||||
|
||||
### 4.3 采样历史字段(秤端本地存储)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| 采样编号 | 唯一标识(SP-xxxx) |
|
||||
| 采样时间 | 记录采样操作时间 |
|
||||
| 操作人 | 采样操作人姓名 |
|
||||
| 供应商 | 采样关联的供应商 |
|
||||
| 食材名称 | AI 识别结果 |
|
||||
| 采样重量(kg) | 采样时秤体重量 |
|
||||
| 外观品级 | 合格 / 不合格 |
|
||||
| 采样备注 | 可选文字备注 |
|
||||
|
||||
### 4.4 后台分配字段(不经过秤端)
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| 存放区域 | 由毛菜库存柜进行信息流转,秤端不管 |
|
||||
| 库存更新 | 入库成功后联动仓储管理 |
|
||||
| 验收记录查询/导出 | 历史数据管理 |
|
||||
| 采样记录查询/导出 | 历史采样数据管理 |
|
||||
| 异常预警 | 偏差超阈值自动告警 |
|
||||
|
||||
---
|
||||
|
||||
## 五、秤端 vs 后台职责划分
|
||||
|
||||
### 5.1 秤端(设备侧)
|
||||
|
||||
| 职责 | 实现 |
|
||||
|---|---|
|
||||
| 欢迎页人脸识别 | 摄像头 + 模型,进入系统时验证一次 |
|
||||
| 关键节点身份校验 | 切换页面/提交时调用摄像头校验,换人则拦截 |
|
||||
| 选择待采样订单 | 触摸屏列表选择 |
|
||||
| 选择待验收订单 | 触摸屏列表选择 |
|
||||
| 称重计量 | 传感器自动读取 |
|
||||
| AI识别食材品种 | 食材识别摄像头 + 模型,屏幕显示可修正 |
|
||||
| 场景监控录像 | 后台静默录制全程,记录操作人、操作时间、视频内容 |
|
||||
| 采样记录保存 | 一键保存采样数据 |
|
||||
| 自动判定验收结果 | 对比偏差率阈值 |
|
||||
| 验收结果手动修改 | 通过 ↔ 不通过可切换 |
|
||||
| 外观品级判定 | 二选一:合格 / 不合格 |
|
||||
| 不合格处置策略 | 三选一 + 部分/全部自动判定 |
|
||||
| 不合格原因录入 | 文字输入(仅不合格时) |
|
||||
| 确认入库 | 一键推送数据到后台 |
|
||||
|
||||
### 5.2 后台(毛菜入库模块)
|
||||
|
||||
| 职责 | 说明 |
|
||||
|---|---|
|
||||
| 订单数据拉取 | 从绿色种采-采供管理获取已填配送信息的订单 |
|
||||
| 溯源码分配 | 自产唯一/随机匹配,外采自动生成 |
|
||||
| 供应商类型映射 | 外部供应商 → 外采 |
|
||||
| 入库单号生成 | 统一编号规则 |
|
||||
| 偏差率阈值配置 | 设置中心可配置 |
|
||||
| 库存联动 | 入库成功后更新仓储库存(由毛菜库存柜流转) |
|
||||
| 采样记录管理 | 查询、导出、统计分析 |
|
||||
| 验收记录管理 | 查询、导出、统计分析 |
|
||||
|
||||
---
|
||||
|
||||
## 六、不合格处理方式明细
|
||||
|
||||
| 处理方式 | 处理范围 | 含义 |
|
||||
|---|---|---|
|
||||
| 退货退款 | 全部 | 全部退回供应商,全额退款 |
|
||||
| 退货退款 | 部分 | 不合格部分退回,按比例退款 |
|
||||
| 仅退款 | 全部 | 不退货,全额退款(供应商承担责任) |
|
||||
| 仅退款 | 部分 | 不退货,按不合格比例退款 |
|
||||
| 换货 | 全部 | 全部退回,供应商重新发货 |
|
||||
| 换货 | 部分 | 不合格部分退回,补发合格品 |
|
||||
|
||||
### 部分/全部自动判定规则
|
||||
|
||||
| 条件 | 自动判定 | 退款重量计算 |
|
||||
|---|---|---|
|
||||
| 秤体有重量(实重 > 0) | 部分 | 退款重量 = 订单重量 - 秤体重量 |
|
||||
| 秤体无重量(实重 = 0 或空秤) | 全部 | 退款重量 = 订单重量 |
|
||||
|
||||
> 验收人可手动修改处理范围(部分 ↔ 全部),修改后退款重量重新计算。
|
||||
|
||||
---
|
||||
|
||||
## 七、溯源码处理逻辑
|
||||
|
||||
| 场景 | 溯源码处理 |
|
||||
|---|---|
|
||||
| 内部供应(自产) | 系统根据菜品唯一匹配溯源码;同菜品多溯源码时随机匹配一个 |
|
||||
| 外部采买(外采) | 系统自动生成溯源码(遵循 `ST-EX-xxx` 编码规则) |
|
||||
|
||||
---
|
||||
|
||||
## 八、供应商来源类型映射
|
||||
|
||||
| 供应商类型 | 来源类型 |
|
||||
|---|---|
|
||||
| 外部供应商 | 外采 |
|
||||
| 内部基地/自产 | 自产 |
|
||||
|
||||
---
|
||||
|
||||
## 九、设置中心 — 偏差率阈值配置
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|---|---|
|
||||
| 偏差率阈值(%) | 默认值待定义,验收人可自行修改。重量偏差率 ≤ 阈值 → 自动判定验收通过;> 阈值 → 自动判定验收不通过 |
|
||||
| 配置位置 | 设置中心 |
|
||||
| 生效范围 | 全局生效,所有验收秤共用同一阈值 |
|
||||
|
||||
---
|
||||
|
||||
## 十、临时储位说明
|
||||
|
||||
秤端不负责存放区域分配。理论上存在**临时储位**概念,食材验收后暂存于某个临时位置,最终存储在哪里通过**毛菜库存柜**进行信息流转。秤端只需完成验收确认,后续储位分配由毛菜库存柜模块处理。
|
||||
|
||||
---
|
||||
|
||||
## 十一、页面映射关系
|
||||
|
||||
| 页面编号 | 文件名 | 页面名称 | 对应流程 |
|
||||
|---|---|---|---|
|
||||
| 00 | `00-welcome.html` | 欢迎页 | 人脸识别激活 + 菜单导航 |
|
||||
| 01 | `01-verify-inspection.html` | 验收食材 | 验收流程(Step 1~8) |
|
||||
| 02 | `02-settings.html` | 设置 | 偏差率阈值配置 |
|
||||
| 03 | `03-verify-history.html` | 验收记录 | 验收历史查询 |
|
||||
| 04 | `04-ingredient-sampling.html` | 食材采样 | 采样流程(Step S1~S7) |
|
||||
| 05 | `05-sampling-history.html` | 采样历史 | 采样记录查询 |
|
||||
|
||||
### 11.1 欢迎页流程
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────┐
|
||||
│ 00 欢迎页 │
|
||||
│ 左侧:人脸识别区域 │
|
||||
│ · 点击激活人脸识别 │
|
||||
│ · 识别中 / 识别成功(显示姓名+工号) │
|
||||
│ 右侧:功能按钮列(需先识别身份) │
|
||||
│ · 验收食材 → 01-verify-inspection │
|
||||
│ · 验收记录 → 03-verify-history │
|
||||
│ · 食材采样 → 04-ingredient-sampling │
|
||||
│ · 采样历史 → 05-sampling-history │
|
||||
│ · 设置 → 02-settings │
|
||||
└───────────────────────────────────────┘
|
||||
|
||||
权限拦截规则:
|
||||
未识别身份时点击任意按钮 → Toast 提示
|
||||
"⚠️ 您无权使用该设备,请联系管理员添加权限"
|
||||
不跳转页面,停留在欢迎页
|
||||
|
||||
已识别身份时点击按钮:
|
||||
携带 user + uid 参数跳转目标页面
|
||||
```
|
||||
|
||||
### 11.2 页面跳转关系
|
||||
|
||||
```
|
||||
┌─────────────┐ 点击「验收食材」
|
||||
│ 00 欢迎页 │ ──────────────────→ ┌──────────────────┐
|
||||
│ ·人脸识别 │ │ 01 验收食材 │
|
||||
│ ·菜单导航 │ 点击「验收记录」 │ ·食材识别画面 │
|
||||
│ │ ──────────────────→ │ ·订单/称重/验收 │
|
||||
│ │ │ ·不合格处理 │
|
||||
└─────────────┘ └──┬──────────────┬┘
|
||||
│ 点击「采样」 │ 称重后确认
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐
|
||||
│ 04 食材采样 │ │ 后台推送入库 │
|
||||
│ ·食材识别画面 │ └──────────────┘
|
||||
│ ·采样操作 │
|
||||
└──┬───────────┘
|
||||
│ 返回
|
||||
|
||||
跨功能页面切换 / 提交操作时:
|
||||
调用人脸摄像头校验身份
|
||||
├─ 本人 → 放行
|
||||
└─ 换人 → 拦截(跳回首页 或 toast 提示)
|
||||
|
||||
点击其他菜单(从欢迎页):
|
||||
点击「验收记录」→ 03-verify-history(验收历史查询)
|
||||
点击「食材采样」→ 04-ingredient-sampling(采样操作)
|
||||
点击「采样历史」→ 05-sampling-history(采样记录查询)
|
||||
点击「设置」→ 02-settings(阈值配置)
|
||||
|
||||
各子页面返回:
|
||||
01 验收食材 → 返回 00 欢迎页
|
||||
03 验收记录 → 返回 00 欢迎页
|
||||
04 食材采样 → 返回 00 欢迎页
|
||||
05 采样历史 → 返回 00 欢迎页
|
||||
02 设置 → 返回 00 欢迎页
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十二、核心业务线路中的位置
|
||||
|
||||
```
|
||||
绿色种采-采供管理(填配送信息)
|
||||
↓
|
||||
毛菜验收秤(本模块)
|
||||
├─ 欢迎页(人脸识别 + 菜单导航)
|
||||
│ ├─ 验收食材 → 验收操作 → 入库
|
||||
│ ├─ 验收记录 → 历史查询
|
||||
│ ├─ 食材采样(前置环节)→ 可转验收
|
||||
│ ├─ 采样历史 → 历史查询
|
||||
│ └─ 设置 → 阈值配置
|
||||
└─ 毛菜入库(后台建档)
|
||||
↓
|
||||
卫生检测
|
||||
↓
|
||||
仓储管理 → 毛菜库存柜(储位分配)
|
||||
↓
|
||||
净菜加工 → ...
|
||||
```
|
||||
@@ -10,26 +10,97 @@
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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); }
|
||||
.main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); }
|
||||
.placeholder { text-align: center; color: rgba(255,255,255,0.3); }
|
||||
.placeholder__icon { font-size: 48px; margin-bottom: 16px; }
|
||||
.placeholder__text { font-size: 14px; }
|
||||
.top-bar {
|
||||
display: flex; align-items: center; height: 44px; background: #1a2b4a; padding: 0 20px;
|
||||
}
|
||||
.top-bar__title {
|
||||
flex: 1; text-align: center; font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85);
|
||||
}
|
||||
.main-content {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px;
|
||||
padding: 24px 40px; height: calc(100% - 44px); align-content: center;
|
||||
}
|
||||
.card {
|
||||
display: flex; align-items: center; gap: 20px; padding: 24px 28px;
|
||||
background: #1a2b4a; border-radius: 14px; text-decoration: none; cursor: pointer;
|
||||
transition: all 0.2s; border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3);
|
||||
border-color: rgba(74,144,217,0.3);
|
||||
}
|
||||
.card__icon { font-size: 28px; width: 40px; text-align: center; flex-shrink: 0; }
|
||||
.card__info { flex: 1; }
|
||||
.card__title { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }
|
||||
.card__desc { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.card__arrow { font-size: 18px; color: rgba(255,255,255,0.4); flex-shrink: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">毛菜验收秤</span>
|
||||
<a class="back-btn" href="../index.html" style="font-size:14px;cursor:pointer;color:rgba(255,255,255,0.7);text-decoration:none;">◀ 返回</a>
|
||||
<span class="top-bar__title">毛菜验收秤</span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__icon">🚧</div>
|
||||
<div class="placeholder__text">毛菜验收秤 页面待开发</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<a class="card" href="00-login.html">
|
||||
<span class="card__icon">🔐</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">员工登录</div>
|
||||
<div class="card__desc">人脸识别、账号密码登录</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="00-welcome.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">功能菜单</div>
|
||||
<div class="card__desc">验收食材、验收记录、食材采样、采样历史、设置</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="01-verify-inspection.html">
|
||||
<span class="card__icon">⚖️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">验收操作</div>
|
||||
<div class="card__desc">订单选择、称重验收、不合格处置</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="04-ingredient-sampling.html">
|
||||
<span class="card__icon">🔍</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">食材采样</div>
|
||||
<div class="card__desc">采样操作、外观品级判定、保存采样</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="05-sampling-history.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">采样历史</div>
|
||||
<div class="card__desc">采样记录查看与筛选</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="03-verify-history.html">
|
||||
<span class="card__icon">📝</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">验收记录</div>
|
||||
<div class="card__desc">历史验收记录查看与详情</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="02-settings.html">
|
||||
<span class="card__icon">⚙️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">设置</div>
|
||||
<div class="card__desc">偏差率阈值配置、设备状态</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 登录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.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);
|
||||
}
|
||||
|
||||
/* ---- 主内容:登录面板居中 ---- */
|
||||
.login-main {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: calc(100% - 44px); padding: 24px 32px;
|
||||
}
|
||||
|
||||
/* 登录面板 */
|
||||
.login-panel {
|
||||
width: 480px; display: flex; flex-direction: column; gap: 20px;
|
||||
background: #1a2b4a; border-radius: 16px; padding: 32px 40px;
|
||||
}
|
||||
.login-panel__title {
|
||||
font-size: 18px; font-weight: 600; color: rgba(255,255,255,0.85); text-align: center;
|
||||
}
|
||||
.login-panel__subtitle {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); text-align: center; margin-top: -12px;
|
||||
}
|
||||
|
||||
/* 登录方式切换 Tab */
|
||||
.login-tabs {
|
||||
display: flex; gap: 0; border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.login-tab {
|
||||
flex: 1; padding: 12px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.5);
|
||||
cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.2s;
|
||||
}
|
||||
.login-tab:hover { color: rgba(255,255,255,0.8); }
|
||||
.login-tab.active {
|
||||
color: #4a90d9; border-bottom-color: #4a90d9;
|
||||
}
|
||||
|
||||
/* 人脸识别区 */
|
||||
.face-section {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 24px 0;
|
||||
}
|
||||
.face-section.hidden { display: none; }
|
||||
.face-frame {
|
||||
width: 200px; height: 200px; border-radius: 50%;
|
||||
border: 3px dashed rgba(74,144,217,0.4);
|
||||
display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 8px;
|
||||
position: relative; background: rgba(26,43,74,0.3);
|
||||
cursor: pointer; transition: all 0.3s;
|
||||
}
|
||||
.face-frame:hover {
|
||||
border-color: #4a90d9; background: rgba(74,144,217,0.1);
|
||||
}
|
||||
.face-frame__icon { font-size: 44px; color: rgba(255,255,255,0.3); }
|
||||
.face-frame__text { font-size: 13px; color: rgba(255,255,255,0.4); text-align: center; }
|
||||
|
||||
@keyframes ringPulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.3); }
|
||||
50% { box-shadow: 0 0 0 12px rgba(74,144,217,0); }
|
||||
}
|
||||
.face-frame.detecting {
|
||||
border-color: #4a90d9; border-style: solid;
|
||||
animation: ringPulse 1.5s infinite;
|
||||
}
|
||||
.face-frame.success {
|
||||
border-color: #52c41a; border-style: solid; background: rgba(82,196,26,0.08);
|
||||
}
|
||||
|
||||
.face-info {
|
||||
text-align: center; min-height: 48px;
|
||||
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 10px 16px; width: 100%;
|
||||
}
|
||||
.face-info__status {
|
||||
font-size: 12px; color: rgba(255,255,255,0.35); display: block;
|
||||
}
|
||||
.face-info__name {
|
||||
font-size: 15px; font-weight: 700; color: #52c41a; display: none;
|
||||
}
|
||||
.face-info__name.show { display: block; }
|
||||
.face-info__detail {
|
||||
font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px; display: none;
|
||||
}
|
||||
.face-info__detail.show { display: block; }
|
||||
|
||||
/* 账号密码表单 */
|
||||
.password-section {
|
||||
display: flex; flex-direction: column; gap: 14px; padding: 16px 0;
|
||||
}
|
||||
.password-section.hidden { display: none; }
|
||||
.form-group {
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
}
|
||||
.form-label {
|
||||
font-size: 12px; color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.form-input {
|
||||
background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px; padding: 12px 14px; font-size: 14px; color: #fff;
|
||||
outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.form-input:focus {
|
||||
border-color: #4a90d9;
|
||||
}
|
||||
.form-input::placeholder {
|
||||
color: rgba(255,255,255,0.3);
|
||||
}
|
||||
.login-btn {
|
||||
background: #4a90d9; border: none; border-radius: 8px;
|
||||
padding: 12px; font-size: 14px; font-weight: 600; color: #fff;
|
||||
cursor: pointer; transition: all 0.2s; margin-top: 8px;
|
||||
}
|
||||
.login-btn:hover {
|
||||
background: #5ba0e9; transform: translateY(-1px);
|
||||
}
|
||||
.login-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
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; max-width: 90%;
|
||||
text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--warn { background: rgba(250,173,22,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">加工毛菜秤 - 员工登录</span>
|
||||
</div>
|
||||
|
||||
<div class="login-main">
|
||||
<div class="login-panel">
|
||||
<div class="login-panel__title">员工登录</div>
|
||||
<div class="login-panel__subtitle">请选择登录方式</div>
|
||||
|
||||
<!-- 登录方式切换 -->
|
||||
<div class="login-tabs">
|
||||
<div class="login-tab active" data-tab="face" onclick="switchTab('face')">人脸识别</div>
|
||||
<div class="login-tab" data-tab="password" onclick="switchTab('password')">账号密码</div>
|
||||
</div>
|
||||
|
||||
<!-- 人脸识别区 -->
|
||||
<div class="face-section" id="faceSection">
|
||||
<div class="face-frame" id="faceFrame" onclick="startFaceDetect()">
|
||||
<div class="face-frame__icon" id="faceIcon">👤</div>
|
||||
<div class="face-frame__text" id="faceText">点击激活<br>人脸识别</div>
|
||||
</div>
|
||||
<div class="face-info">
|
||||
<span class="face-info__status" id="faceStatus">识别成功后即可进入功能菜单</span>
|
||||
<div class="face-info__name" id="faceName"></div>
|
||||
<div class="face-info__detail" id="faceDetail"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 账号密码表单 -->
|
||||
<div class="password-section hidden" id="passwordSection">
|
||||
<div class="form-group">
|
||||
<label class="form-label">员工工号</label>
|
||||
<input type="text" class="form-input" id="empId" placeholder="请输入员工工号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">登录密码</label>
|
||||
<input type="password" class="form-input" id="empPassword" placeholder="请输入登录密码">
|
||||
</div>
|
||||
<button class="login-btn" onclick="passwordLogin()">登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var loginState = 'idle'; // idle | detecting | success
|
||||
var currentUser = null; // { name, id, dept, role }
|
||||
var loginTab = 'face'; // face | password
|
||||
|
||||
// 模拟员工数据
|
||||
var mockUsers = [
|
||||
{ name: '张三', id: 'EMP-20260001', dept: '加工车间', role: '清洗员', empId: '10001', password: '123456' },
|
||||
{ name: '李四', id: 'EMP-20260015', dept: '加工车间', role: '清洗员', empId: '10002', password: '123456' },
|
||||
{ name: '王五', id: 'EMP-20260023', dept: '质检部', role: '质检员', empId: '10003', password: '123456' }
|
||||
];
|
||||
|
||||
// 切换登录方式
|
||||
function switchTab(tab) {
|
||||
loginTab = tab;
|
||||
document.querySelectorAll('.login-tab').forEach(function(el) {
|
||||
el.classList.toggle('active', el.dataset.tab === tab);
|
||||
});
|
||||
document.getElementById('faceSection').classList.toggle('hidden', tab !== 'face');
|
||||
document.getElementById('passwordSection').classList.toggle('hidden', tab !== 'password');
|
||||
}
|
||||
|
||||
// 开始人脸识别
|
||||
function startFaceDetect() {
|
||||
if (loginState !== 'idle') return;
|
||||
loginState = 'detecting';
|
||||
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.add('detecting');
|
||||
icon.innerHTML = '🔍';
|
||||
text.textContent = '识别中...';
|
||||
showToast('正在识别人脸...', 1500);
|
||||
|
||||
setTimeout(function() {
|
||||
var user = mockUsers[Math.floor(Math.random() * mockUsers.length)];
|
||||
loginSuccess(user);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 账号密码登录
|
||||
function passwordLogin() {
|
||||
var empId = document.getElementById('empId').value.trim();
|
||||
var password = document.getElementById('empPassword').value.trim();
|
||||
|
||||
if (!empId || !password) {
|
||||
showToast('请输入工号和密码', 2000, 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
var user = mockUsers.find(function(u) { return u.empId === empId && u.password === password; });
|
||||
if (!user) {
|
||||
showToast('工号或密码错误', 2000, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
loginSuccess(user);
|
||||
}
|
||||
|
||||
// 登录成功处理
|
||||
function loginSuccess(user) {
|
||||
currentUser = user;
|
||||
loginState = 'success';
|
||||
|
||||
// 更新人脸识别区状态
|
||||
var frame = document.getElementById('faceFrame');
|
||||
var icon = document.getElementById('faceIcon');
|
||||
var text = document.getElementById('faceText');
|
||||
frame.classList.remove('detecting');
|
||||
frame.classList.add('success');
|
||||
icon.innerHTML = '✅';
|
||||
text.innerHTML = user.name;
|
||||
|
||||
// 显示身份信息
|
||||
document.getElementById('faceStatus').style.display = 'none';
|
||||
var nameEl = document.getElementById('faceName');
|
||||
nameEl.textContent = user.name + ' · ' + user.role;
|
||||
nameEl.classList.add('show');
|
||||
var detailEl = document.getElementById('faceDetail');
|
||||
detailEl.textContent = user.dept + ' · ' + user.id;
|
||||
detailEl.classList.add('show');
|
||||
|
||||
showToast('欢迎,' + user.name + ',正在进入功能菜单...', 1500);
|
||||
|
||||
// 延迟跳转到功能菜单页
|
||||
setTimeout(function() {
|
||||
window.location.href = '00-welcome.html?user=' + encodeURIComponent(user.name) + '&uid=' + user.id;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Toast 提示
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'warn') el.classList.add('toast--warn');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,239 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 功能菜单 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* ---- 顶部导航栏 ---- */
|
||||
.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);
|
||||
}
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* ---- 主内容:菜单按钮列居中 ---- */
|
||||
.menu-main {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
height: calc(100% - 44px); padding: 24px 32px;
|
||||
}
|
||||
|
||||
.menu-panel {
|
||||
width: 560px; display: flex; flex-direction: column; gap: 14px;
|
||||
}
|
||||
.menu-panel__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
|
||||
}
|
||||
.menu-panel__title {
|
||||
font-size: 14px; color: rgba(255,255,255,0.4); letter-spacing: 1px;
|
||||
}
|
||||
.menu-panel__user {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
padding: 16px 20px; border-radius: 12px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; text-decoration: none; color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
.menu-btn:hover {
|
||||
transform: translateX(4px); border-color: rgba(74,144,217,0.3);
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
.menu-btn:active { transform: translateX(2px) scale(0.99); }
|
||||
.menu-btn__icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 22px; flex-shrink: 0;
|
||||
}
|
||||
.menu-btn__icon--blue { background: rgba(74,144,217,0.15); }
|
||||
.menu-btn__icon--green { background: rgba(82,196,26,0.15); }
|
||||
.menu-btn__icon--orange { background: rgba(250,173,22,0.15); }
|
||||
.menu-btn__icon--gray { background: rgba(255,255,255,0.08); }
|
||||
.menu-btn__info { flex: 1; }
|
||||
.menu-btn__title { font-size: 15px; font-weight: 600; margin-bottom: 2px; }
|
||||
.menu-btn__desc { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.menu-btn__arrow { font-size: 16px; color: rgba(255,255,255,0.25); flex-shrink: 0; }
|
||||
|
||||
/* 今日任务到达提示 */
|
||||
.menu-btn--has-tasks {
|
||||
border-color: rgba(74,144,217,0.5) !important;
|
||||
animation: menuPulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes menuPulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(74,144,217,0.4); }
|
||||
50% { box-shadow: 0 0 0 6px rgba(74,144,217,0); }
|
||||
}
|
||||
.menu-btn__badge {
|
||||
position: absolute; top: -6px; right: -6px;
|
||||
background: #ff4d4f; color: #fff;
|
||||
font-size: 10px; font-weight: 700;
|
||||
min-width: 18px; height: 18px; border-radius: 9px;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
padding: 0 4px; line-height: 1;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
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; max-width: 90%;
|
||||
text-align: center; line-height: 1.5;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--warn { background: rgba(250,173,22,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-login.html">◀ 返回</a>
|
||||
<span class="top-bar__title">加工毛菜秤 - 功能菜单</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="menu-main">
|
||||
<div class="menu-panel">
|
||||
<div class="menu-panel__header">
|
||||
<div class="menu-panel__title">📌 功能菜单</div>
|
||||
<div class="menu-panel__user" id="menuUser"></div>
|
||||
</div>
|
||||
|
||||
<a class="menu-btn" id="btnTasks" onclick="handleMenu('01-washing-tasks.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">📋</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">今日任务</div>
|
||||
<div class="menu-btn__desc">当日待处理批次清单</div>
|
||||
</div>
|
||||
<span class="menu-btn__badge" id="taskBadge">0</span>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" id="btnBatchWeigh" onclick="handleMenu('02-batch-weighing.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--green">⚖️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">来料过秤</div>
|
||||
<div class="menu-btn__desc">清洗前,批量上秤 AI 识别,自动匹配溯源码</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" id="btnHistory" onclick="handleMenu('04-history.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--blue">📝</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">操作记录</div>
|
||||
<div class="menu-btn__desc">过秤操作流水记录</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
|
||||
<a class="menu-btn" id="btnSettings" onclick="handleMenu('05-settings.html')">
|
||||
<div class="menu-btn__icon menu-btn__icon--gray">⚙️</div>
|
||||
<div class="menu-btn__info">
|
||||
<div class="menu-btn__title">设备设置</div>
|
||||
<div class="menu-btn__desc">设备参数配置与状态查看</div>
|
||||
</div>
|
||||
<div class="menu-btn__arrow">›</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
var currentUser = null;
|
||||
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 页面加载时检查登录状态
|
||||
(function() {
|
||||
var userName = getUrlParam('user');
|
||||
var userId = getUrlParam('uid');
|
||||
|
||||
if (!userName || !userId) {
|
||||
// 未登录,跳转到登录页
|
||||
window.location.href = '00-login.html';
|
||||
return;
|
||||
}
|
||||
|
||||
currentUser = { name: userName, id: userId };
|
||||
|
||||
// 显示用户信息
|
||||
document.getElementById('topUser').textContent = '操作员:' + userName;
|
||||
document.getElementById('menuUser').textContent = '当前用户:' + userName;
|
||||
|
||||
// 启动任务轮询(每 30 秒检测新任务)
|
||||
setTimeout(function() { checkNewTasks(5); }, 1500);
|
||||
setInterval(function() { checkNewTasks(5); }, 30000);
|
||||
})();
|
||||
|
||||
// 检测新任务到达
|
||||
function checkNewTasks(count) {
|
||||
var btn = document.getElementById('btnTasks');
|
||||
var badge = document.getElementById('taskBadge');
|
||||
btn.classList.add('menu-btn--has-tasks');
|
||||
badge.textContent = count;
|
||||
badge.style.display = 'flex';
|
||||
showToast('⚡ 新任务已到达(' + count + ' 条待处理)', 2500);
|
||||
}
|
||||
|
||||
// 菜单点击处理
|
||||
function handleMenu(target) {
|
||||
if (!currentUser) {
|
||||
window.location.href = '00-login.html';
|
||||
return;
|
||||
}
|
||||
window.location.href = target + '?user=' + encodeURIComponent(currentUser.name) + '&uid=' + currentUser.id;
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function handleLogout() {
|
||||
window.location.href = '00-login.html';
|
||||
}
|
||||
|
||||
// Toast 提示
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'warn') el.classList.add('toast--warn');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,440 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 今日任务 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.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 {
|
||||
height: calc(100% - 44px); padding: 20px 28px; display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
|
||||
/* 状态标签卡(统计+筛选二合一) */
|
||||
.tasks-tabs {
|
||||
display: flex; gap: 10px;
|
||||
}
|
||||
.status-tab {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border-radius: 8px;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
cursor: pointer; transition: all 0.2s; user-select: none;
|
||||
}
|
||||
.status-tab:hover {
|
||||
border-color: rgba(255,255,255,0.15);
|
||||
}
|
||||
.status-tab.active--pending {
|
||||
background: rgba(250,173,22,0.15); border-color: rgba(250,173,22,0.4);
|
||||
}
|
||||
.status-tab.active--done {
|
||||
background: rgba(82,196,26,0.15); border-color: rgba(82,196,26,0.4);
|
||||
}
|
||||
.status-tab__label {
|
||||
font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.7);
|
||||
}
|
||||
.status-tab__count {
|
||||
font-size: 20px; font-weight: 700; color: #fff;
|
||||
}
|
||||
|
||||
/* 任务列表区 */
|
||||
.tasks-section {
|
||||
flex: 1; display: flex; flex-direction: column; gap: 12px; overflow: hidden;
|
||||
}
|
||||
.section-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8);
|
||||
}
|
||||
|
||||
/* 任务列表 */
|
||||
.task-list {
|
||||
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 12px;
|
||||
padding-right: 4px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.task-list::-webkit-scrollbar { width: 4px; }
|
||||
.task-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.task-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
/* ── 任务单卡片 ── */
|
||||
.order-card {
|
||||
background: #1a2b4a; border-radius: 12px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
transition: all 0.2s; overflow: hidden;
|
||||
}
|
||||
.order-card:hover {
|
||||
border-color: rgba(74,144,217,0.3);
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 任务单头部 */
|
||||
.order-card__header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
.order-card__header-left {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
}
|
||||
.order-card__no {
|
||||
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.85);
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
.order-card__badge {
|
||||
display: inline-block; padding: 3px 10px; border-radius: 4px;
|
||||
font-size: 11px; font-weight: 500;
|
||||
}
|
||||
.order-card__badge--pending { background: rgba(250,173,22,0.15); color: #faad16; }
|
||||
.order-card__badge--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
|
||||
/* 分隔线 */
|
||||
.order-card__divider {
|
||||
height: 1px; background: rgba(255,255,255,0.06); margin: 0 20px;
|
||||
}
|
||||
|
||||
/* 食材行 */
|
||||
.order-card__body {
|
||||
padding: 4px 20px;
|
||||
}
|
||||
.ingredient-row {
|
||||
display: flex; align-items: center; padding: 10px 0;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.04);
|
||||
}
|
||||
.ingredient-row:last-child { border-bottom: none; }
|
||||
|
||||
/* 食材图标 */
|
||||
.ingredient-row__icon {
|
||||
width: 32px; height: 32px; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; flex-shrink: 0; margin-right: 12px;
|
||||
}
|
||||
.icon--potato { background: rgba(250,173,22,0.15); }
|
||||
.icon--tomato { background: rgba(255,77,79,0.15); }
|
||||
.icon--cabbage { background: rgba(82,196,26,0.15); }
|
||||
.icon--carrot { background: rgba(250,140,22,0.15); }
|
||||
.icon--cucumber { background: rgba(20,184,166,0.15); }
|
||||
|
||||
.ingredient-row__name {
|
||||
font-size: 14px; font-weight: 500; color: #fff; flex: 1;
|
||||
}
|
||||
.ingredient-row__weight {
|
||||
font-size: 13px; color: rgba(255,255,255,0.6); margin-right: 24px; white-space: nowrap;
|
||||
}
|
||||
.ingredient-row__deadline {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
min-width: 90px; text-align: right; white-space: nowrap;
|
||||
}
|
||||
/* 超时标红 */
|
||||
.ingredient-row__deadline--overdue {
|
||||
color: #ff4d4f; font-weight: 500;
|
||||
}
|
||||
|
||||
/* 卡片底部操作区 */
|
||||
.order-card__footer {
|
||||
display: flex; justify-content: flex-end; align-items: center;
|
||||
padding: 8px 20px;
|
||||
}
|
||||
.order-card__done-mark {
|
||||
font-size: 12px; color: #52c41a; font-weight: 500;
|
||||
}
|
||||
|
||||
/* 处理按钮 */
|
||||
.task-action-btn {
|
||||
padding: 5px 14px; border-radius: 6px;
|
||||
background: #4a90d9; color: #fff;
|
||||
font-size: 12px; font-weight: 500;
|
||||
cursor: pointer; border: none; transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-action-btn:hover { background: #3a7bc8; box-shadow: 0 2px 8px rgba(74,144,217,0.3); }
|
||||
.task-action-btn:active { transform: scale(0.96); }
|
||||
|
||||
/* 已完成卡片(灰化) */
|
||||
.order-card--done {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.order-card--done:hover {
|
||||
border-color: rgba(255,255,255,0.06) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* 新任务单高亮入场 */
|
||||
.order-card--new {
|
||||
animation: taskHighlight 3s ease-out forwards;
|
||||
}
|
||||
@keyframes taskHighlight {
|
||||
0% { border-color: #4a90d9; box-shadow: 0 0 12px rgba(74,144,217,0.5); background: rgba(74,144,217,0.12); }
|
||||
100% { border-color: rgba(255,255,255,0.06); box-shadow: none; background: #1a2b4a; }
|
||||
}
|
||||
|
||||
/* 顶部操作区 */
|
||||
.tabs-spacer { flex: 1; }
|
||||
.top-action-btn {
|
||||
padding: 10px 28px; border-radius: 8px; font-size: 14px; font-weight: 600;
|
||||
background: #4a90d9; color: #fff; border: none; cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.top-action-btn:hover { background: #5ba0e9; transform: translateY(-1px); }
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1100; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">今日清洗任务</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 状态标签卡(统计+筛选) -->
|
||||
<div class="tasks-tabs">
|
||||
<div class="status-tab active--pending" data-filter="pending" onclick="filterTasks('pending', this)">
|
||||
<span class="status-tab__label">待处理</span>
|
||||
<span class="status-tab__count" id="pendingCount">2</span>
|
||||
</div>
|
||||
<div class="status-tab" data-filter="done" onclick="filterTasks('done', this)">
|
||||
<span class="status-tab__label">已完成</span>
|
||||
<span class="status-tab__count" id="doneCount">1</span>
|
||||
</div>
|
||||
<span class="tabs-spacer"></span>
|
||||
<button class="top-action-btn" onclick="goWeigh()">去处理</button>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表区 -->
|
||||
<div class="tasks-section">
|
||||
|
||||
<div class="task-list" id="taskList">
|
||||
|
||||
<!-- 任务单 1:待处理(3 个食材,按截止时间排序) -->
|
||||
<div class="order-card" data-status="pending">
|
||||
<div class="order-card__header">
|
||||
<div class="order-card__header-left">
|
||||
<span class="order-card__no">RW-20260613-001</span>
|
||||
<span class="order-card__badge order-card__badge--pending">待处理</span>
|
||||
</div>
|
||||
<button class="task-action-btn" onclick="handleTask(this)" data-taskno="RW-20260613-001">处理</button>
|
||||
</div>
|
||||
<div class="order-card__divider"></div>
|
||||
<div class="order-card__body">
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--cucumber">🥒</div>
|
||||
<span class="ingredient-row__name">黄瓜</span>
|
||||
<span class="ingredient-row__weight">需 4.0 斤</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">需 5.0 斤</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">需 3.5 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 11:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-card__divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 任务单 2:待处理(2 个食材) -->
|
||||
<div class="order-card" data-status="pending">
|
||||
<div class="order-card__header">
|
||||
<div class="order-card__header-left">
|
||||
<span class="order-card__no">RW-20260613-002</span>
|
||||
<span class="order-card__badge order-card__badge--pending">待处理</span>
|
||||
</div>
|
||||
<button class="task-action-btn" onclick="handleTask(this)" data-taskno="RW-20260613-002">处理</button>
|
||||
</div>
|
||||
<div class="order-card__divider"></div>
|
||||
<div class="order-card__body">
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--carrot">🥕</div>
|
||||
<span class="ingredient-row__name">胡萝卜</span>
|
||||
<span class="ingredient-row__weight">需 2.0 斤</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">需 1.5 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 14:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-card__divider"></div>
|
||||
</div>
|
||||
|
||||
<!-- 任务单 3:已完成(3 个食材) -->
|
||||
<div class="order-card order-card--done" data-status="done">
|
||||
<div class="order-card__header">
|
||||
<div class="order-card__header-left">
|
||||
<span class="order-card__no">RW-20260613-003</span>
|
||||
<span class="order-card__badge order-card__badge--done">已完成</span>
|
||||
</div>
|
||||
<span class="order-card__done-mark">✓ 已完成</span>
|
||||
</div>
|
||||
<div class="order-card__divider"></div>
|
||||
<div class="order-card__body">
|
||||
<div class="ingredient-row">
|
||||
<div class="ingredient-row__icon icon--cabbage">🥬</div>
|
||||
<span class="ingredient-row__name">大白菜</span>
|
||||
<span class="ingredient-row__weight">需 8.0 斤</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">需 2.5 斤</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">需 3.0 斤</span>
|
||||
<span class="ingredient-row__deadline">截止 15:00</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-card__divider"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 显示用户名
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) {
|
||||
document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
}
|
||||
|
||||
// 页面加载时自动执行 pending 筛选
|
||||
var pendingTab = document.querySelector('[data-filter="pending"]');
|
||||
filterTasks('pending', pendingTab);
|
||||
|
||||
// 筛选任务单(标签卡切换)
|
||||
function filterTasks(status, tab) {
|
||||
document.querySelectorAll('.status-tab').forEach(function(el) {
|
||||
el.classList.remove('active--pending', 'active--done');
|
||||
});
|
||||
tab.classList.add(status === 'pending' ? 'active--pending' : 'active--done');
|
||||
document.querySelectorAll('.order-card').forEach(function(card) {
|
||||
card.style.display = (card.dataset.status === status) ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// ── 处理指定任务单:跳到来料过秤页,携带任务单编号 ──
|
||||
function handleTask(btn) {
|
||||
var taskNo = btn.dataset.taskno;
|
||||
var user = document.getElementById('topUser').textContent.replace('操作员: ', '');
|
||||
window.location.href = '02-batch-weighing.html?user=' + encodeURIComponent(user) + '&taskNo=' + encodeURIComponent(taskNo);
|
||||
}
|
||||
|
||||
// ── 兼容旧版 goWeigh(顶部按钮,取第一个待处理任务单)──
|
||||
function goWeigh() {
|
||||
var firstBtn = document.querySelector('.order-card[data-status="pending"] .task-action-btn');
|
||||
if (firstBtn) handleTask(firstBtn);
|
||||
}
|
||||
|
||||
// ── 退出登录 ──
|
||||
function handleLogout() {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, duration) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
|
||||
// ── 模拟新任务单到达(15 秒后推送一个新任务单)──
|
||||
setTimeout(function() {
|
||||
var taskList = document.getElementById('taskList');
|
||||
var newCard = document.createElement('div');
|
||||
newCard.className = 'order-card order-card--new';
|
||||
newCard.dataset.status = 'pending';
|
||||
newCard.innerHTML =
|
||||
'<div class="order-card__header">' +
|
||||
'<div class="order-card__header-left">' +
|
||||
'<span class="order-card__no">RW-20260613-004</span>' +
|
||||
'<span class="order-card__badge order-card__badge--pending">待处理</span>' +
|
||||
'</div>' +
|
||||
'<button class="task-action-btn" onclick="handleTask(this)" data-taskno="RW-20260613-004">处理</button>' +
|
||||
'</div>' +
|
||||
'<div class="order-card__divider"></div>' +
|
||||
'<div class="order-card__body">' +
|
||||
'<div class="ingredient-row">' +
|
||||
'<div class="ingredient-row__icon icon--cabbage">🥦</div>' +
|
||||
'<span class="ingredient-row__name">西兰花</span>' +
|
||||
'<span class="ingredient-row__weight">需 3.0 斤</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">需 2.0 斤</span>' +
|
||||
'<span class="ingredient-row__deadline">截止 13:30</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="order-card__divider"></div>';
|
||||
taskList.insertBefore(newCard, taskList.firstChild);
|
||||
showToast('⚡ 新任务单已到达:RW-20260613-004', 2500);
|
||||
// 更新待处理计数
|
||||
document.getElementById('pendingCount').textContent = '3';
|
||||
}, 15000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,973 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 来料过秤 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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); }
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* 任务关联信息条 */
|
||||
.task-info {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: rgba(74,144,217,0.08); border-radius: 10px; padding: 8px 14px;
|
||||
border: 1px solid rgba(74,144,217,0.15);
|
||||
}
|
||||
.task-info__label { font-size: 11px; color: rgba(255,255,255,0.5); }
|
||||
.task-info__value { font-size: 13px; font-weight: 600; color: #4a90d9; font-family: "Courier New", monospace; }
|
||||
.task-info__empty { font-size: 11px; color: rgba(255,255,255,0.3); }
|
||||
|
||||
.main-content {
|
||||
height: calc(100% - 44px); padding: 14px 20px; display: flex; gap: 16px;
|
||||
}
|
||||
|
||||
/* ========== 左侧:识别区 ========== */
|
||||
.scale-panel {
|
||||
flex: 0 0 440px; display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
|
||||
/* 批次信息条 */
|
||||
.batch-bar {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
background: #1a2b4a; border-radius: 10px; padding: 10px 14px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.batch-bar__label { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.batch-bar__value { font-size: 14px; font-weight: 600; color: #4a90d9; }
|
||||
.batch-bar__count {
|
||||
margin-left: auto; font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.batch-bar__count strong { color: #fff; font-size: 16px; margin-right: 2px; }
|
||||
|
||||
/* 摄像头预览框 */
|
||||
.camera-frame {
|
||||
width: 100%; height: 150px; flex-shrink: 0; background: #0D1117; border-radius: 12px;
|
||||
border: 1px solid rgba(255,255,255,0.08); display: flex; align-items: center;
|
||||
justify-content: center; position: relative; overflow: hidden;
|
||||
}
|
||||
.camera-frame__text { color: rgba(255,255,255,0.2); font-size: 12px; z-index: 1; }
|
||||
.camera-frame.detecting { border-color: #4a90d9; }
|
||||
.camera-frame.stable { border-color: #52c41a; }
|
||||
.camera-frame::before, .camera-frame::after {
|
||||
content: ""; position: absolute; pointer-events: none; z-index: 2;
|
||||
}
|
||||
.camera-frame::before {
|
||||
top: 0; bottom: 0; left: 50%; width: 1px;
|
||||
border-left: 1px dashed rgba(74,144,217,0.25);
|
||||
}
|
||||
.camera-frame::after {
|
||||
left: 0; right: 0; top: 50%; height: 1px;
|
||||
border-top: 1px dashed rgba(74,144,217,0.25);
|
||||
}
|
||||
.corner { position: absolute; width: 20px; height: 20px; z-index: 3; }
|
||||
.corner--tl { top: 10px; left: 10px; border-top: 2px solid #4a90d9; border-left: 2px solid #4a90d9; }
|
||||
.corner--tr { top: 10px; right: 10px; border-top: 2px solid #4a90d9; border-right: 2px solid #4a90d9; }
|
||||
.corner--bl { bottom: 10px; left: 10px; border-bottom: 2px solid #4a90d9; border-left: 2px solid #4a90d9; }
|
||||
.corner--br { bottom: 10px; right: 10px; border-bottom: 2px solid #4a90d9; border-right: 2px solid #4a90d9; }
|
||||
|
||||
/* 识别状态条 */
|
||||
.recognize-status {
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
padding: 6px 8px; background: #1a2b4a; border-radius: 8px;
|
||||
font-size: 12px; color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.status-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
}
|
||||
.status-dot--idle { background: rgba(255,255,255,0.3); }
|
||||
.status-dot--detecting {
|
||||
background: #faad14; box-shadow: 0 0 6px #faad14;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.status-dot--ready { background: #52c41a; box-shadow: 0 0 6px #52c41a; }
|
||||
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
||||
/* 重新识别按钮 */
|
||||
.reidentify-btn {
|
||||
width: 100%; padding: 7px 0; background: rgba(74,144,217,0.1);
|
||||
border: 1px solid rgba(74,144,217,0.25); border-radius: 8px;
|
||||
color: #4a90d9; font-size: 12px; font-weight: 500; cursor: pointer;
|
||||
transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 5px;
|
||||
}
|
||||
.reidentify-btn:hover { background: rgba(74,144,217,0.2); border-color: #4a90d9; }
|
||||
.reidentify-btn:active { transform: scale(0.98); }
|
||||
|
||||
/* 搜索栏 */
|
||||
.food-search { flex-shrink: 0; }
|
||||
.food-search__wrap { position: relative; }
|
||||
.food-search__input {
|
||||
width: 100%; padding: 8px 14px 8px 34px; background: #121832;
|
||||
border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #fff;
|
||||
font-size: 13px; outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.food-search__input:focus { border-color: #4a90d9; }
|
||||
.food-search__input::placeholder { color: rgba(255,255,255,0.25); }
|
||||
.food-search__icon {
|
||||
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
|
||||
font-size: 13px; color: rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
/* 候选食材区 */
|
||||
.candidate-section {
|
||||
flex-shrink: 0; display: flex; flex-direction: column; gap: 6px;
|
||||
}
|
||||
.candidate-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.candidate-title {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500;
|
||||
}
|
||||
.candidate-list {
|
||||
display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
|
||||
}
|
||||
.candidate-card {
|
||||
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 8px; overflow: hidden; cursor: pointer; transition: all 0.2s;
|
||||
display: flex; flex-direction: row; align-items: center; height: 44px;
|
||||
position: relative;
|
||||
}
|
||||
.candidate-card:hover {
|
||||
border-color: rgba(74,144,217,0.3); background: rgba(74,144,217,0.08);
|
||||
}
|
||||
.candidate-card.selected {
|
||||
border-color: #52c41a; background: rgba(82,196,26,0.1);
|
||||
}
|
||||
.candidate-card__img {
|
||||
width: 36px; height: 36px; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px; background: rgba(255,255,255,0.02);
|
||||
border-radius: 6px; margin: 4px;
|
||||
}
|
||||
.candidate-card__name {
|
||||
flex: 1; font-size: 12px; font-weight: 500;
|
||||
color: rgba(255,255,255,0.75); white-space: nowrap;
|
||||
overflow: hidden; text-overflow: ellipsis; padding-right: 6px;
|
||||
}
|
||||
.candidate-card__sim {
|
||||
position: absolute; top: 3px; right: 3px;
|
||||
font-size: 9px; font-weight: 700; color: #52c41a;
|
||||
background: rgba(82,196,26,0.15); border-radius: 3px;
|
||||
padding: 1px 4px; line-height: 1.3;
|
||||
}
|
||||
/* 候选区空状态 */
|
||||
.candidate-empty {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
padding: 12px 0; gap: 4px;
|
||||
}
|
||||
.candidate-empty.hide { display: none; }
|
||||
.candidate-empty__icon { font-size: 22px; opacity: 0.3; }
|
||||
.candidate-empty__text { font-size: 11px; color: rgba(255,255,255,0.25); }
|
||||
|
||||
/* 重量显示(紧凑) */
|
||||
.weight-display { text-align: center; padding: 4px 0; }
|
||||
.weight-display__value {
|
||||
font-size: 42px; font-weight: 700; color: #fff; line-height: 1;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
.weight-display__unit {
|
||||
font-size: 16px; color: rgba(255,255,255,0.5); margin-left: 4px;
|
||||
}
|
||||
.weight-display__status {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); margin-top: 4px;
|
||||
}
|
||||
.weight-display__status.detecting { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
|
||||
/* 溯源码(紧凑行) */
|
||||
.trace-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
background: rgba(74,144,217,0.08); border-radius: 8px; padding: 6px 12px;
|
||||
}
|
||||
.trace-row__label { font-size: 11px; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
.trace-row__code {
|
||||
font-size: 13px; font-weight: 600; color: #4a90d9;
|
||||
font-family: "Courier New", monospace; letter-spacing: 0.5px;
|
||||
}
|
||||
.trace-row__status { margin-left: auto; font-size: 11px; color: #52c41a; white-space: nowrap; }
|
||||
|
||||
/* ========== 右侧:批次清单 ========== */
|
||||
.batch-panel { flex: 1; display: flex; flex-direction: column; gap: 10px; }
|
||||
.batch-summary {
|
||||
background: #1a2b4a; border-radius: 12px; padding: 12px 16px;
|
||||
display: flex; gap: 16px;
|
||||
}
|
||||
.batch-stat { flex: 1; text-align: center; }
|
||||
.batch-stat__value { font-size: 22px; font-weight: 700; color: #fff; }
|
||||
.batch-stat__label { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
.batch-stat__value--green { color: #52c41a; }
|
||||
|
||||
.batch-list-section {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 14px 16px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.batch-list__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;
|
||||
}
|
||||
.batch-list__title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.batch-list__count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.batch-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;
|
||||
}
|
||||
.batch-list::-webkit-scrollbar { width: 4px; }
|
||||
.batch-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.batch-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
.batch-item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px; background: rgba(0,0,0,0.15); border-radius: 8px;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
/* 待称重行样式 */
|
||||
.batch-item--pending {
|
||||
background: rgba(250,173,22,0.06);
|
||||
border: 1px dashed rgba(250,173,22,0.2);
|
||||
}
|
||||
.batch-item--pending .batch-item__name { color: rgba(255,255,255,0.6); }
|
||||
.batch-item__status {
|
||||
font-size: 11px; font-weight: 500; white-space: nowrap; min-width: 48px; text-align: center;
|
||||
}
|
||||
.batch-item__status--pending { color: #faad14; }
|
||||
.batch-item__status--done { color: #52c41a; }
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
.batch-item__icon {
|
||||
width: 34px; height: 34px; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; background: rgba(74,144,217,0.15); flex-shrink: 0;
|
||||
}
|
||||
.batch-item__info { flex: 1; min-width: 0; }
|
||||
.batch-item__name { font-size: 13px; font-weight: 500; color: #fff; }
|
||||
.batch-item__trace {
|
||||
font-size: 11px; color: rgba(74,144,217,0.8); font-family: "Courier New", monospace;
|
||||
}
|
||||
.batch-item__weight {
|
||||
font-size: 14px; font-weight: 600; color: #52c41a; white-space: nowrap;
|
||||
}
|
||||
.batch-item__required {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); white-space: nowrap;
|
||||
min-width: 70px; text-align: right;
|
||||
}
|
||||
.batch-item__del {
|
||||
width: 26px; height: 26px; border-radius: 6px; border: none; cursor: pointer;
|
||||
font-size: 12px; display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(255,77,79,0.08); color: rgba(255,77,79,0.6);
|
||||
border: 1px solid rgba(255,77,79,0.15); transition: all 0.2s; flex-shrink: 0;
|
||||
}
|
||||
.batch-item__del:hover { background: rgba(255,77,79,0.2); color: #ff4d4f; }
|
||||
|
||||
.batch-empty {
|
||||
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
color: rgba(255,255,255,0.3);
|
||||
}
|
||||
.batch-empty__icon { font-size: 32px; margin-bottom: 8px; }
|
||||
.batch-empty__text { font-size: 12px; }
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit-bar { display: flex; align-items: center; gap: 12px; }
|
||||
.submit-btn {
|
||||
flex: 1; padding: 14px; border-radius: 10px; font-size: 15px; font-weight: 600;
|
||||
background: #52c41a; color: #fff; border: none; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.submit-btn:hover { background: #62d42a; }
|
||||
.submit-btn:disabled { background: rgba(82,196,26,0.3); cursor: not-allowed; }
|
||||
.submit-hint {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">来料过秤(清洗前)</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 左侧:识别区 -->
|
||||
<div class="scale-panel">
|
||||
<!-- 任务关联信息条 -->
|
||||
<div class="task-info" id="taskInfo" style="display:none">
|
||||
<span class="task-info__label">关联任务</span>
|
||||
<span class="task-info__value" id="taskNoDisplay">--</span>
|
||||
<span style="color:rgba(255,255,255,0.15);font-size:11px">|</span>
|
||||
<span class="task-info__label">应录入</span>
|
||||
<span class="task-info__value" id="taskWeightDisplay" style="color:#faad14">--</span>
|
||||
<span style="color:rgba(255,255,255,0.15);font-size:11px">|</span>
|
||||
<span class="task-info__label">已录入</span>
|
||||
<span class="task-info__value" id="taskActualDisplay" style="color:#52c41a">0.0 斤</span>
|
||||
</div>
|
||||
|
||||
<!-- 批次信息条 -->
|
||||
<div class="batch-bar">
|
||||
<span class="batch-bar__label">当前批次</span>
|
||||
<span class="batch-bar__value" id="batchNo">LC-20260613-001</span>
|
||||
<span class="batch-bar__count">已录入 <strong id="batchCount">0</strong> 件</span>
|
||||
</div>
|
||||
|
||||
<!-- 摄像头预览框 -->
|
||||
<div class="camera-frame" id="cameraFrame">
|
||||
<span class="camera-frame__text">实时采样区预览</span>
|
||||
<div class="corner corner--tl"></div>
|
||||
<div class="corner corner--tr"></div>
|
||||
<div class="corner corner--bl"></div>
|
||||
<div class="corner corner--br"></div>
|
||||
</div>
|
||||
|
||||
<!-- 识别状态 -->
|
||||
<div class="recognize-status">
|
||||
<span class="status-dot status-dot--idle" id="statusDot"></span>
|
||||
<span id="statusText">就绪,等待食材放置</span>
|
||||
</div>
|
||||
<!-- 重新识别按钮 -->
|
||||
<button class="reidentify-btn" onclick="handleReidentify()">🔄 重新识别</button>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="food-search">
|
||||
<div class="food-search__wrap">
|
||||
<span class="food-search__icon">🔍</span>
|
||||
<input type="text" class="food-search__input" id="searchInput"
|
||||
placeholder="搜索毛菜名称,回车确认...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 候选食材 -->
|
||||
<div class="candidate-section">
|
||||
<div class="candidate-header">
|
||||
<span class="candidate-title" id="candidateTitle">智能识别候选</span>
|
||||
</div>
|
||||
<div class="candidate-list" id="candidateList"></div>
|
||||
<div class="candidate-empty" id="candidateEmpty">
|
||||
<div class="candidate-empty__icon">📷</div>
|
||||
<div class="candidate-empty__text">等待食材放置后自动识别</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 溯源码 -->
|
||||
<div class="trace-row">
|
||||
<span class="trace-row__label">溯源码</span>
|
||||
<span class="trace-row__code" id="traceCode">--</span>
|
||||
<span class="trace-row__status" id="traceStatus"></span>
|
||||
</div>
|
||||
|
||||
<!-- 重量显示 -->
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请放置食材至秤台</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:批次清单 -->
|
||||
<div class="batch-panel">
|
||||
<!-- 批次汇总 -->
|
||||
<div class="batch-summary">
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value" id="totalItems">0</div>
|
||||
<div class="batch-stat__label">品类数</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value" id="totalWeight">0.0</div>
|
||||
<div class="batch-stat__label">总重量(斤)</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value batch-stat__value--green" id="totalTraced">0</div>
|
||||
<div class="batch-stat__label">已匹配溯源</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 清单列表 -->
|
||||
<div class="batch-list-section">
|
||||
<div class="batch-list__header">
|
||||
<span class="batch-list__title">本批次清单</span>
|
||||
<span class="batch-list__count" id="listCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="batch-list" id="batchList">
|
||||
<div class="batch-empty" id="emptyState">
|
||||
<div class="batch-empty__icon">📭</div>
|
||||
<div class="batch-empty__text">尚未录入,请依次放置食材</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交栏 -->
|
||||
<div class="submit-bar">
|
||||
<span class="submit-hint" id="submitHint">待全部食材称重完成后提交</span>
|
||||
<button class="submit-btn" id="submitBtn" onclick="submitBatch()" disabled>提交本批次</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// 任务单数据(含食材级应录入重量)
|
||||
var taskDataMap = {
|
||||
'RW-20260613-001': {
|
||||
weight: 12.5,
|
||||
foods: { '黄瓜': 4.0, '土豆': 5.0, '西红柿': 3.5 }
|
||||
},
|
||||
'RW-20260613-002': {
|
||||
weight: 3.5,
|
||||
foods: { '胡萝卜': 2.0, '小番茄': 1.5 }
|
||||
},
|
||||
'RW-20260613-003': {
|
||||
weight: 13.5,
|
||||
foods: { '大白菜': 8.0, '娃娃菜': 2.5, '土豆(晚餐)': 3.0 }
|
||||
},
|
||||
'RW-20260613-004': {
|
||||
weight: 5.0,
|
||||
foods: { '西兰花': 3.0, '芹菜': 2.0 }
|
||||
}
|
||||
};
|
||||
|
||||
// 获取食材应录入重量
|
||||
function getRequiredWeight(foodName) {
|
||||
var taskData = taskDataMap[taskNo];
|
||||
if (taskData && taskData.foods && taskData.foods[foodName] !== undefined) {
|
||||
return taskData.foods[foodName];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 食材图标映射
|
||||
var foodIconMap = {
|
||||
'黄瓜': '🥒', '土豆': '🥔', '西红柿': '🍅',
|
||||
'胡萝卜': '🥕', '小番茄': '🍅', '大白菜': '🥬',
|
||||
'娃娃菜': '🥬', '西兰花': '🥦', '芹菜': '🥬'
|
||||
};
|
||||
|
||||
// 初始化:预填任务单食材行
|
||||
function initTaskList() {
|
||||
var taskData = taskDataMap[taskNo];
|
||||
if (!taskData || !taskData.foods) return;
|
||||
var idSeq = 1000;
|
||||
var foods = taskData.foods;
|
||||
for (var name in foods) {
|
||||
if (foods.hasOwnProperty(name)) {
|
||||
batchItems.push({
|
||||
id: idSeq++,
|
||||
name: name,
|
||||
icon: foodIconMap[name] || '🥬',
|
||||
weight: 0,
|
||||
requiredWeight: foods[name],
|
||||
trace: '',
|
||||
time: '',
|
||||
done: false
|
||||
});
|
||||
}
|
||||
}
|
||||
// 按应录入重量降序排列
|
||||
batchItems.sort(function(a, b) { return b.requiredWeight - a.requiredWeight; });
|
||||
renderBatchList();
|
||||
updateSummary();
|
||||
}
|
||||
|
||||
// 任务编号(无参数时默认取第一个任务单)
|
||||
var taskNo = getUrlParam('taskNo');
|
||||
if (!taskNo) {
|
||||
for (var k in taskDataMap) {
|
||||
if (taskDataMap.hasOwnProperty(k)) { taskNo = k; break; }
|
||||
}
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function handleLogout() {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
|
||||
// AI 识别结果库(含相似度)
|
||||
var aiResults = [
|
||||
{ name: '土豆', icon: '🥔', sim: 97 },
|
||||
{ name: '西红柿', icon: '🍅', sim: 96 },
|
||||
{ name: '大白菜', icon: '🥬', sim: 95 },
|
||||
{ name: '胡萝卜', icon: '🥕', sim: 98 },
|
||||
{ name: '黄瓜', icon: '🥒', sim: 94 },
|
||||
{ name: '娃娃菜', icon: '🥬', sim: 93 }
|
||||
];
|
||||
|
||||
// 食材库(搜索用)
|
||||
var foodDB = [
|
||||
{ name: '土豆', icon: '🥔' },
|
||||
{ name: '西红柿', icon: '🍅' },
|
||||
{ name: '大白菜', icon: '🥬' },
|
||||
{ name: '胡萝卜', icon: '🥕' },
|
||||
{ name: '黄瓜', icon: '🥒' },
|
||||
{ name: '娃娃菜', icon: '🥬' },
|
||||
{ name: '茄子', icon: '🍆' },
|
||||
{ name: '青椒', icon: '🫑' },
|
||||
{ name: '芹菜', icon: '🥬' },
|
||||
{ name: '菠菜', icon: '🥬' },
|
||||
{ name: '豆角', icon: '🫘' },
|
||||
{ name: '西兰花', icon: '🥦' }
|
||||
];
|
||||
|
||||
var scaleState = 'idle'; // idle | detecting | stable
|
||||
var currentWeight = 0;
|
||||
var selectedFood = null;
|
||||
var currentTrace = '';
|
||||
var batchItems = [];
|
||||
var traceSeq = 1;
|
||||
var candidates = [];
|
||||
var autoAddTimer = null;
|
||||
|
||||
// 预填任务单食材行(在 batchItems 声明之后调用)
|
||||
if (taskNo) {
|
||||
document.getElementById('taskInfo').style.display = 'flex';
|
||||
document.getElementById('taskNoDisplay').textContent = taskNo;
|
||||
var taskData = taskDataMap[taskNo];
|
||||
if (taskData) {
|
||||
document.getElementById('taskWeightDisplay').textContent = taskData.weight.toFixed(1) + ' 斤';
|
||||
initTaskList();
|
||||
}
|
||||
}
|
||||
|
||||
// 生成溯源码(自动匹配)
|
||||
function genTraceCode() {
|
||||
var now = new Date();
|
||||
var date = now.getFullYear().toString() +
|
||||
String(now.getMonth()+1).padStart(2,'0') +
|
||||
String(now.getDate()).padStart(2,'0');
|
||||
var seq = String(traceSeq++).padStart(4, '0');
|
||||
return 'SC' + date + seq;
|
||||
}
|
||||
|
||||
// ========== 搜索功能 ==========
|
||||
var searchInput = document.getElementById('searchInput');
|
||||
searchInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
var keyword = searchInput.value.trim();
|
||||
if (!keyword) return;
|
||||
var results = foodDB.filter(function(f) {
|
||||
return f.name.indexOf(keyword) !== -1;
|
||||
});
|
||||
showCandidates(results.map(function(f) {
|
||||
return { name: f.name, icon: f.icon, sim: null };
|
||||
}), '搜索结果', true);
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 候选食材 ==========
|
||||
function generateCandidates() {
|
||||
// 获取待称重的食材名称
|
||||
var pendingNames = {};
|
||||
batchItems.forEach(function(it) {
|
||||
if (!it.done) pendingNames[it.name] = true;
|
||||
});
|
||||
|
||||
var list = [];
|
||||
var shuffled = aiResults.slice().sort(function() { return Math.random() - 0.5; });
|
||||
for (var i = 0; i < Math.min(4, shuffled.length); i++) {
|
||||
// 优先显示任务单中待称重的食材
|
||||
if (pendingNames[shuffled[i].name]) {
|
||||
list.push({
|
||||
name: shuffled[i].name,
|
||||
icon: shuffled[i].icon,
|
||||
sim: shuffled[i].sim + Math.floor(Math.random() * 3) - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
// 如果待称重食材不够 4 个,补充其他食材
|
||||
while (list.length < 4) {
|
||||
var extra = aiResults[Math.floor(Math.random() * aiResults.length)];
|
||||
if (!list.some(function(l) { return l.name === extra.name; })) {
|
||||
list.push({
|
||||
name: extra.name,
|
||||
icon: extra.icon,
|
||||
sim: extra.sim + Math.floor(Math.random() * 3) - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
list.sort(function(a, b) { return (b.sim || 0) - (a.sim || 0); });
|
||||
showCandidates(list, '智能识别候选', false);
|
||||
}
|
||||
|
||||
function showCandidates(list, title, isSearch) {
|
||||
candidates = list;
|
||||
document.getElementById('candidateTitle').textContent = title || '智能识别候选';
|
||||
var emptyEl = document.getElementById('candidateEmpty');
|
||||
var listEl = document.getElementById('candidateList');
|
||||
|
||||
if (list.length === 0) {
|
||||
listEl.innerHTML = '';
|
||||
emptyEl.classList.remove('hide');
|
||||
return;
|
||||
}
|
||||
emptyEl.classList.add('hide');
|
||||
var html = '';
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var simHtml = list[i].sim ? '<div class="candidate-card__sim">' + list[i].sim + '%</div>' : '';
|
||||
html += '<div class="candidate-card" data-idx="' + i + '" onclick="pickCandidate(' + i + ')">'
|
||||
+ '<div class="candidate-card__img">' + list[i].icon + '</div>'
|
||||
+ '<div class="candidate-card__name">' + list[i].name + '</div>'
|
||||
+ simHtml
|
||||
+ '</div>';
|
||||
}
|
||||
listEl.innerHTML = html;
|
||||
}
|
||||
|
||||
// 选中候选食材
|
||||
function pickCandidate(idx) {
|
||||
if (idx >= candidates.length) return;
|
||||
selectedFood = candidates[idx];
|
||||
// 高亮选中
|
||||
document.querySelectorAll('.candidate-card').forEach(function(el, i) {
|
||||
el.classList.toggle('selected', i === idx);
|
||||
});
|
||||
document.getElementById('statusText').textContent = '已选:' + selectedFood.name;
|
||||
// 若秤已稳定,立即录入
|
||||
if (scaleState === 'stable') {
|
||||
clearTimeout(autoAddTimer);
|
||||
var allDone = batchItems.length > 0 && batchItems.every(function(it) { return it.done; });
|
||||
if (!allDone) addToBatch();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 检测流程 ==========
|
||||
function startDetection() {
|
||||
if (scaleState !== 'idle') return;
|
||||
scaleState = 'detecting';
|
||||
selectedFood = null;
|
||||
currentTrace = '';
|
||||
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.add('detecting');
|
||||
cam.classList.remove('stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--detecting';
|
||||
document.getElementById('statusText').textContent = '正在识别...';
|
||||
document.getElementById('weightStatus').textContent = '检测中...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status detecting';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
|
||||
// 随机食材 + 重量动画
|
||||
var item = aiResults[Math.floor(Math.random() * aiResults.length)];
|
||||
var targetWeight = (Math.random() * 4 + 1).toFixed(2) * 1;
|
||||
|
||||
var anim = 0;
|
||||
var timer = setInterval(function() {
|
||||
anim += 0.15;
|
||||
if (anim >= targetWeight) {
|
||||
anim = targetWeight;
|
||||
clearInterval(timer);
|
||||
onStable(item, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = anim.toFixed(2);
|
||||
}, 100);
|
||||
|
||||
// 延迟显示候选
|
||||
setTimeout(function() {
|
||||
generateCandidates();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// 稳定 → 自动选中首个候选 → 生成溯源码 → 自动录入
|
||||
function onStable(item, weight) {
|
||||
scaleState = 'stable';
|
||||
currentWeight = weight;
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.remove('detecting');
|
||||
cam.classList.add('stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
|
||||
// 自动选中首个候选
|
||||
if (candidates.length > 0 && !selectedFood) {
|
||||
selectedFood = candidates[0];
|
||||
var cards = document.querySelectorAll('.candidate-card');
|
||||
if (cards.length > 0) cards[0].classList.add('selected');
|
||||
document.getElementById('statusText').textContent = '已识别:' + selectedFood.name;
|
||||
}
|
||||
|
||||
// 自动匹配溯源码
|
||||
currentTrace = genTraceCode();
|
||||
document.getElementById('traceCode').textContent = currentTrace;
|
||||
document.getElementById('traceStatus').innerHTML = '✅ 已匹配';
|
||||
|
||||
// 2秒后自动录入(全部完成时不自动录入)
|
||||
autoAddTimer = setTimeout(function() {
|
||||
if (scaleState === 'stable' && selectedFood) {
|
||||
var allDone = batchItems.length > 0 && batchItems.every(function(it) { return it.done; });
|
||||
if (!allDone) addToBatch();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// ========== 批次操作 ==========
|
||||
function addToBatch() {
|
||||
if (!selectedFood || scaleState !== 'stable') return;
|
||||
var foodName = selectedFood.name;
|
||||
|
||||
// 查找匹配的待称重行
|
||||
var matched = null;
|
||||
for (var i = 0; i < batchItems.length; i++) {
|
||||
if (batchItems[i].name === foodName && !batchItems[i].done) {
|
||||
matched = batchItems[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
// 更新已有行
|
||||
matched.weight = currentWeight;
|
||||
matched.trace = currentTrace;
|
||||
matched.time = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
||||
matched.done = true;
|
||||
showToast(foodName + ' ' + currentWeight.toFixed(2) + '斤 已录入(应 ' + matched.requiredWeight.toFixed(1) + '斤)', 1500, 'success');
|
||||
} else {
|
||||
// 任务单外的食材,追加新行
|
||||
var entry = {
|
||||
id: Date.now(),
|
||||
name: foodName,
|
||||
icon: selectedFood.icon,
|
||||
weight: currentWeight,
|
||||
requiredWeight: null,
|
||||
trace: currentTrace,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }),
|
||||
done: true
|
||||
};
|
||||
batchItems.push(entry);
|
||||
showToast(foodName + ' ' + currentWeight.toFixed(2) + '斤 已录入', 1500, 'success');
|
||||
}
|
||||
|
||||
renderBatchList();
|
||||
updateSummary();
|
||||
resetScale();
|
||||
}
|
||||
|
||||
// 删除已录入项
|
||||
function deleteBatchItem(id) {
|
||||
var idx = -1;
|
||||
for (var i = 0; i < batchItems.length; i++) {
|
||||
if (batchItems[i].id === id) { idx = i; break; }
|
||||
}
|
||||
if (idx === -1) return;
|
||||
var name = batchItems[idx].name;
|
||||
batchItems.splice(idx, 1);
|
||||
renderBatchList();
|
||||
updateSummary();
|
||||
showToast(name + ' 已删除', 1200);
|
||||
}
|
||||
|
||||
// 渲染批次清单
|
||||
function renderBatchList() {
|
||||
var list = document.getElementById('batchList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
|
||||
// 清空旧条目
|
||||
var existing = list.querySelectorAll('.batch-item');
|
||||
existing.forEach(function(el) { el.remove(); });
|
||||
|
||||
if (batchItems.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('listCount').textContent = '共 0 条';
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
document.getElementById('batchCount').textContent = '0';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
|
||||
var doneCount = 0;
|
||||
batchItems.forEach(function(item) { if (item.done) doneCount++; });
|
||||
|
||||
document.getElementById('listCount').textContent = '共 ' + batchItems.length + ' 条(已完成 ' + doneCount + ')';
|
||||
document.getElementById('batchCount').textContent = doneCount;
|
||||
|
||||
// 全部完成后才启用提交
|
||||
var allDone = doneCount === batchItems.length && batchItems.length > 0;
|
||||
document.getElementById('submitBtn').disabled = !allDone;
|
||||
document.getElementById('submitHint').textContent = allDone ? '可以提交本批次' : '待全部食材称重完成后提交(' + doneCount + '/' + batchItems.length + ')';
|
||||
|
||||
// 渲染:待称重在前,已称重在后
|
||||
var pending = batchItems.filter(function(it) { return !it.done; });
|
||||
var done = batchItems.filter(function(it) { return it.done; });
|
||||
var ordered = pending.concat(done);
|
||||
|
||||
ordered.forEach(function(item) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'batch-item' + (item.done ? '' : ' batch-item--pending');
|
||||
|
||||
var statusHtml = item.done
|
||||
? '<span class="batch-item__status batch-item__status--done">✓ ' + item.weight.toFixed(2) + '斤</span>'
|
||||
: '<span class="batch-item__status batch-item__status--pending">待称重</span>';
|
||||
|
||||
var weightHtml = item.done
|
||||
? '<div class="batch-item__weight">+' + item.weight.toFixed(2) + '斤</div>'
|
||||
: '<div class="batch-item__weight" style="color:rgba(255,255,255,0.2)">--</div>';
|
||||
|
||||
var traceHtml = item.trace
|
||||
? '<div class="batch-item__trace">' + item.trace + '</div>'
|
||||
: '';
|
||||
|
||||
el.innerHTML =
|
||||
'<div class="batch-item__icon">' + item.icon + '</div>' +
|
||||
'<div class="batch-item__info">' +
|
||||
'<div class="batch-item__name">' + item.name + '</div>' +
|
||||
traceHtml +
|
||||
'</div>' +
|
||||
'<div class="batch-item__required">应 ' + (item.requiredWeight !== null ? item.requiredWeight.toFixed(1) + '斤' : '--') + '</div>' +
|
||||
weightHtml +
|
||||
statusHtml;
|
||||
|
||||
list.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
// 更新汇总
|
||||
function updateSummary() {
|
||||
var names = {};
|
||||
var totalW = 0;
|
||||
var doneCount = 0;
|
||||
batchItems.forEach(function(it) {
|
||||
names[it.name] = true;
|
||||
if (it.done) { totalW += it.weight; doneCount++; }
|
||||
});
|
||||
document.getElementById('totalItems').textContent = Object.keys(names).length;
|
||||
document.getElementById('totalWeight').textContent = totalW.toFixed(1);
|
||||
document.getElementById('totalTraced').textContent = doneCount;
|
||||
// 实时更新 task-info 条中的「已录入」
|
||||
var actualEl = document.getElementById('taskActualDisplay');
|
||||
if (actualEl) actualEl.textContent = totalW.toFixed(1) + ' 斤';
|
||||
}
|
||||
|
||||
// 重新识别:清除当前状态,重新触发 AI 识别
|
||||
function handleReidentify() {
|
||||
scaleState = 'idle';
|
||||
selectedFood = null;
|
||||
currentTrace = '';
|
||||
candidates = [];
|
||||
clearTimeout(autoAddTimer);
|
||||
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.remove('detecting', 'stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--detecting';
|
||||
document.getElementById('statusText').textContent = '正在重新识别...';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
document.getElementById('candidateList').innerHTML = '';
|
||||
document.getElementById('candidateEmpty').classList.remove('hide');
|
||||
document.getElementById('candidateTitle').textContent = '智能识别候选';
|
||||
searchInput.value = '';
|
||||
|
||||
showToast('正在重新识别...', 1200);
|
||||
setTimeout(function() {
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--idle';
|
||||
document.getElementById('statusText').textContent = '就绪,等待食材放置';
|
||||
if (scaleState === 'idle') startDetection();
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
// 清零重置
|
||||
function resetScale() {
|
||||
scaleState = 'idle';
|
||||
currentWeight = 0;
|
||||
selectedFood = null;
|
||||
currentTrace = '';
|
||||
candidates = [];
|
||||
clearTimeout(autoAddTimer);
|
||||
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.remove('detecting', 'stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--idle';
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请放置食材至秤台';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
document.getElementById('candidateList').innerHTML = '';
|
||||
document.getElementById('candidateEmpty').classList.remove('hide');
|
||||
document.getElementById('candidateTitle').textContent = '智能识别候选';
|
||||
searchInput.value = '';
|
||||
|
||||
// 检查是否还有待称重食材
|
||||
var hasPending = batchItems.some(function(it) { return !it.done; });
|
||||
if (hasPending) {
|
||||
document.getElementById('statusText').textContent = '就绪,等待食材放置';
|
||||
setTimeout(function() {
|
||||
if (scaleState === 'idle') startDetection();
|
||||
}, 2500);
|
||||
} else {
|
||||
document.getElementById('statusText').textContent = '全部食材已完成称重,可提交';
|
||||
}
|
||||
}
|
||||
|
||||
// 提交本批次
|
||||
function submitBatch() {
|
||||
var allDone = batchItems.length > 0 && batchItems.every(function(it) { return it.done; });
|
||||
if (!allDone) {
|
||||
showToast('还有食材未称重,无法提交', 2000, 'error');
|
||||
return;
|
||||
}
|
||||
var totalW = 0;
|
||||
batchItems.forEach(function(it) { totalW += it.weight; });
|
||||
showToast('任务单提交成功:' + batchItems.length + '类食材,共 ' + totalW.toFixed(1) + '斤', 1500, 'success');
|
||||
setTimeout(function() {
|
||||
var user = document.getElementById('topUser').textContent.replace('操作员: ', '');
|
||||
window.location.href = '01-washing-tasks.html' + (user ? '?user=' + encodeURIComponent(user) : '');
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
|
||||
// 启动后自动开始第一次检测
|
||||
setTimeout(startDetection, 1500);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,587 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 称重录入 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.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 {
|
||||
height: calc(100% - 44px); padding: 20px 28px; display: flex; gap: 24px;
|
||||
}
|
||||
|
||||
/* 左侧:秤区 */
|
||||
.scale-panel {
|
||||
flex: 0 0 560px; display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
|
||||
/* 当前任务卡 */
|
||||
.current-task {
|
||||
background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.current-task__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;
|
||||
}
|
||||
.current-task__label {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.current-task__switch {
|
||||
font-size: 12px; color: #4a90d9; cursor: pointer;
|
||||
}
|
||||
.current-task__switch:hover { text-decoration: underline; }
|
||||
.current-task__body {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
}
|
||||
.current-task__icon {
|
||||
width: 52px; height: 52px; border-radius: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 26px; background: rgba(250,173,22,0.15);
|
||||
}
|
||||
.current-task__info {
|
||||
flex: 1;
|
||||
}
|
||||
.current-task__name {
|
||||
font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px;
|
||||
}
|
||||
.current-task__detail {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.current-task__progress {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.progress-mini {
|
||||
width: 80px; height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;
|
||||
}
|
||||
.progress-mini__fill {
|
||||
height: 100%; background: #4a90d9; border-radius: 2px;
|
||||
}
|
||||
.progress-mini__text {
|
||||
font-size: 11px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
/* 秤显示区 */
|
||||
.scale-display {
|
||||
flex: 1; background: #1a2b4a; border-radius: 16px; padding: 24px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px;
|
||||
border: 2px solid rgba(255,255,255,0.06); position: relative;
|
||||
}
|
||||
.scale-display.weighing {
|
||||
border-color: #4a90d9;
|
||||
}
|
||||
.scale-display.stable {
|
||||
border-color: #52c41a;
|
||||
}
|
||||
|
||||
/* AI 识别结果 */
|
||||
.ai-result {
|
||||
display: flex; align-items: center; gap: 16px; padding: 16px 24px;
|
||||
background: rgba(0,0,0,0.2); border-radius: 12px; width: 100%;
|
||||
}
|
||||
.ai-result__icon {
|
||||
width: 56px; height: 56px; border-radius: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 30px; background: rgba(74,144,217,0.15);
|
||||
}
|
||||
.ai-result__info {
|
||||
flex: 1;
|
||||
}
|
||||
.ai-result__label {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 4px;
|
||||
}
|
||||
.ai-result__name {
|
||||
font-size: 20px; font-weight: 700; color: #fff;
|
||||
}
|
||||
.ai-result__confidence {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 2px;
|
||||
}
|
||||
.ai-result__status {
|
||||
font-size: 12px; padding: 4px 10px; border-radius: 4px;
|
||||
}
|
||||
.ai-result__status--matching { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
.ai-result__status--mismatch { background: rgba(255,77,79,0.15); color: #ff4d4f; }
|
||||
.ai-result__status--idle { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); }
|
||||
|
||||
/* 重量显示 */
|
||||
.weight-display {
|
||||
text-align: center;
|
||||
}
|
||||
.weight-display__value {
|
||||
font-size: 72px; font-weight: 700; color: #fff; line-height: 1;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
.weight-display__unit {
|
||||
font-size: 20px; color: rgba(255,255,255,0.5); margin-left: 4px;
|
||||
}
|
||||
.weight-display__status {
|
||||
font-size: 13px; color: rgba(255,255,255,0.4); margin-top: 8px;
|
||||
}
|
||||
.weight-display__status.weighing { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
|
||||
/* 操作按钮区 */
|
||||
.scale-actions {
|
||||
display: flex; gap: 12px; width: 100%;
|
||||
}
|
||||
.scale-btn {
|
||||
flex: 1; padding: 14px; border-radius: 10px; font-size: 14px; font-weight: 600;
|
||||
border: none; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.scale-btn--primary {
|
||||
background: #4a90d9; color: #fff;
|
||||
}
|
||||
.scale-btn--primary:hover { background: #5ba0e9; }
|
||||
.scale-btn--primary:disabled {
|
||||
background: rgba(74,144,217,0.3); cursor: not-allowed;
|
||||
}
|
||||
.scale-btn--secondary {
|
||||
background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8);
|
||||
}
|
||||
.scale-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
|
||||
/* 右侧:记录区 */
|
||||
.record-panel {
|
||||
flex: 1; display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
|
||||
/* 今日统计 */
|
||||
.today-stats {
|
||||
background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
display: flex; gap: 20px;
|
||||
}
|
||||
.today-stat {
|
||||
flex: 1; text-align: center;
|
||||
}
|
||||
.today-stat__value {
|
||||
font-size: 24px; font-weight: 700; color: #fff;
|
||||
}
|
||||
.today-stat__label {
|
||||
font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 本次称重记录 */
|
||||
.records-section {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.records-header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;
|
||||
}
|
||||
.records-title {
|
||||
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8);
|
||||
}
|
||||
.records-count {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
.records-list {
|
||||
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.records-list::-webkit-scrollbar { width: 4px; }
|
||||
.records-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.records-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
.record-item {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 12px; background: rgba(0,0,0,0.15); border-radius: 8px;
|
||||
}
|
||||
.record-item__icon {
|
||||
width: 36px; height: 36px; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; background: rgba(250,173,22,0.15);
|
||||
}
|
||||
.record-item__info {
|
||||
flex: 1;
|
||||
}
|
||||
.record-item__name {
|
||||
font-size: 13px; font-weight: 500; color: #fff;
|
||||
}
|
||||
.record-item__time {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
.record-item__weight {
|
||||
font-size: 14px; font-weight: 600; color: #4a90d9;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
color: rgba(255,255,255,0.3);
|
||||
}
|
||||
.empty-state__icon {
|
||||
font-size: 36px; margin-bottom: 12px;
|
||||
}
|
||||
.empty-state__text {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="01-washing-tasks.html">◀ 返回</a>
|
||||
<span class="top-bar__title">称重录入</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 左侧:秤区 -->
|
||||
<div class="scale-panel">
|
||||
<!-- 当前任务卡 -->
|
||||
<div class="current-task">
|
||||
<div class="current-task__header">
|
||||
<span class="current-task__label">当前任务</span>
|
||||
<span class="current-task__switch" onclick="switchTask()">切换任务</span>
|
||||
</div>
|
||||
<div class="current-task__body">
|
||||
<div class="current-task__icon" id="taskIcon">🥔</div>
|
||||
<div class="current-task__info">
|
||||
<div class="current-task__name" id="taskName">土豆</div>
|
||||
<div class="current-task__detail">
|
||||
<span id="taskDetail">午餐 | 截止 10:30</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="current-task__progress">
|
||||
<div class="progress-mini">
|
||||
<div class="progress-mini__fill" id="taskProgressFill" style="width: 60%"></div>
|
||||
</div>
|
||||
<span class="progress-mini__text" id="taskProgressText">3.0/5.0斤</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 秤显示区 -->
|
||||
<div class="scale-display" id="scaleDisplay">
|
||||
<!-- AI 识别结果 -->
|
||||
<div class="ai-result">
|
||||
<div class="ai-result__icon" id="aiIcon">📇</div>
|
||||
<div class="ai-result__info">
|
||||
<div class="ai-result__label">AI 识别结果</div>
|
||||
<div class="ai-result__name" id="aiName">等待放置食材...</div>
|
||||
<div class="ai-result__confidence" id="aiConfidence"></div>
|
||||
</div>
|
||||
<span class="ai-result__status ai-result__status--idle" id="aiStatus">待机</span>
|
||||
</div>
|
||||
|
||||
<!-- 重量显示 -->
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请将食材放置秤上</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="scale-actions">
|
||||
<button class="scale-btn scale-btn--secondary" onclick="resetScale()">清零</button>
|
||||
<button class="scale-btn scale-btn--primary" id="confirmBtn" onclick="confirmWeighing()" disabled>确认提交</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:记录区 -->
|
||||
<div class="record-panel">
|
||||
<!-- 今日统计 -->
|
||||
<div class="today-stats">
|
||||
<div class="today-stat">
|
||||
<div class="today-stat__value">5</div>
|
||||
<div class="today-stat__label">今日称重次数</div>
|
||||
</div>
|
||||
<div class="today-stat">
|
||||
<div class="today-stat__value">15.6</div>
|
||||
<div class="today-stat__label">总重量(斤)</div>
|
||||
</div>
|
||||
<div class="today-stat">
|
||||
<div class="today-stat__value">3</div>
|
||||
<div class="today-stat__label">完成品类</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本次称重记录 -->
|
||||
<div class="records-section">
|
||||
<div class="records-header">
|
||||
<span class="records-title">本次操作记录</span>
|
||||
<span class="records-count" id="recordsCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="records-list" id="recordsList">
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">📭</div>
|
||||
<div class="empty-state__text">暂无称重记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 模拟任务数据
|
||||
var tasks = [
|
||||
{ id: 1, name: '土豆', icon: '🥔', need: 5.0, done: 3.0, deadline: '10:30', meal: '午餐' },
|
||||
{ id: 2, name: '西红柿', icon: '🍅', need: 3.5, done: 0, deadline: '11:00', meal: '午餐' },
|
||||
{ id: 3, name: '黄瓜', icon: '🥒', need: 4.0, done: 2.0, deadline: '10:00', meal: '午餐' }
|
||||
];
|
||||
var currentTaskIndex = 0;
|
||||
|
||||
// 模拟AI识别结果
|
||||
var aiResults = {
|
||||
'potato': { name: '土豆', icon: '🥔', confidence: 98 },
|
||||
'tomato': { name: '西红柿', icon: '🍅', confidence: 96 },
|
||||
'cabbage': { name: '大白菜', icon: '🥬', confidence: 95 },
|
||||
'carrot': { name: '胡萝卜', icon: '🥕', confidence: 97 },
|
||||
'cucumber': { name: '黄瓜', icon: '🥒', confidence: 94 },
|
||||
'unknown': { name: '未识别', icon: '❓', confidence: 0 }
|
||||
};
|
||||
|
||||
var state = 'idle'; // idle | detecting | stable
|
||||
var currentWeight = 0;
|
||||
var currentAiResult = null;
|
||||
var records = [];
|
||||
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) {
|
||||
document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
}
|
||||
|
||||
var ingredient = getUrlParam('ingredient');
|
||||
if (ingredient) {
|
||||
var idx = tasks.findIndex(function(t) { return t.name.includes(ingredient) || ingredient.includes(t.name); });
|
||||
if (idx >= 0) currentTaskIndex = idx;
|
||||
}
|
||||
|
||||
updateTaskDisplay();
|
||||
simulateWeighing();
|
||||
}
|
||||
|
||||
// 更新任务显示
|
||||
function updateTaskDisplay() {
|
||||
var task = tasks[currentTaskIndex];
|
||||
document.getElementById('taskIcon').innerHTML = task.icon;
|
||||
document.getElementById('taskName').textContent = task.name;
|
||||
document.getElementById('taskDetail').textContent = task.meal + ' | 截止 ' + task.deadline;
|
||||
|
||||
var percent = Math.round((task.done / task.need) * 100);
|
||||
document.getElementById('taskProgressFill').style.width = percent + '%';
|
||||
document.getElementById('taskProgressText').textContent = task.done.toFixed(1) + '/' + task.need.toFixed(1) + '斤';
|
||||
}
|
||||
|
||||
// 切换任务
|
||||
function switchTask() {
|
||||
currentTaskIndex = (currentTaskIndex + 1) % tasks.length;
|
||||
updateTaskDisplay();
|
||||
resetScale();
|
||||
showToast('已切换到: ' + tasks[currentTaskIndex].name, 1500);
|
||||
}
|
||||
|
||||
// 模拟称重流程
|
||||
function simulateWeighing() {
|
||||
// 模拟用户放菜
|
||||
setTimeout(function() {
|
||||
startDetecting('potato'); // 模拟识别到土豆
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 开始识别
|
||||
function startDetecting(ingredientKey) {
|
||||
state = 'detecting';
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.add('weighing');
|
||||
scale.classList.remove('stable');
|
||||
|
||||
document.getElementById('weightStatus').textContent = '正在称重...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status weighing';
|
||||
|
||||
// 模拟重量变化
|
||||
var targetWeight = 2.35;
|
||||
var currentWeightAnim = 0;
|
||||
var weightInterval = setInterval(function() {
|
||||
currentWeightAnim += 0.15;
|
||||
if (currentWeightAnim >= targetWeight) {
|
||||
currentWeightAnim = targetWeight;
|
||||
clearInterval(weightInterval);
|
||||
onWeightStable(ingredientKey, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = currentWeightAnim.toFixed(2);
|
||||
}, 100);
|
||||
|
||||
// AI识别
|
||||
setTimeout(function() {
|
||||
var result = aiResults[ingredientKey];
|
||||
currentAiResult = result;
|
||||
document.getElementById('aiIcon').innerHTML = result.icon;
|
||||
document.getElementById('aiName').textContent = result.name;
|
||||
document.getElementById('aiConfidence').textContent = '置信度: ' + result.confidence + '%';
|
||||
|
||||
// 检查是否匹配当前任务
|
||||
var task = tasks[currentTaskIndex];
|
||||
var isMatch = task.name === result.name || task.name.includes(result.name);
|
||||
|
||||
var statusEl = document.getElementById('aiStatus');
|
||||
if (isMatch) {
|
||||
statusEl.textContent = '匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--matching';
|
||||
} else {
|
||||
statusEl.textContent = '不匹配';
|
||||
statusEl.className = 'ai-result__status ai-result__status--mismatch';
|
||||
showToast('AI识别不匹配,请检查食材', 2500, 'error');
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// 重量稳定
|
||||
function onWeightStable(ingredientKey, weight) {
|
||||
state = 'stable';
|
||||
currentWeight = weight;
|
||||
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing');
|
||||
scale.classList.add('stable');
|
||||
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
|
||||
document.getElementById('confirmBtn').disabled = false;
|
||||
}
|
||||
|
||||
// 确认提交
|
||||
function confirmWeighing() {
|
||||
if (state !== 'stable' || !currentAiResult) return;
|
||||
|
||||
var task = tasks[currentTaskIndex];
|
||||
task.done += currentWeight;
|
||||
|
||||
// 添加记录
|
||||
var record = {
|
||||
name: currentAiResult.name,
|
||||
icon: currentAiResult.icon,
|
||||
weight: currentWeight,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
};
|
||||
records.unshift(record);
|
||||
updateRecordsDisplay();
|
||||
|
||||
showToast('提交成功: ' + currentAiResult.name + ' ' + currentWeight.toFixed(2) + '斤', 2000, 'success');
|
||||
|
||||
// 重置秤
|
||||
resetScale();
|
||||
updateTaskDisplay();
|
||||
}
|
||||
|
||||
// 更新记录显示
|
||||
function updateRecordsDisplay() {
|
||||
var list = document.getElementById('recordsList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
|
||||
if (records.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('recordsCount').textContent = '共 0 条';
|
||||
return;
|
||||
}
|
||||
|
||||
empty.style.display = 'none';
|
||||
document.getElementById('recordsCount').textContent = '共 ' + records.length + ' 条';
|
||||
|
||||
// 清空并重新渲染
|
||||
var existingItems = list.querySelectorAll('.record-item');
|
||||
existingItems.forEach(function(el) { el.remove(); });
|
||||
|
||||
records.forEach(function(r) {
|
||||
var item = document.createElement('div');
|
||||
item.className = 'record-item';
|
||||
item.innerHTML = '<div class="record-item__icon">' + r.icon + '</div>' +
|
||||
'<div class="record-item__info"><div class="record-item__name">' + r.name + '</div>' +
|
||||
'<div class="record-item__time">' + r.time + '</div></div>' +
|
||||
'<div class="record-item__weight">+' + r.weight.toFixed(2) + '斤</div>';
|
||||
list.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// 清零
|
||||
function resetScale() {
|
||||
state = 'idle';
|
||||
currentWeight = 0;
|
||||
currentAiResult = null;
|
||||
|
||||
var scale = document.getElementById('scaleDisplay');
|
||||
scale.classList.remove('weighing', 'stable');
|
||||
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请将食材放置秤上';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
|
||||
document.getElementById('aiIcon').innerHTML = '📇';
|
||||
document.getElementById('aiName').textContent = '等待放置食材...';
|
||||
document.getElementById('aiConfidence').textContent = '';
|
||||
document.getElementById('aiStatus').textContent = '待机';
|
||||
document.getElementById('aiStatus').className = 'ai-result__status ai-result__status--idle';
|
||||
|
||||
document.getElementById('confirmBtn').disabled = true;
|
||||
|
||||
// 模拟下一次称重
|
||||
setTimeout(function() {
|
||||
var ingredients = ['potato', 'tomato', 'cabbage'];
|
||||
var randomIng = ingredients[Math.floor(Math.random() * ingredients.length)];
|
||||
startDetecting(randomIng);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,725 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 净菜过秤 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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 {
|
||||
height: calc(100% - 44px); padding: 14px 20px; display: flex; gap: 16px;
|
||||
}
|
||||
|
||||
/* ========== 左侧:识别区 ========== */
|
||||
.scale-panel {
|
||||
flex: 0 0 440px; display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
|
||||
/* 批次信息条 */
|
||||
.batch-bar {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
background: #1a2b4a; border-radius: 10px; padding: 10px 14px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.batch-bar__label { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.batch-bar__value { font-size: 14px; font-weight: 600; color: #52c41a; }
|
||||
.batch-bar__count {
|
||||
margin-left: auto; font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.batch-bar__count strong { color: #fff; font-size: 16px; margin-right: 2px; }
|
||||
|
||||
/* 摄像头预览框 */
|
||||
.camera-frame {
|
||||
width: 100%; height: 150px; flex-shrink: 0; background: #0D1117; border-radius: 12px;
|
||||
border: 1px solid rgba(255,255,255,0.08); display: flex; align-items: center;
|
||||
justify-content: center; position: relative; overflow: hidden;
|
||||
}
|
||||
.camera-frame__text { color: rgba(255,255,255,0.2); font-size: 12px; z-index: 1; }
|
||||
.camera-frame.detecting { border-color: #4a90d9; }
|
||||
.camera-frame.stable { border-color: #52c41a; }
|
||||
.camera-frame::before, .camera-frame::after {
|
||||
content: ""; position: absolute; pointer-events: none; z-index: 2;
|
||||
}
|
||||
.camera-frame::before {
|
||||
top: 0; bottom: 0; left: 50%; width: 1px;
|
||||
border-left: 1px dashed rgba(82,196,26,0.25);
|
||||
}
|
||||
.camera-frame::after {
|
||||
left: 0; right: 0; top: 50%; height: 1px;
|
||||
border-top: 1px dashed rgba(82,196,26,0.25);
|
||||
}
|
||||
.corner { position: absolute; width: 20px; height: 20px; z-index: 3; }
|
||||
.corner--tl { top: 10px; left: 10px; border-top: 2px solid #52c41a; border-left: 2px solid #52c41a; }
|
||||
.corner--tr { top: 10px; right: 10px; border-top: 2px solid #52c41a; border-right: 2px solid #52c41a; }
|
||||
.corner--bl { bottom: 10px; left: 10px; border-bottom: 2px solid #52c41a; border-left: 2px solid #52c41a; }
|
||||
.corner--br { bottom: 10px; right: 10px; border-bottom: 2px solid #52c41a; border-right: 2px solid #52c41a; }
|
||||
|
||||
/* 识别状态条 */
|
||||
.recognize-status {
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
padding: 6px 8px; background: #1a2b4a; border-radius: 8px;
|
||||
font-size: 12px; color: rgba(255,255,255,0.6);
|
||||
}
|
||||
.status-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
}
|
||||
.status-dot--idle { background: rgba(255,255,255,0.3); }
|
||||
.status-dot--detecting {
|
||||
background: #faad14; box-shadow: 0 0 6px #faad14;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
.status-dot--ready { background: #52c41a; box-shadow: 0 0 6px #52c41a; }
|
||||
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
||||
|
||||
/* 搜索栏 */
|
||||
.food-search { flex-shrink: 0; }
|
||||
.food-search__wrap { position: relative; }
|
||||
.food-search__input {
|
||||
width: 100%; padding: 8px 14px 8px 34px; background: #121832;
|
||||
border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #fff;
|
||||
font-size: 13px; outline: none; transition: border-color 0.2s;
|
||||
}
|
||||
.food-search__input:focus { border-color: #52c41a; }
|
||||
.food-search__input::placeholder { color: rgba(255,255,255,0.25); }
|
||||
.food-search__icon {
|
||||
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
|
||||
font-size: 13px; color: rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
/* 候选食材区 */
|
||||
.candidate-section {
|
||||
flex-shrink: 0; display: flex; flex-direction: column; gap: 6px;
|
||||
}
|
||||
.candidate-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
}
|
||||
.candidate-title {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4); font-weight: 500;
|
||||
}
|
||||
.candidate-list {
|
||||
display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
|
||||
}
|
||||
.candidate-card {
|
||||
background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 8px; overflow: hidden; cursor: pointer; transition: all 0.2s;
|
||||
display: flex; flex-direction: row; align-items: center; height: 44px;
|
||||
position: relative;
|
||||
}
|
||||
.candidate-card:hover {
|
||||
border-color: rgba(82,196,26,0.3); background: rgba(82,196,26,0.08);
|
||||
}
|
||||
.candidate-card.selected {
|
||||
border-color: #52c41a; background: rgba(82,196,26,0.1);
|
||||
}
|
||||
.candidate-card__img {
|
||||
width: 36px; height: 36px; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px; background: rgba(255,255,255,0.02);
|
||||
border-radius: 6px; margin: 4px;
|
||||
}
|
||||
.candidate-card__name {
|
||||
flex: 1; font-size: 12px; font-weight: 500;
|
||||
color: rgba(255,255,255,0.75); white-space: nowrap;
|
||||
overflow: hidden; text-overflow: ellipsis; padding-right: 6px;
|
||||
}
|
||||
.candidate-card__sim {
|
||||
position: absolute; top: 3px; right: 3px;
|
||||
font-size: 9px; font-weight: 700; color: #52c41a;
|
||||
background: rgba(82,196,26,0.15); border-radius: 3px;
|
||||
padding: 1px 4px; line-height: 1.3;
|
||||
}
|
||||
/* 候选区空状态 */
|
||||
.candidate-empty {
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
padding: 12px 0; gap: 4px;
|
||||
}
|
||||
.candidate-empty.hide { display: none; }
|
||||
.candidate-empty__icon { font-size: 22px; opacity: 0.3; }
|
||||
.candidate-empty__text { font-size: 11px; color: rgba(255,255,255,0.25); }
|
||||
|
||||
/* 重量显示(紧凑) */
|
||||
.weight-display { text-align: center; padding: 4px 0; }
|
||||
.weight-display__value {
|
||||
font-size: 42px; font-weight: 700; color: #fff; line-height: 1;
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
.weight-display__unit {
|
||||
font-size: 16px; color: rgba(255,255,255,0.5); margin-left: 4px;
|
||||
}
|
||||
.weight-display__status {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); margin-top: 4px;
|
||||
}
|
||||
.weight-display__status.detecting { color: #4a90d9; }
|
||||
.weight-display__status.stable { color: #52c41a; }
|
||||
|
||||
/* 溯源码(紧凑行) */
|
||||
.trace-row {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
background: rgba(82,196,26,0.08); border-radius: 8px; padding: 6px 12px;
|
||||
}
|
||||
.trace-row__label { font-size: 11px; color: rgba(255,255,255,0.5); white-space: nowrap; }
|
||||
.trace-row__code {
|
||||
font-size: 13px; font-weight: 600; color: #52c41a;
|
||||
font-family: "Courier New", monospace; letter-spacing: 0.5px;
|
||||
}
|
||||
.trace-row__status { margin-left: auto; font-size: 11px; color: #52c41a; white-space: nowrap; }
|
||||
|
||||
/* ========== 右侧:批次清单 ========== */
|
||||
.batch-panel { flex: 1; display: flex; flex-direction: column; gap: 10px; }
|
||||
.batch-summary {
|
||||
background: #1a2b4a; border-radius: 12px; padding: 12px 16px;
|
||||
display: flex; gap: 16px;
|
||||
}
|
||||
.batch-stat { flex: 1; text-align: center; }
|
||||
.batch-stat__value { font-size: 22px; font-weight: 700; color: #fff; }
|
||||
.batch-stat__label { font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 2px; }
|
||||
.batch-stat__value--green { color: #52c41a; }
|
||||
|
||||
.batch-list-section {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 14px 16px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.batch-list__header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;
|
||||
}
|
||||
.batch-list__title { font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8); }
|
||||
.batch-list__count { font-size: 12px; color: rgba(255,255,255,0.4); }
|
||||
.batch-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;
|
||||
}
|
||||
.batch-list::-webkit-scrollbar { width: 4px; }
|
||||
.batch-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.batch-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
.batch-item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px; background: rgba(0,0,0,0.15); border-radius: 8px;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
.batch-item__icon {
|
||||
width: 34px; height: 34px; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; background: rgba(82,196,26,0.15); flex-shrink: 0;
|
||||
}
|
||||
.batch-item__info { flex: 1; min-width: 0; }
|
||||
.batch-item__name { font-size: 13px; font-weight: 500; color: #fff; }
|
||||
.batch-item__trace {
|
||||
font-size: 11px; color: rgba(82,196,26,0.8); font-family: "Courier New", monospace;
|
||||
}
|
||||
.batch-item__weight {
|
||||
font-size: 14px; font-weight: 600; color: #52c41a; white-space: nowrap;
|
||||
}
|
||||
.batch-item__del {
|
||||
width: 26px; height: 26px; border-radius: 6px; border: none; cursor: pointer;
|
||||
font-size: 12px; display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(255,77,79,0.08); color: rgba(255,77,79,0.6);
|
||||
border: 1px solid rgba(255,77,79,0.15); transition: all 0.2s; flex-shrink: 0;
|
||||
}
|
||||
.batch-item__del:hover { background: rgba(255,77,79,0.2); color: #ff4d4f; }
|
||||
|
||||
.batch-empty {
|
||||
flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
color: rgba(255,255,255,0.3);
|
||||
}
|
||||
.batch-empty__icon { font-size: 32px; margin-bottom: 8px; }
|
||||
.batch-empty__text { font-size: 12px; }
|
||||
|
||||
/* 提交按钮 */
|
||||
.submit-bar { display: flex; align-items: center; gap: 12px; }
|
||||
.submit-btn {
|
||||
flex: 1; padding: 14px; border-radius: 10px; font-size: 15px; font-weight: 600;
|
||||
background: #52c41a; color: #fff; border: none; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.submit-btn:hover { background: #62d42a; }
|
||||
.submit-btn:disabled { background: rgba(82,196,26,0.3); cursor: not-allowed; }
|
||||
.submit-hint {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4); white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
.toast--error { background: rgba(255,77,79,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">净菜过秤(清洗后)</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 左侧:识别区 -->
|
||||
<div class="scale-panel">
|
||||
<!-- 批次信息条 -->
|
||||
<div class="batch-bar">
|
||||
<span class="batch-bar__label">当前批次</span>
|
||||
<span class="batch-bar__value" id="batchNo">JC-20260613-001</span>
|
||||
<span class="batch-bar__count">已录入 <strong id="batchCount">0</strong> 件</span>
|
||||
</div>
|
||||
|
||||
<!-- 摄像头预览框 -->
|
||||
<div class="camera-frame" id="cameraFrame">
|
||||
<span class="camera-frame__text">实时采样区预览</span>
|
||||
<div class="corner corner--tl"></div>
|
||||
<div class="corner corner--tr"></div>
|
||||
<div class="corner corner--bl"></div>
|
||||
<div class="corner corner--br"></div>
|
||||
</div>
|
||||
|
||||
<!-- 识别状态 -->
|
||||
<div class="recognize-status">
|
||||
<span class="status-dot status-dot--idle" id="statusDot"></span>
|
||||
<span id="statusText">就绪,等待食材放置</span>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="food-search">
|
||||
<div class="food-search__wrap">
|
||||
<span class="food-search__icon">🔍</span>
|
||||
<input type="text" class="food-search__input" id="searchInput"
|
||||
placeholder="搜索净菜名称,回车确认...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 候选食材 -->
|
||||
<div class="candidate-section">
|
||||
<div class="candidate-header">
|
||||
<span class="candidate-title" id="candidateTitle">智能识别候选</span>
|
||||
</div>
|
||||
<div class="candidate-list" id="candidateList"></div>
|
||||
<div class="candidate-empty" id="candidateEmpty">
|
||||
<div class="candidate-empty__icon">📷</div>
|
||||
<div class="candidate-empty__text">等待食材放置后自动识别</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 溯源码 -->
|
||||
<div class="trace-row">
|
||||
<span class="trace-row__label">溯源码</span>
|
||||
<span class="trace-row__code" id="traceCode">--</span>
|
||||
<span class="trace-row__status" id="traceStatus"></span>
|
||||
</div>
|
||||
|
||||
<!-- 重量显示 -->
|
||||
<div class="weight-display">
|
||||
<span class="weight-display__value" id="weightValue">0.00</span>
|
||||
<span class="weight-display__unit">斤</span>
|
||||
<div class="weight-display__status" id="weightStatus">请放置食材至秤台</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:批次清单 -->
|
||||
<div class="batch-panel">
|
||||
<!-- 批次汇总 -->
|
||||
<div class="batch-summary">
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value" id="totalItems">0</div>
|
||||
<div class="batch-stat__label">品类数</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value" id="totalWeight">0.0</div>
|
||||
<div class="batch-stat__label">总重量(斤)</div>
|
||||
</div>
|
||||
<div class="batch-stat">
|
||||
<div class="batch-stat__value batch-stat__value--green" id="totalTraced">0</div>
|
||||
<div class="batch-stat__label">已匹配溯源</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 清单列表 -->
|
||||
<div class="batch-list-section">
|
||||
<div class="batch-list__header">
|
||||
<span class="batch-list__title">本批次清单</span>
|
||||
<span class="batch-list__count" id="listCount">共 0 条</span>
|
||||
</div>
|
||||
<div class="batch-list" id="batchList">
|
||||
<div class="batch-empty" id="emptyState">
|
||||
<div class="batch-empty__icon">📭</div>
|
||||
<div class="batch-empty__text">尚未录入,请依次放置清洗后食材</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交栏 -->
|
||||
<div class="submit-bar">
|
||||
<span class="submit-hint">全部录入完成后提交本批次</span>
|
||||
<button class="submit-btn" id="submitBtn" onclick="submitBatch()" disabled>提交本批次</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
|
||||
// AI 识别结果库(含相似度)
|
||||
var aiResults = [
|
||||
{ name: '土豆(净)', icon: '🥔', sim: 97 },
|
||||
{ name: '西红柿(净)', icon: '🍅', sim: 96 },
|
||||
{ name: '大白菜(净)', icon: '🥬', sim: 95 },
|
||||
{ name: '胡萝卜(净)', icon: '🥕', sim: 98 },
|
||||
{ name: '黄瓜(净)', icon: '🥒', sim: 94 },
|
||||
{ name: '娃娃菜(净)', icon: '🥬', sim: 93 }
|
||||
];
|
||||
|
||||
// 食材库(搜索用)
|
||||
var foodDB = [
|
||||
{ name: '土豆(净)', icon: '🥔' },
|
||||
{ name: '西红柿(净)', icon: '🍅' },
|
||||
{ name: '大白菜(净)', icon: '🥬' },
|
||||
{ name: '胡萝卜(净)', icon: '🥕' },
|
||||
{ name: '黄瓜(净)', icon: '🥒' },
|
||||
{ name: '娃娃菜(净)', icon: '🥬' },
|
||||
{ name: '茄子(净)', icon: '🍆' },
|
||||
{ name: '青椒(净)', icon: '🫑' },
|
||||
{ name: '芹菜(净)', icon: '🥬' },
|
||||
{ name: '菠菜(净)', icon: '🥬' },
|
||||
{ name: '豆角(净)', icon: '🫘' },
|
||||
{ name: '西兰花(净)', icon: '🥦' }
|
||||
];
|
||||
|
||||
var scaleState = 'idle'; // idle | detecting | stable
|
||||
var currentWeight = 0;
|
||||
var selectedFood = null;
|
||||
var currentTrace = '';
|
||||
var batchItems = [];
|
||||
var traceSeq = 1;
|
||||
var candidates = [];
|
||||
var autoAddTimer = null;
|
||||
|
||||
// 生成溯源码(自动匹配)
|
||||
function genTraceCode() {
|
||||
var now = new Date();
|
||||
var date = now.getFullYear().toString() +
|
||||
String(now.getMonth()+1).padStart(2,'0') +
|
||||
String(now.getDate()).padStart(2,'0');
|
||||
var seq = String(traceSeq++).padStart(4, '0');
|
||||
return 'SC' + date + seq;
|
||||
}
|
||||
|
||||
// ========== 搜索功能 ==========
|
||||
var searchInput = document.getElementById('searchInput');
|
||||
searchInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
var keyword = searchInput.value.trim();
|
||||
if (!keyword) return;
|
||||
var results = foodDB.filter(function(f) {
|
||||
return f.name.indexOf(keyword) !== -1;
|
||||
});
|
||||
showCandidates(results.map(function(f) {
|
||||
return { name: f.name, icon: f.icon, sim: null };
|
||||
}), '搜索结果', true);
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 候选食材 ==========
|
||||
function generateCandidates() {
|
||||
var list = [];
|
||||
var shuffled = aiResults.slice().sort(function() { return Math.random() - 0.5; });
|
||||
for (var i = 0; i < Math.min(4, shuffled.length); i++) {
|
||||
list.push({
|
||||
name: shuffled[i].name,
|
||||
icon: shuffled[i].icon,
|
||||
sim: shuffled[i].sim + Math.floor(Math.random() * 3) - 1
|
||||
});
|
||||
}
|
||||
list.sort(function(a, b) { return (b.sim || 0) - (a.sim || 0); });
|
||||
showCandidates(list, '智能识别候选', false);
|
||||
}
|
||||
|
||||
function showCandidates(list, title, isSearch) {
|
||||
candidates = list;
|
||||
document.getElementById('candidateTitle').textContent = title || '智能识别候选';
|
||||
var emptyEl = document.getElementById('candidateEmpty');
|
||||
var listEl = document.getElementById('candidateList');
|
||||
|
||||
if (list.length === 0) {
|
||||
listEl.innerHTML = '';
|
||||
emptyEl.classList.remove('hide');
|
||||
return;
|
||||
}
|
||||
emptyEl.classList.add('hide');
|
||||
var html = '';
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var simHtml = list[i].sim ? '<div class="candidate-card__sim">' + list[i].sim + '%</div>' : '';
|
||||
html += '<div class="candidate-card" data-idx="' + i + '" onclick="pickCandidate(' + i + ')">'
|
||||
+ '<div class="candidate-card__img">' + list[i].icon + '</div>'
|
||||
+ '<div class="candidate-card__name">' + list[i].name + '</div>'
|
||||
+ simHtml
|
||||
+ '</div>';
|
||||
}
|
||||
listEl.innerHTML = html;
|
||||
}
|
||||
|
||||
// 选中候选食材
|
||||
function pickCandidate(idx) {
|
||||
if (idx >= candidates.length) return;
|
||||
selectedFood = candidates[idx];
|
||||
// 高亮选中
|
||||
document.querySelectorAll('.candidate-card').forEach(function(el, i) {
|
||||
el.classList.toggle('selected', i === idx);
|
||||
});
|
||||
document.getElementById('statusText').textContent = '已选:' + selectedFood.name;
|
||||
// 若秤已稳定,立即录入
|
||||
if (scaleState === 'stable') {
|
||||
clearTimeout(autoAddTimer);
|
||||
addToBatch();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 检测流程 ==========
|
||||
function startDetection() {
|
||||
if (scaleState !== 'idle') return;
|
||||
scaleState = 'detecting';
|
||||
selectedFood = null;
|
||||
currentTrace = '';
|
||||
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.add('detecting');
|
||||
cam.classList.remove('stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--detecting';
|
||||
document.getElementById('statusText').textContent = '正在识别...';
|
||||
document.getElementById('weightStatus').textContent = '检测中...';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status detecting';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
|
||||
// 随机食材 + 重量动画
|
||||
var item = aiResults[Math.floor(Math.random() * aiResults.length)];
|
||||
var targetWeight = (Math.random() * 4 + 1).toFixed(2) * 1;
|
||||
|
||||
var anim = 0;
|
||||
var timer = setInterval(function() {
|
||||
anim += 0.15;
|
||||
if (anim >= targetWeight) {
|
||||
anim = targetWeight;
|
||||
clearInterval(timer);
|
||||
onStable(item, targetWeight);
|
||||
}
|
||||
document.getElementById('weightValue').textContent = anim.toFixed(2);
|
||||
}, 100);
|
||||
|
||||
// 延迟显示候选
|
||||
setTimeout(function() {
|
||||
generateCandidates();
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// 稳定 → 自动选中首个候选 → 生成溯源码 → 自动录入
|
||||
function onStable(item, weight) {
|
||||
scaleState = 'stable';
|
||||
currentWeight = weight;
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.remove('detecting');
|
||||
cam.classList.add('stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--ready';
|
||||
document.getElementById('weightStatus').textContent = '重量已稳定';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status stable';
|
||||
|
||||
// 自动选中首个候选
|
||||
if (candidates.length > 0 && !selectedFood) {
|
||||
selectedFood = candidates[0];
|
||||
var cards = document.querySelectorAll('.candidate-card');
|
||||
if (cards.length > 0) cards[0].classList.add('selected');
|
||||
document.getElementById('statusText').textContent = '已识别:' + selectedFood.name;
|
||||
}
|
||||
|
||||
// 自动匹配溯源码
|
||||
currentTrace = genTraceCode();
|
||||
document.getElementById('traceCode').textContent = currentTrace;
|
||||
document.getElementById('traceStatus').innerHTML = '✅ 已匹配';
|
||||
|
||||
// 2秒后自动录入
|
||||
autoAddTimer = setTimeout(function() {
|
||||
if (scaleState === 'stable' && selectedFood) {
|
||||
addToBatch();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// ========== 批次操作 ==========
|
||||
function addToBatch() {
|
||||
if (!selectedFood || scaleState !== 'stable') return;
|
||||
var entry = {
|
||||
id: Date.now(),
|
||||
name: selectedFood.name,
|
||||
icon: selectedFood.icon,
|
||||
weight: currentWeight,
|
||||
trace: currentTrace,
|
||||
time: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
};
|
||||
batchItems.push(entry);
|
||||
renderBatchList();
|
||||
updateSummary();
|
||||
showToast(entry.name + ' ' + entry.weight.toFixed(2) + '斤 已录入', 1500, 'success');
|
||||
resetScale();
|
||||
}
|
||||
|
||||
// 删除已录入项
|
||||
function deleteBatchItem(id) {
|
||||
var idx = -1;
|
||||
for (var i = 0; i < batchItems.length; i++) {
|
||||
if (batchItems[i].id === id) { idx = i; break; }
|
||||
}
|
||||
if (idx === -1) return;
|
||||
var name = batchItems[idx].name;
|
||||
batchItems.splice(idx, 1);
|
||||
renderBatchList();
|
||||
updateSummary();
|
||||
showToast(name + ' 已删除', 1200);
|
||||
}
|
||||
|
||||
// 渲染批次清单
|
||||
function renderBatchList() {
|
||||
var list = document.getElementById('batchList');
|
||||
var empty = document.getElementById('emptyState');
|
||||
if (batchItems.length === 0) {
|
||||
empty.style.display = 'flex';
|
||||
document.getElementById('listCount').textContent = '共 0 条';
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
document.getElementById('batchCount').textContent = '0';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
document.getElementById('listCount').textContent = '共 ' + batchItems.length + ' 条';
|
||||
document.getElementById('submitBtn').disabled = false;
|
||||
document.getElementById('batchCount').textContent = batchItems.length;
|
||||
// 清空旧条目
|
||||
var existing = list.querySelectorAll('.batch-item');
|
||||
existing.forEach(function(el) { el.remove(); });
|
||||
// 渲染新条目(最新在前)
|
||||
batchItems.slice().reverse().forEach(function(item) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'batch-item';
|
||||
el.innerHTML =
|
||||
'<div class="batch-item__icon">' + item.icon + '</div>' +
|
||||
'<div class="batch-item__info">' +
|
||||
'<div class="batch-item__name">' + item.name + '</div>' +
|
||||
'<div class="batch-item__trace">' + item.trace + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="batch-item__weight">+' + item.weight.toFixed(2) + '斤</div>' +
|
||||
'<button class="batch-item__del" onclick="deleteBatchItem(' + item.id + ')" title="删除">✖</button>';
|
||||
list.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
// 更新汇总
|
||||
function updateSummary() {
|
||||
var names = {};
|
||||
var totalW = 0;
|
||||
batchItems.forEach(function(it) {
|
||||
names[it.name] = true;
|
||||
totalW += it.weight;
|
||||
});
|
||||
document.getElementById('totalItems').textContent = Object.keys(names).length;
|
||||
document.getElementById('totalWeight').textContent = totalW.toFixed(1);
|
||||
document.getElementById('totalTraced').textContent = batchItems.length;
|
||||
}
|
||||
|
||||
// 清零重置
|
||||
function resetScale() {
|
||||
scaleState = 'idle';
|
||||
currentWeight = 0;
|
||||
selectedFood = null;
|
||||
currentTrace = '';
|
||||
candidates = [];
|
||||
clearTimeout(autoAddTimer);
|
||||
|
||||
var cam = document.getElementById('cameraFrame');
|
||||
cam.classList.remove('detecting', 'stable');
|
||||
document.getElementById('statusDot').className = 'status-dot status-dot--idle';
|
||||
document.getElementById('statusText').textContent = '就绪,等待食材放置';
|
||||
document.getElementById('weightValue').textContent = '0.00';
|
||||
document.getElementById('weightStatus').textContent = '请放置食材至秤台';
|
||||
document.getElementById('weightStatus').className = 'weight-display__status';
|
||||
document.getElementById('traceCode').textContent = '--';
|
||||
document.getElementById('traceStatus').textContent = '';
|
||||
// 清空候选
|
||||
document.getElementById('candidateList').innerHTML = '';
|
||||
document.getElementById('candidateEmpty').classList.remove('hide');
|
||||
document.getElementById('candidateTitle').textContent = '智能识别候选';
|
||||
// 清空搜索
|
||||
searchInput.value = '';
|
||||
// 自动触发下一次检测
|
||||
setTimeout(function() {
|
||||
if (scaleState === 'idle') startDetection();
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
// 提交本批次
|
||||
function submitBatch() {
|
||||
if (batchItems.length === 0) return;
|
||||
var names = {};
|
||||
batchItems.forEach(function(it) { names[it.name] = true; });
|
||||
showToast('本批次提交成功:' + Object.keys(names).length + '类,共 ' + batchItems.length + '件', 3000, 'success');
|
||||
setTimeout(function() {
|
||||
batchItems = [];
|
||||
renderBatchList();
|
||||
document.getElementById('totalItems').textContent = '0';
|
||||
document.getElementById('totalWeight').textContent = '0.0';
|
||||
document.getElementById('totalTraced').textContent = '0';
|
||||
document.getElementById('batchCount').textContent = '0';
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
var batchNo = document.getElementById('batchNo');
|
||||
var seq = parseInt(batchNo.textContent.split('-')[2]) + 1;
|
||||
batchNo.textContent = 'JC-20260613-' + String(seq).padStart(3, '0');
|
||||
resetScale();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
if (type === 'error') el.classList.add('toast--error');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
|
||||
// 启动后自动开始第一次检测
|
||||
setTimeout(startDetection, 1500);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,521 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 清洗操作 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.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 {
|
||||
height: calc(100% - 44px); padding: 24px 32px; display: flex; gap: 28px;
|
||||
}
|
||||
|
||||
/* 左侧:清洗状态区 */
|
||||
.washing-panel {
|
||||
flex: 0 0 520px; display: flex; flex-direction: column; gap: 20px;
|
||||
}
|
||||
|
||||
/* 食材信息卡 */
|
||||
.ingredient-card {
|
||||
background: #1a2b4a; border-radius: 16px; padding: 24px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.ingredient-card__header {
|
||||
display: flex; align-items: center; gap: 16px; margin-bottom: 20px;
|
||||
}
|
||||
.ingredient-card__icon {
|
||||
width: 64px; height: 64px; border-radius: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 34px; background: rgba(250,173,22,0.15);
|
||||
}
|
||||
.ingredient-card__info {
|
||||
flex: 1;
|
||||
}
|
||||
.ingredient-card__name {
|
||||
font-size: 20px; font-weight: 700; color: #fff; margin-bottom: 4px;
|
||||
}
|
||||
.ingredient-card__meta {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); display: flex; gap: 12px;
|
||||
}
|
||||
.ingredient-card__batch {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 4px 10px; background: rgba(74,144,217,0.15); border-radius: 4px;
|
||||
font-size: 11px; color: #4a90d9;
|
||||
}
|
||||
|
||||
/* 清洗参数 */
|
||||
.wash-params {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
|
||||
}
|
||||
.param-item {
|
||||
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 14px 16px;
|
||||
}
|
||||
.param-item__label {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 6px;
|
||||
}
|
||||
.param-item__value {
|
||||
font-size: 18px; font-weight: 600; color: #fff;
|
||||
}
|
||||
.param-item__value--blue { color: #4a90d9; }
|
||||
.param-item__value--green { color: #52c41a; }
|
||||
|
||||
/* 清洗进度区 */
|
||||
.wash-progress {
|
||||
flex: 1; background: #1a2b4a; border-radius: 16px; padding: 28px;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 24px;
|
||||
border: 2px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.wash-progress.active {
|
||||
border-color: #4a90d9;
|
||||
}
|
||||
.wash-progress.done {
|
||||
border-color: #52c41a;
|
||||
}
|
||||
|
||||
/* 圆形进度 */
|
||||
.progress-circle {
|
||||
position: relative; width: 180px; height: 180px;
|
||||
}
|
||||
.progress-circle__svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.progress-circle__bg {
|
||||
fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 8;
|
||||
}
|
||||
.progress-circle__fill {
|
||||
fill: none; stroke: #4a90d9; stroke-width: 8;
|
||||
stroke-linecap: round; stroke-dasharray: 502; stroke-dashoffset: 502;
|
||||
transition: stroke-dashoffset 0.5s;
|
||||
}
|
||||
.progress-circle__fill.done {
|
||||
stroke: #52c41a;
|
||||
}
|
||||
.progress-circle__text {
|
||||
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
}
|
||||
.progress-circle__percent {
|
||||
font-size: 36px; font-weight: 700; color: #fff;
|
||||
}
|
||||
.progress-circle__label {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 清洗阶段 */
|
||||
.wash-stages {
|
||||
display: flex; gap: 12px; width: 100%;
|
||||
}
|
||||
.wash-stage {
|
||||
flex: 1; text-align: center; padding: 12px; border-radius: 10px;
|
||||
background: rgba(0,0,0,0.15);
|
||||
}
|
||||
.wash-stage__icon {
|
||||
font-size: 20px; margin-bottom: 6px;
|
||||
}
|
||||
.wash-stage__name {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5);
|
||||
}
|
||||
.wash-stage.active {
|
||||
background: rgba(74,144,217,0.15);
|
||||
}
|
||||
.wash-stage.active .wash-stage__name {
|
||||
color: #4a90d9;
|
||||
}
|
||||
.wash-stage.done {
|
||||
background: rgba(82,196,26,0.15);
|
||||
}
|
||||
.wash-stage.done .wash-stage__name {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.wash-actions {
|
||||
display: flex; gap: 12px; width: 100%;
|
||||
}
|
||||
.wash-btn {
|
||||
flex: 1; padding: 14px; border-radius: 10px; font-size: 14px; font-weight: 600;
|
||||
border: none; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.wash-btn--primary {
|
||||
background: #4a90d9; color: #fff;
|
||||
}
|
||||
.wash-btn--primary:hover { background: #5ba0e9; }
|
||||
.wash-btn--success {
|
||||
background: #52c41a; color: #fff;
|
||||
}
|
||||
.wash-btn--success:hover { background: #62d42a; }
|
||||
.wash-btn--secondary {
|
||||
background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.8);
|
||||
}
|
||||
.wash-btn--secondary:hover { background: rgba(255,255,255,0.12); }
|
||||
|
||||
/* 右侧:队列区 */
|
||||
.queue-panel {
|
||||
flex: 1; display: flex; flex-direction: column; gap: 16px;
|
||||
}
|
||||
|
||||
/* 待清洗队列 */
|
||||
.queue-section {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
display: flex; flex-direction: column; overflow: hidden;
|
||||
}
|
||||
.queue-header {
|
||||
display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px;
|
||||
}
|
||||
.queue-title {
|
||||
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8);
|
||||
}
|
||||
.queue-count {
|
||||
font-size: 12px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
.queue-list {
|
||||
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.queue-list::-webkit-scrollbar { width: 4px; }
|
||||
.queue-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.queue-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
.queue-item {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
padding: 12px; background: rgba(0,0,0,0.15); border-radius: 8px;
|
||||
}
|
||||
.queue-item__icon {
|
||||
width: 36px; height: 36px; border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; background: rgba(250,173,22,0.15);
|
||||
}
|
||||
.queue-item__info { flex: 1; }
|
||||
.queue-item__name {
|
||||
font-size: 13px; font-weight: 500; color: #fff;
|
||||
}
|
||||
.queue-item__weight {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4);
|
||||
}
|
||||
.queue-item__status {
|
||||
font-size: 11px; padding: 3px 8px; border-radius: 4px;
|
||||
}
|
||||
.queue-item__status--waiting { background: rgba(250,173,22,0.15); color: #faad16; }
|
||||
.queue-item__status--washing { background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
.queue-item__status--done { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
|
||||
/* 已完成统计 */
|
||||
.done-stats {
|
||||
background: #1a2b4a; border-radius: 12px; padding: 16px 20px;
|
||||
display: flex; gap: 20px;
|
||||
}
|
||||
.done-stat {
|
||||
flex: 1; text-align: center;
|
||||
}
|
||||
.done-stat__value {
|
||||
font-size: 20px; font-weight: 700; color: #52c41a;
|
||||
}
|
||||
.done-stat__label {
|
||||
font-size: 11px; color: rgba(255,255,255,0.5); margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
.toast--success { background: rgba(82,196,26,0.92); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="02-weighing.html">◀ 返回</a>
|
||||
<span class="top-bar__title">清洗操作</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 左侧:清洗状态区 -->
|
||||
<div class="washing-panel">
|
||||
<!-- 食材信息卡 -->
|
||||
<div class="ingredient-card">
|
||||
<div class="ingredient-card__header">
|
||||
<div class="ingredient-card__icon">🥔</div>
|
||||
<div class="ingredient-card__info">
|
||||
<div class="ingredient-card__name">土豆</div>
|
||||
<div class="ingredient-card__meta">
|
||||
<span>重量: 2.35斤</span>
|
||||
<span>午餐</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ingredient-card__batch">批次 #20260613-001</div>
|
||||
</div>
|
||||
|
||||
<!-- 清洗参数 -->
|
||||
<div class="wash-params">
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">水温</div>
|
||||
<div class="param-item__value param-item__value--blue">18°C</div>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">清洗时长</div>
|
||||
<div class="param-item__value param-item__value--green">8分钟</div>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">水流强度</div>
|
||||
<div class="param-item__value">中等</div>
|
||||
</div>
|
||||
<div class="param-item">
|
||||
<div class="param-item__label">消毒液浓度</div>
|
||||
<div class="param-item__value">50ppm</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 清洗进度区 -->
|
||||
<div class="wash-progress" id="washProgress">
|
||||
<!-- 圆形进度 -->
|
||||
<div class="progress-circle">
|
||||
<svg class="progress-circle__svg" width="180" height="180" viewBox="0 0 180 180">
|
||||
<circle class="progress-circle__bg" cx="90" cy="90" r="80"/>
|
||||
<circle class="progress-circle__fill" id="progressFill" cx="90" cy="90" r="80"/>
|
||||
</svg>
|
||||
<div class="progress-circle__text">
|
||||
<div class="progress-circle__percent" id="progressPercent">0%</div>
|
||||
<div class="progress-circle__label" id="progressLabel">等待开始</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 清洗阶段 -->
|
||||
<div class="wash-stages">
|
||||
<div class="wash-stage" id="stage1">
|
||||
<div class="wash-stage__icon">💧</div>
|
||||
<div class="wash-stage__name">浸泡</div>
|
||||
</div>
|
||||
<div class="wash-stage" id="stage2">
|
||||
<div class="wash-stage__icon">🌊</div>
|
||||
<div class="wash-stage__name">冲洗</div>
|
||||
</div>
|
||||
<div class="wash-stage" id="stage3">
|
||||
<div class="wash-stage__icon">✨</div>
|
||||
<div class="wash-stage__name">消毒</div>
|
||||
</div>
|
||||
<div class="wash-stage" id="stage4">
|
||||
<div class="wash-stage__icon">🌬</div>
|
||||
<div class="wash-stage__name">沥水</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="wash-actions">
|
||||
<button class="wash-btn wash-btn--secondary" onclick="skipWash()">跳过</button>
|
||||
<button class="wash-btn wash-btn--primary" id="startBtn" onclick="startWash()">开始清洗</button>
|
||||
<button class="wash-btn wash-btn--success" id="completeBtn" onclick="completeWash()" style="display:none">完成清洗</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:队列区 -->
|
||||
<div class="queue-panel">
|
||||
<!-- 已完成统计 -->
|
||||
<div class="done-stats">
|
||||
<div class="done-stat">
|
||||
<div class="done-stat__value">3</div>
|
||||
<div class="done-stat__label">今日已清洗</div>
|
||||
</div>
|
||||
<div class="done-stat">
|
||||
<div class="done-stat__value">12.5斤</div>
|
||||
<div class="done-stat__label">总重量</div>
|
||||
</div>
|
||||
<div class="done-stat">
|
||||
<div class="done-stat__value">100%</div>
|
||||
<div class="done-stat__label">合格率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待清洗队列 -->
|
||||
<div class="queue-section">
|
||||
<div class="queue-header">
|
||||
<span class="queue-title">待清洗队列</span>
|
||||
<span class="queue-count" id="queueCount">共 4 项</span>
|
||||
</div>
|
||||
<div class="queue-list">
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🍅</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">西红柿</div>
|
||||
<div class="queue-item__weight">2.8斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待清洗</span>
|
||||
</div>
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🥒</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">黄瓜</div>
|
||||
<div class="queue-item__weight">3.2斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待清洗</span>
|
||||
</div>
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🥕</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">胡萝卜</div>
|
||||
<div class="queue-item__weight">1.8斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待清洗</span>
|
||||
</div>
|
||||
<div class="queue-item">
|
||||
<div class="queue-item__icon">🥬</div>
|
||||
<div class="queue-item__info">
|
||||
<div class="queue-item__name">大白菜</div>
|
||||
<div class="queue-item__weight">4.5斤</div>
|
||||
</div>
|
||||
<span class="queue-item__status queue-item__status--waiting">待清洗</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) {
|
||||
document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
}
|
||||
|
||||
var isWashing = false;
|
||||
var washProgress = 0;
|
||||
var washInterval = null;
|
||||
|
||||
// 开始清洗
|
||||
function startWash() {
|
||||
if (isWashing) return;
|
||||
isWashing = true;
|
||||
washProgress = 0;
|
||||
|
||||
document.getElementById('startBtn').style.display = 'none';
|
||||
document.getElementById('washProgress').classList.add('active');
|
||||
|
||||
showToast('清洗开始...', 1500);
|
||||
|
||||
washInterval = setInterval(function() {
|
||||
washProgress += 1;
|
||||
|
||||
// 更新进度圆环
|
||||
var offset = 502 - (502 * washProgress / 100);
|
||||
document.getElementById('progressFill').style.strokeDashoffset = offset;
|
||||
document.getElementById('progressPercent').textContent = washProgress + '%';
|
||||
|
||||
// 更新阶段
|
||||
updateStages();
|
||||
|
||||
if (washProgress >= 100) {
|
||||
clearInterval(washInterval);
|
||||
onWashComplete();
|
||||
}
|
||||
}, 200); // 20秒完成(演示用)
|
||||
}
|
||||
|
||||
// 更新阶段状态
|
||||
function updateStages() {
|
||||
var stages = ['stage1', 'stage2', 'stage3', 'stage4'];
|
||||
var thresholds = [25, 50, 75, 100];
|
||||
|
||||
stages.forEach(function(id, idx) {
|
||||
var el = document.getElementById(id);
|
||||
el.classList.remove('active', 'done');
|
||||
if (washProgress >= thresholds[idx]) {
|
||||
el.classList.add('done');
|
||||
} else if (washProgress >= (thresholds[idx - 1] || 0)) {
|
||||
el.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// 更新标签
|
||||
if (washProgress < 25) {
|
||||
document.getElementById('progressLabel').textContent = '浸泡中...';
|
||||
} else if (washProgress < 50) {
|
||||
document.getElementById('progressLabel').textContent = '冲洗中...';
|
||||
} else if (washProgress < 75) {
|
||||
document.getElementById('progressLabel').textContent = '消毒中...';
|
||||
} else if (washProgress < 100) {
|
||||
document.getElementById('progressLabel').textContent = '沥水中...';
|
||||
} else {
|
||||
document.getElementById('progressLabel').textContent = '清洗完成';
|
||||
}
|
||||
}
|
||||
|
||||
// 清洗完成
|
||||
function onWashComplete() {
|
||||
isWashing = false;
|
||||
document.getElementById('washProgress').classList.remove('active');
|
||||
document.getElementById('washProgress').classList.add('done');
|
||||
document.getElementById('progressFill').classList.add('done');
|
||||
document.getElementById('completeBtn').style.display = 'block';
|
||||
|
||||
showToast('清洗完成,请点击"完成清洗"提交', 3000, 'success');
|
||||
}
|
||||
|
||||
// 完成清洗
|
||||
function completeWash() {
|
||||
showToast('已提交,食材已转入净菜加工区', 2500, 'success');
|
||||
setTimeout(function() {
|
||||
window.location.href = '02-weighing.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// 跳过清洗
|
||||
function skipWash() {
|
||||
if (confirm('确定跳过清洗步骤?')) {
|
||||
showToast('已跳过清洗', 1500);
|
||||
setTimeout(function() {
|
||||
window.location.href = '02-weighing.html' + (userName ? '?user=' + encodeURIComponent(userName) : '');
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, duration, type) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.className = 'toast';
|
||||
if (type === 'success') el.classList.add('toast--success');
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,486 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 操作记录 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
.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); }
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1; display: none; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.empty-state__icon { font-size: 40px; opacity: 0.3; }
|
||||
.empty-state__text { font-size: 14px; color: rgba(255,255,255,0.35); }
|
||||
.empty-state__sub { font-size: 12px; color: rgba(255,255,255,0.2); }
|
||||
|
||||
.main-content {
|
||||
height: calc(100% - 44px); padding: 16px 24px; display: flex; flex-direction: column; gap: 14px;
|
||||
}
|
||||
|
||||
/* 日期导航 */
|
||||
.date-nav {
|
||||
display: flex; align-items: center; justify-content: center; gap: 16px;
|
||||
}
|
||||
.date-nav__btn {
|
||||
width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.08); color: rgba(255,255,255,0.6);
|
||||
font-size: 16px; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.date-nav__btn:hover { background: rgba(255,255,255,0.08); color: #fff; }
|
||||
.date-nav__label {
|
||||
font-size: 15px; font-weight: 600; color: rgba(255,255,255,0.85); min-width: 140px; text-align: center;
|
||||
}
|
||||
|
||||
/* 操作类型 Tab */
|
||||
.type-tabs {
|
||||
display: flex; gap: 10px;
|
||||
}
|
||||
.type-tab {
|
||||
padding: 10px 22px; border-radius: 8px; font-size: 13px; font-weight: 500;
|
||||
background: #1a2b4a; border: 1px solid rgba(255,255,255,0.06);
|
||||
color: rgba(255,255,255,0.6); cursor: pointer; transition: all 0.2s; user-select: none;
|
||||
}
|
||||
.type-tab:hover { border-color: rgba(255,255,255,0.15); color: #fff; }
|
||||
.type-tab.active { background: #4a90d9; border-color: #4a90d9; color: #fff; }
|
||||
.type-tab__count {
|
||||
display: inline-block; margin-left: 6px; font-size: 11px; padding: 1px 6px;
|
||||
border-radius: 10px; background: rgba(255,255,255,0.15);
|
||||
}
|
||||
.type-tab.active .type-tab__count { background: rgba(255,255,255,0.25); }
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-row { display: flex; gap: 14px; }
|
||||
.stat-card {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 14px 18px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
}
|
||||
.stat-card__label { font-size: 12px; color: rgba(255,255,255,0.5); margin-bottom: 4px; }
|
||||
.stat-card__value { font-size: 22px; font-weight: 700; color: #fff; }
|
||||
.stat-card__value--blue { color: #4a90d9; }
|
||||
.stat-card__value--green { color: #52c41a; }
|
||||
|
||||
/* 记录卡片列表 */
|
||||
.records-area {
|
||||
flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;
|
||||
padding-right: 4px;
|
||||
scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.records-area::-webkit-scrollbar { width: 4px; }
|
||||
.records-area::-webkit-scrollbar-track { background: transparent; }
|
||||
.records-area::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
.record-card {
|
||||
display: flex; align-items: center; gap: 14px;
|
||||
background: #1a2b4a; border-radius: 12px; padding: 14px 18px;
|
||||
border: 1px solid rgba(255,255,255,0.06); transition: all 0.2s; cursor: pointer;
|
||||
}
|
||||
.record-card:hover {
|
||||
border-color: rgba(74,144,217,0.3); box-shadow: 0 4px 16px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* 食材图标 */
|
||||
.record-card__icon {
|
||||
width: 44px; height: 44px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 22px; flex-shrink: 0;
|
||||
}
|
||||
.record-card__icon--blue { background: rgba(74,144,217,0.15); }
|
||||
.record-card__icon--green { background: rgba(82,196,26,0.15); }
|
||||
|
||||
/* 食材信息 */
|
||||
.record-card__info { flex: 0 0 120px; }
|
||||
.record-card__name { font-size: 14px; font-weight: 600; color: #fff; margin-bottom: 3px; }
|
||||
.record-card__batch { font-size: 11px; color: rgba(255,255,255,0.4); font-family: "Courier New", monospace; }
|
||||
|
||||
/* 操作类型标签 */
|
||||
.record-card__type {
|
||||
flex-shrink: 0; padding: 4px 10px; border-radius: 4px;
|
||||
font-size: 11px; font-weight: 500; white-space: nowrap;
|
||||
}
|
||||
.record-card__type--in { background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
.record-card__type--out { background: rgba(82,196,26,0.15); color: #52c41a; }
|
||||
|
||||
/* 重量 */
|
||||
.record-card__weight {
|
||||
flex: 0 0 80px; text-align: center;
|
||||
}
|
||||
.record-card__weight-value { font-size: 16px; font-weight: 700; color: #fff; }
|
||||
.record-card__weight-unit { font-size: 11px; color: rgba(255,255,255,0.4); margin-left: 2px; }
|
||||
|
||||
/* 溯源码 */
|
||||
.record-card__trace {
|
||||
flex: 1; min-width: 0;
|
||||
}
|
||||
.record-card__trace-label { font-size: 10px; color: rgba(255,255,255,0.35); margin-bottom: 2px; }
|
||||
.record-card__trace-code {
|
||||
font-size: 12px; color: rgba(255,255,255,0.6); font-family: "Courier New", monospace;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 操作员 + 时间 */
|
||||
.record-card__meta {
|
||||
flex: 0 0 100px; text-align: right;
|
||||
}
|
||||
.record-card__operator { font-size: 12px; color: rgba(255,255,255,0.6); margin-bottom: 2px; }
|
||||
.record-card__time { font-size: 11px; color: rgba(255,255,255,0.35); }
|
||||
|
||||
/* 底部汇总 */
|
||||
.bottom-bar {
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.bottom-bar__text {
|
||||
font-size: 12px; color: rgba(255,255,255,0.35);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">操作记录</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 日期导航 -->
|
||||
<div class="date-nav">
|
||||
<button class="date-nav__btn" onclick="changeDate(-1)">◀</button>
|
||||
<span class="date-nav__label" id="dateLabel">2026-06-13</span>
|
||||
<button class="date-nav__btn" onclick="changeDate(1)">▶</button>
|
||||
</div>
|
||||
|
||||
<!-- 操作类型 Tab -->
|
||||
<div class="type-tabs">
|
||||
<div class="type-tab active" data-type="all" onclick="filterType('all', this)">
|
||||
全部<span class="type-tab__count" id="countAll">8</span>
|
||||
</div>
|
||||
<div class="type-tab" data-type="in" onclick="filterType('in', this)">
|
||||
来料过秤<span class="type-tab__count" id="countIn">8</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">总操作次数</div>
|
||||
<div class="stat-card__value stat-card__value--blue" id="statTotal">8</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card__label">总重量(斤)</div>
|
||||
<div class="stat-card__value" id="statWeight">33.3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态(非当日日期) -->
|
||||
<div class="empty-state" id="emptyState">
|
||||
<div class="empty-state__icon">📅</div>
|
||||
<div class="empty-state__text">该日期暂无操作记录</div>
|
||||
<div class="empty-state__sub">仅展示当日(2026-06-13)的过秤数据</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录卡片列表 -->
|
||||
<div class="records-area" id="recordsArea">
|
||||
<!-- 记录项1: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🥔</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">土豆</div>
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">5.2</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130001</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">09:15</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项2: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🍅</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">西红柿</div>
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">3.8</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130002</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">09:18</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项3: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🥬</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">大白菜</div>
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">8.2</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130003</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">09:22</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项4: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🥕</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">胡萝卜</div>
|
||||
<div class="record-card__batch">LC-20260613-001</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">2.5</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130004</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">09:25</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项5: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🥒</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">黄瓜</div>
|
||||
<div class="record-card__batch">LC-20260613-002</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">4.3</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130005</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">10:05</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项6: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🥬</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">娃娃菜</div>
|
||||
<div class="record-card__batch">LC-20260613-002</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">2.6</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130006</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">10:08</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项7: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🥔</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">土豆</div>
|
||||
<div class="record-card__batch">LC-20260613-003</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">3.2</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130007</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">14:10</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记录项8: 来料过秤 -->
|
||||
<div class="record-card" data-type="in">
|
||||
<div class="record-card__icon record-card__icon--blue">🍅</div>
|
||||
<div class="record-card__info">
|
||||
<div class="record-card__name">西红柿</div>
|
||||
<div class="record-card__batch">LC-20260613-003</div>
|
||||
</div>
|
||||
<div class="record-card__type record-card__type--in">来料过秤</div>
|
||||
<div class="record-card__weight">
|
||||
<span class="record-card__weight-value">3.5</span>
|
||||
<span class="record-card__weight-unit">斤</span>
|
||||
</div>
|
||||
<div class="record-card__trace">
|
||||
<div class="record-card__trace-label">溯源码</div>
|
||||
<div class="record-card__trace-code">SC202606130008</div>
|
||||
</div>
|
||||
<div class="record-card__meta">
|
||||
<div class="record-card__operator">张三</div>
|
||||
<div class="record-card__time">14:15</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 底部汇总 -->
|
||||
<div class="bottom-bar">
|
||||
<span class="bottom-bar__text" id="bottomText">共 8 条记录</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) {
|
||||
document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function handleLogout() {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
|
||||
// 日期切换
|
||||
var currentDate = new Date('2026-06-13');
|
||||
var todayStr = '2026-06-13';
|
||||
function changeDate(delta) {
|
||||
currentDate.setDate(currentDate.getDate() + delta);
|
||||
var y = currentDate.getFullYear();
|
||||
var m = String(currentDate.getMonth() + 1).padStart(2, '0');
|
||||
var d = String(currentDate.getDate()).padStart(2, '0');
|
||||
var dateStr = y + '-' + m + '-' + d;
|
||||
document.getElementById('dateLabel').textContent = dateStr;
|
||||
|
||||
var recordsArea = document.getElementById('recordsArea');
|
||||
var emptyState = document.getElementById('emptyState');
|
||||
var statsRow = document.querySelector('.stats-row');
|
||||
var bottomBar = document.querySelector('.bottom-bar');
|
||||
|
||||
if (dateStr === todayStr) {
|
||||
// 当日:显示记录
|
||||
recordsArea.style.display = 'flex';
|
||||
emptyState.style.display = 'none';
|
||||
statsRow.style.display = 'flex';
|
||||
bottomBar.style.display = 'flex';
|
||||
showToast('已切换至 ' + dateStr, 1200);
|
||||
} else {
|
||||
// 非当日:空状态
|
||||
recordsArea.style.display = 'none';
|
||||
emptyState.style.display = 'flex';
|
||||
statsRow.style.display = 'none';
|
||||
bottomBar.style.display = 'none';
|
||||
showToast('已切换至 ' + dateStr + ',暂无记录', 1200);
|
||||
}
|
||||
}
|
||||
|
||||
// 操作类型筛选
|
||||
function filterType(type, tab) {
|
||||
document.querySelectorAll('.type-tab').forEach(function(el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
tab.classList.add('active');
|
||||
|
||||
var cards = document.querySelectorAll('.record-card');
|
||||
var visibleCount = 0;
|
||||
cards.forEach(function(card) {
|
||||
if (type === 'all' || card.dataset.type === type) {
|
||||
card.style.display = 'flex';
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
document.getElementById('bottomText').textContent = '共 ' + visibleCount + ' 条记录';
|
||||
}
|
||||
|
||||
// Toast
|
||||
function showToast(msg, duration) {
|
||||
var el = document.getElementById('toast');
|
||||
el.textContent = msg;
|
||||
el.classList.add('show');
|
||||
clearTimeout(el._timer);
|
||||
el._timer = setTimeout(function() { el.classList.remove('show'); }, duration || 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,226 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1280, initial-scale=1.0">
|
||||
<title>加工毛菜秤 - 设备设置 | 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.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);
|
||||
}
|
||||
.logout-btn {
|
||||
font-size: 12px; color: rgba(255,255,255,0.5); cursor: pointer;
|
||||
padding: 4px 12px; border-radius: 4px; transition: all 0.2s;
|
||||
border: 1px solid rgba(255,255,255,0.15); background: none; margin-left: 10px;
|
||||
}
|
||||
.logout-btn:hover { color: #fff; border-color: rgba(255,255,255,0.3); }
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
height: calc(100% - 44px); padding: 24px 32px; display: flex; gap: 24px;
|
||||
}
|
||||
|
||||
/* 左侧:设置菜单 */
|
||||
.settings-menu {
|
||||
flex: 0 0 200px; background: #1a2b4a; border-radius: 12px; padding: 12px;
|
||||
display: flex; flex-direction: column; gap: 4px;
|
||||
}
|
||||
.menu-item {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 12px 16px; border-radius: 8px; cursor: pointer;
|
||||
font-size: 13px; color: rgba(255,255,255,0.6);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.menu-item:hover { background: rgba(255,255,255,0.06); color: #fff; }
|
||||
.menu-item.active { background: rgba(74,144,217,0.15); color: #4a90d9; }
|
||||
.menu-item__icon { font-size: 16px; }
|
||||
|
||||
/* 右侧:设置内容 */
|
||||
.settings-content {
|
||||
flex: 1; background: #1a2b4a; border-radius: 12px; padding: 24px 28px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,255,255,0.1) transparent;
|
||||
}
|
||||
.settings-content::-webkit-scrollbar { width: 4px; }
|
||||
.settings-content::-webkit-scrollbar-track { background: transparent; }
|
||||
.settings-content::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.settings-section:last-child { margin-bottom: 0; }
|
||||
.section-title {
|
||||
font-size: 14px; font-weight: 600; color: rgba(255,255,255,0.8);
|
||||
padding-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 设备信息 */
|
||||
.device-info {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
|
||||
}
|
||||
.info-item {
|
||||
background: rgba(0,0,0,0.15); border-radius: 10px; padding: 14px 16px;
|
||||
}
|
||||
.info-item__label {
|
||||
font-size: 11px; color: rgba(255,255,255,0.4); margin-bottom: 4px;
|
||||
}
|
||||
.info-item__value {
|
||||
font-size: 14px; color: #fff; font-weight: 500;
|
||||
}
|
||||
.info-item__value--online { color: #52c41a; }
|
||||
|
||||
/* 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;
|
||||
font-size: 13px; z-index: 1000; opacity: 0; visibility: hidden;
|
||||
transition: opacity 0.3s, transform 0.3s, visibility 0.3s;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="00-welcome.html">◀ 返回</a>
|
||||
<span class="top-bar__title">设备设置</span>
|
||||
<span class="top-bar__user" id="topUser">--</span>
|
||||
<button class="logout-btn" onclick="handleLogout()">退出登录</button>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 左侧:设置菜单 -->
|
||||
<div class="settings-menu">
|
||||
<div class="menu-item active" onclick="switchSection('device', this)">
|
||||
<span class="menu-item__icon">💻</span>
|
||||
<span>设备信息</span>
|
||||
</div>
|
||||
<div class="menu-item" onclick="switchSection('about', this)">
|
||||
<span class="menu-item__icon">ℹ️</span>
|
||||
<span>关于</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:设置内容 -->
|
||||
<div class="settings-content">
|
||||
<!-- 设备信息(默认显示) -->
|
||||
<div class="settings-section" id="section-device">
|
||||
<div class="section-title">设备信息</div>
|
||||
|
||||
<div class="device-info">
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">设备名称</div>
|
||||
<div class="info-item__value">加工毛菜秤-01</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">设备编号</div>
|
||||
<div class="info-item__value">GJMC-2026-001</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">固件版本</div>
|
||||
<div class="info-item__value">v2.3.1</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">AI模型版本</div>
|
||||
<div class="info-item__value">veg-ai-v4.2</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">设备状态</div>
|
||||
<div class="info-item__value info-item__value--online">在线</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">网络状态</div>
|
||||
<div class="info-item__value info-item__value--online">已连接</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">上次校准</div>
|
||||
<div class="info-item__value">2026-06-10</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">累计使用</div>
|
||||
<div class="info-item__value">186天</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关于(默认隐藏) -->
|
||||
<div class="settings-section" id="section-about" style="display:none">
|
||||
<div class="section-title">关于</div>
|
||||
<div class="device-info">
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">系统名称</div>
|
||||
<div class="info-item__value">蚁熊智能餐养平台</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">系统版本</div>
|
||||
<div class="info-item__value">v5.8.0</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">设备类型</div>
|
||||
<div class="info-item__value">加工毛菜秤</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-item__label">制造商</div>
|
||||
<div class="info-item__value">蚁熊智能科技有限公司</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
// 获取 URL 参数
|
||||
function getUrlParam(name) {
|
||||
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
|
||||
var r = window.location.search.substr(1).match(reg);
|
||||
if (r !== null) return decodeURIComponent(r[2]);
|
||||
return null;
|
||||
}
|
||||
|
||||
var userName = getUrlParam('user');
|
||||
if (userName) {
|
||||
document.getElementById('topUser').textContent = '操作员: ' + userName;
|
||||
}
|
||||
|
||||
// 切换设置区域
|
||||
function switchSection(section, el) {
|
||||
document.querySelectorAll('.menu-item').forEach(function(item) {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
el.classList.add('active');
|
||||
|
||||
document.querySelectorAll('.settings-section').forEach(function(sec) {
|
||||
sec.style.display = 'none';
|
||||
});
|
||||
document.getElementById('section-' + section).style.display = 'block';
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
function handleLogout() {
|
||||
window.location.href = '00-welcome.html';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,19 @@
|
||||
# 加工毛菜秤 CHANGELOG
|
||||
|
||||
## [2026-06-15] feat: 新增重新识别按钮
|
||||
|
||||
### 新增
|
||||
- **重新识别按钮**(`02-batch-weighing.html`):AI 识别区新增「🔄 重新识别」按钮(位于识别状态下方),点击后重置称重状态、清空候选列表并重新触发 AI 识别。
|
||||
|
||||
---
|
||||
|
||||
## [1.0.0] - 2026-06-13
|
||||
|
||||
### 新增
|
||||
- 首次创建加工毛菜秤模块(6个页面)
|
||||
- `00-welcome.html` — 欢迎页:人脸识别+账号密码登录+功能菜单
|
||||
- `01-washing-tasks.html` — 今日清洗任务列表:任务卡片、状态筛选、进度展示
|
||||
- `02-weighing.html` — 称重录入:AI识别品类、实时称重、任务匹配/不匹配Toast提示、手动确认提交
|
||||
- `03-washing.html` — 清洗操作:圆形进度、四阶段(浸泡/冲洗/消毒/沥水)、清洗参数展示、待清洗队列
|
||||
- `04-history.html` — 清洗记录:日期/食材/餐别筛选、统计卡片、表格记录、分页
|
||||
- `05-settings.html` — 设备设置:清洗参数/AI识别/秤参数/设备信息/关于 五个配置区
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,184 @@
|
||||
# 加工毛菜秤 — 业务流程文档
|
||||
|
||||
> 创建时间:2026-06-15
|
||||
|
||||
---
|
||||
|
||||
## 一、整体业务流程
|
||||
|
||||
后台定时整合毛菜加工任务,推送到终端设备;终端自动检测新任务并提示,员工按需进入处理,完成称重录入后任务自动标记为已完成。
|
||||
|
||||
```
|
||||
后台系统
|
||||
│
|
||||
│ 定时整合加工任务,推送到终端
|
||||
▼
|
||||
00-welcome(欢迎页)
|
||||
│
|
||||
│ 定时轮询(每 30 秒)
|
||||
├─ 无新任务 → 按钮静默,正常等待
|
||||
└─ 有新任务 → 按钮蓝色边框 + 脉冲动画 + 数量角标
|
||||
Toast:「⚡ 新任务已到达」
|
||||
↓
|
||||
员工点击「今日清洗任务」
|
||||
↓
|
||||
01-washing-tasks(今日任务页)
|
||||
│ 进入时立即拉取任务列表
|
||||
│ 之后每 30 秒自动刷新检测增量任务
|
||||
│ 新任务到达 → Toast 提示 + 新卡片高亮 3 秒
|
||||
│
|
||||
│ 以「任务单」为单位展示(一个任务单含多行食材)
|
||||
│ 每行食材:名称 + 需求量 + 截止时间(超时标红)
|
||||
│ 每个任务单卡片底部含「处理」按钮
|
||||
│ 已完成任务单:卡片灰化 + ✓ 已完成 标记
|
||||
↓
|
||||
员工点击某任务单「处理」
|
||||
↓
|
||||
02-batch-weighing?user=xxx&taskNo=RW-xxx
|
||||
│ 左侧:AI 识别 + 称重区
|
||||
│ 右侧:批次清单 + 任务信息条
|
||||
│ 任务信息条:关联任务号 | 应录入 X 斤 | 已录入 X 斤(实时更新)
|
||||
│
|
||||
│ 操作流程:
|
||||
│ ① 按任务单取货,放上秤盘
|
||||
│ ② AI 自动识别食材 → 自动匹配溯源码 → 自动录入
|
||||
│ ③ 重复①②,直到本批次全部录入
|
||||
│ ④ 点击「提交本批次」→ 提交成功
|
||||
↓
|
||||
自动返回 01-washing-tasks
|
||||
└─ 该任务状态更新为「已完成」
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、任务推送机制
|
||||
|
||||
### 2.1 推送流程
|
||||
|
||||
```
|
||||
后台系统(定时任务)
|
||||
│
|
||||
│ 每隔 N 分钟整合一次加工任务
|
||||
▼
|
||||
任务推送接口(POST /api/tasks/push)
|
||||
│
|
||||
│ 推送到终端设备(HTTP 长轮询 / WebSocket)
|
||||
▼
|
||||
终端接收任务
|
||||
│
|
||||
├─ 00-welcome 轮询:每 30 秒 GET /api/tasks/pending
|
||||
│ └─ 有待处理任务 → 更新按钮状态
|
||||
│
|
||||
└─ 01-washing-tasks 轮询:每 30 秒 GET /api/tasks/today
|
||||
└─ 有新增任务 → 追加到列表 + Toast 提示
|
||||
```
|
||||
|
||||
### 2.2 终端检测策略
|
||||
|
||||
| 页面 | 轮询间隔 | 检测接口 | 响应行为 |
|
||||
|------|---------|---------|---------|
|
||||
| 00-welcome | 30 秒 | `/api/tasks/pending` | 按钮变色 + 脉冲动画 + 角标 + Toast |
|
||||
| 01-washing-tasks | 30 秒 | `/api/tasks/today` | 追加新任务卡片 + Toast + 高亮入场 |
|
||||
|
||||
### 2.3 任务状态流转
|
||||
|
||||
```
|
||||
待处理(pending)
|
||||
│ 员工点击「处理」→ 进入录入
|
||||
▼
|
||||
进行中(processing)
|
||||
│ 员工提交本批次
|
||||
▼
|
||||
已完成(completed)
|
||||
│ 01 任务列表自动刷新
|
||||
└─ 卡片灰化 + ✓ 标记
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、页面职责与交互
|
||||
|
||||
### 3.1 00-welcome(欢迎页)
|
||||
|
||||
**职责**:员工登录 + 任务到达提示入口
|
||||
|
||||
**关键行为**:
|
||||
- 登录成功后启动定时轮询(每 30 秒)
|
||||
- 「今日清洗任务」按钮状态:
|
||||
- 无待处理任务:默认样式,正常可点击
|
||||
- 有待处理任务:蓝色边框 + 脉冲动画 + 右上角数量角标
|
||||
- 新任务到达时 Toast 提示「⚡ 新任务已到达」
|
||||
|
||||
### 3.2 01-washing-tasks(今日任务页)
|
||||
|
||||
**职责**:任务列表展示 + 任务入口
|
||||
|
||||
**任务单卡片结构**:
|
||||
| 区域 | 内容 |
|
||||
|------|------|
|
||||
| 头部 | 任务单编号(左)+ 状态徽章(右) |
|
||||
| 主体 | 多行食材,每行:食材图标 + 名称 + 需求量 + 截止时间(超时标红) |
|
||||
| 底部 | 「处理」按钮(右对齐,仅待处理任务单显示) |
|
||||
|
||||
**关键行为**:
|
||||
- 进入时立即拉取最新任务列表
|
||||
- 之后每 30 秒自动刷新,检测增量任务
|
||||
- 新任务到达:Toast 提示,新卡片高亮 3 秒后恢复正常
|
||||
- 任务完成后刷新列表,该任务单标记为「已完成」(灰化 + ✓)
|
||||
|
||||
### 3.3 02-batch-weighing(来料过秤页)
|
||||
|
||||
**职责**:核心称重录入操作
|
||||
|
||||
**任务信息条**(从 01 携带 taskNo 进入时显示):
|
||||
|
||||
```
|
||||
关联任务 RW-20260613-001 | 应录入 5.0 斤 | 已录入 0.0 斤
|
||||
```
|
||||
|
||||
| 字段 | 颜色 | 数据来源 |
|
||||
|------|------|---------|
|
||||
| 关联任务 | 蓝色 | URL 参数 taskNo |
|
||||
| 应录入 | 橙色 `#faad14` | 任务数据(后台推送) |
|
||||
| 已录入 | 绿色 `#52c41a` | 实时称重累计(页面内更新) |
|
||||
|
||||
---
|
||||
|
||||
## 四、任务数据结构
|
||||
|
||||
```javascript
|
||||
// 任务单数据示例(后台推送格式)
|
||||
{
|
||||
taskNo: "RW-20260613-001", // 任务单编号
|
||||
status: "pending", // pending / processing / completed
|
||||
ingredients: [ // 食材行列表(按截止时间排序)
|
||||
{ name: "黄瓜", requiredWeight: 4.0, deadline: "10:00" },
|
||||
{ name: "土豆", requiredWeight: 5.0, deadline: "10:30" },
|
||||
{ name: "西红柿", requiredWeight: 3.5, deadline: "11:00" },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**任务编号格式**:`RW-YYYYMMDD-NNN`(RW = 任务,当日 3 位序号)
|
||||
|
||||
---
|
||||
|
||||
## 五、页面跳转关系总览
|
||||
|
||||
```
|
||||
00-welcome(欢迎页)
|
||||
│
|
||||
├─ 今日清洗任务 → 01-washing-tasks(携带 user)
|
||||
│ │
|
||||
│ ├─ 点击「处理」→ 02-batch-weighing(携带 user + taskNo)
|
||||
│ │ │
|
||||
│ │ └─ 提交本批次 → 返回 01(任务标记已完成)
|
||||
│ │
|
||||
│ └─ 定时刷新(每 30 秒,检测增量任务)
|
||||
│
|
||||
├─ 来料过秤 → 02-batch-weighing(无 taskNo)
|
||||
│
|
||||
├─ 操作记录 → 04-history
|
||||
│
|
||||
└─ 设备设置 → 05-settings
|
||||
```
|
||||
@@ -0,0 +1,354 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""将 PRD.md 转换为 Word 文档"""
|
||||
from docx import Document
|
||||
from docx.shared import Pt, Cm, RGBColor
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.oxml.ns import qn
|
||||
|
||||
doc = Document()
|
||||
|
||||
# 页面边距
|
||||
for section in doc.sections:
|
||||
section.top_margin = Cm(2.54)
|
||||
section.bottom_margin = Cm(2.54)
|
||||
section.left_margin = Cm(3.18)
|
||||
section.right_margin = Cm(3.18)
|
||||
|
||||
def set_run(run, size=10.5, bold=False, color=None):
|
||||
run.font.size = Pt(size)
|
||||
run.bold = bold
|
||||
run.font.name = "微软雅黑"
|
||||
run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
||||
if color:
|
||||
run.font.color.rgb = RGBColor(*color)
|
||||
|
||||
def h1(text):
|
||||
h = doc.add_heading(text, level=1)
|
||||
for r in h.runs:
|
||||
r.font.name = "微软雅黑"
|
||||
r._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
||||
|
||||
def h2(text):
|
||||
h = doc.add_heading(text, level=2)
|
||||
for r in h.runs:
|
||||
r.font.name = "微软雅黑"
|
||||
r._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
||||
|
||||
def h3(text):
|
||||
h = doc.add_heading(text, level=3)
|
||||
for r in h.runs:
|
||||
r.font.name = "微软雅黑"
|
||||
r._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
||||
|
||||
def p(text, bold=False, size=10.5, color=None, indent=False):
|
||||
para = doc.add_paragraph()
|
||||
run = para.add_run(text)
|
||||
set_run(run, size, bold, color)
|
||||
if indent:
|
||||
para.paragraph_format.first_line_indent = Cm(0.74)
|
||||
return para
|
||||
|
||||
def bullet(text):
|
||||
para = doc.add_paragraph(style="List Bullet")
|
||||
para.clear()
|
||||
run = para.add_run(text)
|
||||
set_run(run, 10.5)
|
||||
return para
|
||||
|
||||
def table(headers, rows):
|
||||
t = doc.add_table(rows=1 + len(rows), cols=len(headers))
|
||||
t.style = "Light Grid Accent 1"
|
||||
t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
for i, h in enumerate(headers):
|
||||
cell = t.rows[0].cells[i]
|
||||
cell.text = ""
|
||||
run = cell.paragraphs[0].add_run(h)
|
||||
set_run(run, 10, bold=True)
|
||||
for ri, row in enumerate(rows):
|
||||
for ci, val in enumerate(row):
|
||||
cell = t.rows[ri + 1].cells[ci]
|
||||
cell.text = ""
|
||||
run = cell.paragraphs[0].add_run(str(val))
|
||||
set_run(run, 9.5)
|
||||
doc.add_paragraph() # 表后空行
|
||||
|
||||
def note(text):
|
||||
para = doc.add_paragraph()
|
||||
run = para.add_run(text)
|
||||
set_run(run, 9.5, color=(102, 102, 102))
|
||||
return para
|
||||
|
||||
# ========== 正文 ==========
|
||||
|
||||
doc.add_heading("加工毛菜秤 — 产品需求文档", level=0)
|
||||
for r in doc.paragraphs[0].runs:
|
||||
r.font.name = "微软雅黑"
|
||||
r._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
||||
|
||||
note("模块位置:other-module/canteen-device/raw-veg-processing-scale/")
|
||||
|
||||
# ===== 一、模块概述 =====
|
||||
h1("一、模块概述")
|
||||
p(
|
||||
"加工毛菜秤是食堂后厨加工间的称重设备(1280x720 PAD),核心功能覆盖员工登录、"
|
||||
"今日清洗任务查看、来料过秤(AI 图像识别 + 称重 + 溯源码自动匹配)、操作记录查询、"
|
||||
"设备参数配置等。设备仅处理「清洗前」的来料过秤环节,不涉及净菜过秤。",
|
||||
indent=True
|
||||
)
|
||||
|
||||
h2("1.1 页面导航结构")
|
||||
table(
|
||||
["起点", "终点", "触发条件"],
|
||||
[
|
||||
["00 欢迎页", "01 今日清洗任务", "登录成功 → 今日任务(携带 user)"],
|
||||
["00 欢迎页", "02 来料过秤", "登录成功 → 来料过秤"],
|
||||
["00 欢迎页", "04 操作记录", "登录成功 → 操作记录"],
|
||||
["00 欢迎页", "05 设备设置", "登录成功 → 设备设置"],
|
||||
["01 今日任务", "02 来料过秤", "任务卡片「处理」按钮(携带 user + taskNo)"],
|
||||
["02 来料过秤", "01 今日任务", "提交本批次 → 自动返回并刷新列表"],
|
||||
["01/02/04/05", "00 欢迎页", "返回 / 退出登录"],
|
||||
]
|
||||
)
|
||||
|
||||
h2("1.3 任务推送与整体业务流程")
|
||||
p("后台定时整合毛菜加工任务并推送到终端,终端自动检测并提示,员工按需进入处理。", indent=True)
|
||||
p("完整流程:", bold=True)
|
||||
bullet("1. 后台定时整合任务,推送到终端设备")
|
||||
bullet("2. 00 欢迎页:定时轮询(每 30 秒)检测新任务")
|
||||
bullet("3. 检测到待处理任务 → 今日任务按钮变色(蓝色边框 + 脉冲动画)+ 显示任务数量角标")
|
||||
bullet("4. 员工点击「今日清洗任务」→ 进入 01 任务列表")
|
||||
bullet("5. 01 任务列表:以任务单为单位展示,每个任务单含多行食材,底部含「处理」按钮")
|
||||
bullet("6. 员工点击某任务单「处理」→ 跳转 02 来料过秤(携带 taskNo)")
|
||||
bullet("7. 02 页面自动加载任务数据(显示应录入重量),按任务单取货、上秤、AI 识别、逐项录入")
|
||||
bullet("8. 提交本批次 → 自动返回 01 → 该任务标记为已完成")
|
||||
p("新任务推送提示:", bold=True)
|
||||
bullet("00 欢迎页:按钮脉冲动画 + 角标数字,Toast「⚡ 新任务已到达」")
|
||||
bullet("01 任务列表:新任务到达时 Toast 提示,新卡片高亮 3 秒后恢复")
|
||||
|
||||
h2("1.2 状态流转")
|
||||
p("详见 Mermaid 状态图(PRD.md 第 1.2 节)。")
|
||||
|
||||
# ===== 二、角色定义 =====
|
||||
h1("二、角色定义")
|
||||
table(
|
||||
["角色", "说明", "权限"],
|
||||
[
|
||||
["清洗员", "加工车间员工,负责来料过秤", "登录、查看任务、来料过秤、查看操作记录"],
|
||||
["质检员", "质检部门员工", "登录、查看操作记录、设备设置"],
|
||||
]
|
||||
)
|
||||
|
||||
h2("2.1 登录方式")
|
||||
table(
|
||||
["方式", "说明"],
|
||||
[
|
||||
["人脸识别", "点击激活 -> 2 秒识别动画 -> 随机匹配模拟用户 -> 登录成功"],
|
||||
["账号密码", "输入员工工号 + 密码 -> 校验通过 -> 登录成功"],
|
||||
]
|
||||
)
|
||||
|
||||
h2("2.2 模拟用户数据")
|
||||
table(
|
||||
["姓名", "工号", "员工ID", "部门", "角色"],
|
||||
[
|
||||
["张三", "10001", "EMP-20260001", "加工车间", "清洗员"],
|
||||
["李四", "10002", "EMP-20260015", "加工车间", "清洗员"],
|
||||
["王五", "10003", "EMP-20260023", "质检部", "质检员"],
|
||||
]
|
||||
)
|
||||
|
||||
# ===== 三、页面清单 =====
|
||||
h1("三、页面清单")
|
||||
table(
|
||||
["序号", "文件", "页面名称", "说明"],
|
||||
[
|
||||
["1", "00-welcome.html", "欢迎页面", "员工登录 + 功能菜单入口"],
|
||||
["2", "01-washing-tasks.html", "今日清洗任务", "当日待处理任务列表,含流程环节指示"],
|
||||
["3", "02-batch-weighing.html", "来料过秤", "AI 识别 + 称重 + 溯源码匹配 + 批次录入"],
|
||||
["4", "04-history.html", "操作记录", "过秤操作流水记录,支持日期/类型筛选"],
|
||||
["5", "05-settings.html", "设备设置", "清洗参数、AI 识别、秤参数、设备信息配置"],
|
||||
]
|
||||
)
|
||||
|
||||
# ===== 四、逐页说明 =====
|
||||
h1("四、逐页说明")
|
||||
|
||||
# 4.1
|
||||
h2("4.1 欢迎页面")
|
||||
note("文件:00-welcome.html")
|
||||
h3("功能说明")
|
||||
p("设备开机后的首个页面,提供员工登录和功能菜单两大区域。")
|
||||
bullet("顶部导航栏:返回按钮(返回餐线设备导航页)+ 页面标题「加工毛菜秤」")
|
||||
bullet("左侧登录面板(420px 宽):登录方式切换 Tab、人脸识别区、账号密码表单")
|
||||
bullet("右侧功能菜单(4 个按钮):今日任务、来料过秤、操作记录、设备设置")
|
||||
bullet("「今日任务」按钮支持自动刷新:每 30 秒轮询一次后台任务推送接口")
|
||||
note("注:欢迎页本身即为登录页,无需「退出登录」按钮。")
|
||||
|
||||
h3("业务规则")
|
||||
bullet("未登录状态下,4 个菜单按钮均为禁用态(opacity: 0.4,不可点击)")
|
||||
bullet("登录成功后按钮启用,顶部显示「当前用户:XXX」")
|
||||
bullet("人脸识别为模拟流程:点击后 2 秒随机匹配一个模拟用户")
|
||||
bullet("账号密码校验:匹配模拟用户数据中的工号和密码")
|
||||
bullet("「今日任务」按钮自动刷新:每 30 秒检测一次是否有新任务到达")
|
||||
bullet("有待处理任务时:按钮边框变蓝色 + 脉冲动画,右上角显示任务数量角标")
|
||||
bullet("新任务到达时:Toast 提示「⚡ 新任务已到达」")
|
||||
|
||||
h3("交互说明")
|
||||
bullet("点击「人脸识别」/「账号密码」Tab 切换登录方式")
|
||||
bullet("点击人脸识别圆形框激活识别 -> 2 秒后登录成功 -> Toast「欢迎,XXX」")
|
||||
bullet("输入工号密码后点击「登录」-> 校验通过 -> 登录成功 / 校验失败 -> Toast「工号或密码错误」")
|
||||
bullet("点击菜单按钮 -> 未登录时 Toast「请先登录」-> 登录后跳转对应页面(URL 携带 user 和 uid 参数)")
|
||||
bullet("「今日任务」按钮:有待处理任务时边框蓝色脉冲 + 角标显示数量,点击跳转 01(携带 user)")
|
||||
|
||||
# 4.2
|
||||
h2("4.2 今日清洗任务")
|
||||
note("文件:01-washing-tasks.html")
|
||||
h3("功能说明")
|
||||
p("展示当日系统分配的毛菜清洗任务列表,以「任务单」为单位组织。")
|
||||
bullet("顶部操作区:状态筛选 Tab(待处理橙色/已完成绿色)+ 「退出登录」按钮")
|
||||
bullet("每个任务单卡片含多行食材,每行:食材图标 + 名称 + 需求量 + 截止时间(超时标红)")
|
||||
bullet("任务单卡片头部显示任务单编号(左)+ 状态徽章(右)")
|
||||
bullet("每个任务单卡片底部含「处理」按钮(右对齐),点击跳转来料过秤页(URL 携带 user + taskNo)")
|
||||
bullet("已完成任务单卡片:整体灰化,底部显示「✓ 已完成」标记")
|
||||
bullet("页面进入时自动拉取任务列表,之后每 30 秒自动刷新一次检测增量任务")
|
||||
|
||||
h3("业务规则")
|
||||
bullet("模拟 3 个任务单:2 个待处理 + 1 个已完成")
|
||||
bullet("任务编号格式:RW-YYYYMMDD-NNN")
|
||||
bullet("新任务到达时 Toast 提示,新任务单卡片高亮 3 秒后恢复正常样式")
|
||||
bullet("任务提交后自动刷新列表,该任务单标记为「已完成」")
|
||||
|
||||
h3("交互说明")
|
||||
bullet("点击「待处理」/「已完成」Tab -> 筛选对应状态的任务单列表")
|
||||
bullet("点击任务单「处理」按钮 -> 跳转到「来料过秤」页面(URL 携带 user 和 taskNo 参数)")
|
||||
bullet("新任务到达 -> Toast「新任务单已到达」-> 新卡片高亮入场")
|
||||
|
||||
# 4.3
|
||||
h2("4.3 来料过秤(清洗前)")
|
||||
note("文件:02-batch-weighing.html")
|
||||
h3("功能说明")
|
||||
p("核心操作页面。左右布局:左侧识别称重区,右侧批次清单管理区。")
|
||||
|
||||
p("左侧 — 识别区(440px 宽):", bold=True)
|
||||
table(
|
||||
["区域", "说明"],
|
||||
[
|
||||
["任务关联条", "显示关联任务编号 + 应录入重量(橙)+ 已录入重量(绿,实时更新);未从任务页进入则隐藏"],
|
||||
["批次信息条", "当前批次号(LC-YYYYMMDD-NNN)+ 已录入件数"],
|
||||
["摄像头预览框", "150px 高,四角标记(蓝色),十字辅助线"],
|
||||
["识别状态条", "状态指示灯(灰/黄闪/绿)+ 状态文字"],
|
||||
["搜索栏", "搜索毛菜名称,回车确认"],
|
||||
["候选食材列表", "2 列网格,每个含图标、名称、相似度百分比"],
|
||||
["溯源码行", "标签 + 溯源码(SC+YYYYMMDD+4位序号)+ 匹配状态"],
|
||||
["重量显示", "大号数字(42px)+ 单位(斤)+ 状态文字"],
|
||||
]
|
||||
)
|
||||
|
||||
p("右侧 — 批次清单区:", bold=True)
|
||||
table(
|
||||
["区域", "说明"],
|
||||
[
|
||||
["汇总卡片", "品类数 / 总重量(斤)/ 已匹配溯源数"],
|
||||
["清单列表", "每条:食材图标 + 名称 + 溯源码 + 重量 + 删除按钮"],
|
||||
["提交栏", "提示文字 + 「提交本批次」按钮(绿色)"],
|
||||
]
|
||||
)
|
||||
|
||||
h3("业务规则")
|
||||
bullet("批次号格式:LC-YYYYMMDD-NNN")
|
||||
bullet("溯源码格式:SC + YYYYMMDD + 4 位流水号,自动匹配")
|
||||
p("AI 识别流程:", bold=True)
|
||||
bullet("1. 页面加载后 1.5 秒自动开始首次检测")
|
||||
bullet("2. 食材放置 -> 摄像头开始识别(黄色闪烁指示灯)")
|
||||
bullet("3. 重量递增动画 -> 稳定后指示灯变绿")
|
||||
bullet("4. 延迟 0.8 秒后自动生成 4 个候选食材,每个含相似度百分比(93%~98%)")
|
||||
bullet("5. 自动选中相似度最高的候选")
|
||||
bullet("6. 自动匹配溯源码")
|
||||
bullet("7. 重量稳定 + 已选食材 -> 2 秒后自动录入批次")
|
||||
bullet("8. 录入完成 -> Toast 提示 -> 秤体清零 -> 2.5 秒后自动触发下一次检测")
|
||||
bullet("搜索功能:输入关键词回车 -> 模糊匹配 -> 替换候选列表")
|
||||
bullet("批次清单:最新录入排最前面,每条可单独删除")
|
||||
bullet("顶部「退出登录」按钮 -> 返回欢迎页")
|
||||
|
||||
h3("交互说明")
|
||||
bullet("搜索栏输入毛菜名称 -> 按回车 -> 候选列表替换为搜索结果")
|
||||
bullet("点击候选卡片 -> 手动选中(绿色高亮)-> 若秤已稳定则立即录入")
|
||||
bullet("点击批次清单中的删除按钮 -> 删除该条 -> Toast「XXX 已删除」")
|
||||
bullet("点击「提交本批次」-> Toast「本批次提交成功」-> 1.5 秒后跳转到「今日清洗任务」")
|
||||
bullet("批次为空时「提交本批次」按钮禁用")
|
||||
|
||||
# 4.4
|
||||
h2("4.4 操作记录")
|
||||
note("文件:04-history.html")
|
||||
h3("功能说明")
|
||||
p("查看历史过秤操作流水。")
|
||||
bullet("日期导航:左右箭头切换日期")
|
||||
bullet("类型 Tab:全部(8)/ 来料过秤(8)")
|
||||
bullet("空状态:切换至非当日日期时,隐藏记录列表和统计卡片,显示「该日期暂无操作记录」")
|
||||
bullet("统计卡片(2 张):总操作次数(蓝色)、总重量(斤)")
|
||||
bullet("记录卡片:食材图标 + 名称 + 批次号 + 类型标签 + 重量 + 溯源码 + 操作员 + 时间")
|
||||
|
||||
h3("业务规则")
|
||||
bullet("模拟 8 条来料过秤记录(3 个批次)")
|
||||
bullet("默认显示当天(2026-06-13)记录,总重量 33.3 斤")
|
||||
bullet("非当日日期显示空状态")
|
||||
bullet("底部汇总:「共 X 条记录」")
|
||||
bullet("顶部「退出登录」按钮 -> 返回欢迎页")
|
||||
|
||||
h3("交互说明")
|
||||
bullet("点击日期箭头 -> 切换日期;当日显示记录,非当日显示空状态")
|
||||
bullet("点击 Tab -> 筛选对应类型记录,列表和底部汇总同步更新")
|
||||
bullet("点击「退出登录」-> 返回欢迎页")
|
||||
|
||||
# 4.5
|
||||
h2("4.5 设备设置")
|
||||
note("文件:05-settings.html")
|
||||
h3("功能说明")
|
||||
p("设备信息查看页面。左侧菜单 + 右侧内容面板,仅展示只读信息。")
|
||||
|
||||
h3("设置分组及字段")
|
||||
p("设备信息(默认显示):", bold=True)
|
||||
table(
|
||||
["字段", "值"],
|
||||
[
|
||||
["设备名称", "加工毛菜秤-01"],
|
||||
["设备编号", "GJMC-2026-001"],
|
||||
["固件版本", "v2.3.1"],
|
||||
["AI 模型版本", "veg-ai-v4.2"],
|
||||
["设备状态", "在线"],
|
||||
["网络状态", "已连接"],
|
||||
["上次校准", "2026-06-10"],
|
||||
["累计使用", "186 天"],
|
||||
]
|
||||
)
|
||||
|
||||
p("关于:", bold=True)
|
||||
table(
|
||||
["字段", "值"],
|
||||
[
|
||||
["系统名称", "蚁熊智能餐养平台"],
|
||||
["系统版本", "v5.8.0"],
|
||||
["设备类型", "加工毛菜秤"],
|
||||
["制造商", "蚁熊智能科技有限公司"],
|
||||
]
|
||||
)
|
||||
|
||||
h3("交互说明")
|
||||
bullet("点击左侧菜单项 -> 右侧切换对应信息分组")
|
||||
bullet("返回按钮 -> 回到欢迎页面")
|
||||
bullet("顶部「退出登录」按钮 -> 返回欢迎页")
|
||||
|
||||
# ===== 五、关键编码规则汇总 =====
|
||||
h1("五、关键编码规则汇总")
|
||||
table(
|
||||
["编码类型", "格式", "示例", "说明"],
|
||||
[
|
||||
["任务编号", "RW-YYYYMMDD-NNN", "RW-20260613-001", "RW = 任务,当日序号 3 位"],
|
||||
["批次号", "LC-YYYYMMDD-NNN", "LC-20260613-001", "LC = 来料,当日序号 3 位"],
|
||||
["溯源码", "SCYYYYMMDDNNNN", "SC202606130001", "SC + 日期 + 4 位流水号"],
|
||||
]
|
||||
)
|
||||
|
||||
# 保存
|
||||
doc.save("PRD.docx")
|
||||
print("OK: PRD.docx")
|
||||
@@ -6,30 +6,83 @@
|
||||
<title>加工毛菜秤 - 蚁熊智能餐养平台</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden;
|
||||
}
|
||||
body { font-family: -apple-system, "Microsoft YaHei", "PingFang SC", sans-serif; background: #0e1326; color: #fff; width: 1280px; height: 720px; overflow: hidden; }
|
||||
.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); }
|
||||
.main { display: flex; align-items: center; justify-content: center; height: calc(100% - 44px); }
|
||||
.placeholder { text-align: center; color: rgba(255,255,255,0.3); }
|
||||
.placeholder__icon { font-size: 48px; margin-bottom: 16px; }
|
||||
.placeholder__text { font-size: 14px; }
|
||||
.back-link { font-size: 14px; cursor: pointer; color: rgba(255,255,255,0.7); text-decoration: none; }
|
||||
.back-link:hover { color: #fff; }
|
||||
.main-content { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px; padding: 24px 40px; height: calc(100% - 44px); align-content: center; }
|
||||
.card { display: flex; align-items: center; gap: 20px; padding: 24px 28px; background: #1a2b4a; border-radius: 14px; text-decoration: none; cursor: pointer; transition: all 0.2s; border: 1px solid rgba(255,255,255,0.06); }
|
||||
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.3); border-color: rgba(74,144,217,0.3); }
|
||||
.card__icon { font-size: 28px; width: 40px; text-align: center; flex-shrink: 0; }
|
||||
.card__info { flex: 1; }
|
||||
.card__title { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }
|
||||
.card__desc { font-size: 12px; color: rgba(255,255,255,0.5); }
|
||||
.card__arrow { font-size: 18px; color: rgba(255,255,255,0.4); flex-shrink: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="top-bar">
|
||||
<a class="back-btn" href="../index.html">◀ 返回</a>
|
||||
<a class="back-link" href="../index.html">◀ 返回</a>
|
||||
<span class="top-bar__title">加工毛菜秤</span>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="placeholder">
|
||||
<div class="placeholder__icon">🚧</div>
|
||||
<div class="placeholder__text">加工毛菜秤 页面待开发</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<a class="card" href="00-login.html">
|
||||
<span class="card__icon">🔐</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">员工登录</div>
|
||||
<div class="card__desc">人脸识别、账号密码登录</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="00-welcome.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">功能菜单</div>
|
||||
<div class="card__desc">今日任务、来料过秤、操作记录、设备设置</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="01-washing-tasks.html">
|
||||
<span class="card__icon">📋</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">今日清洗任务</div>
|
||||
<div class="card__desc">系统分配的毛菜清洗任务列表</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="02-weighing.html">
|
||||
<span class="card__icon">⚖️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">称重录入</div>
|
||||
<div class="card__desc">毛菜上秤AI识别、称重、确认提交</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="03-washing.html">
|
||||
<span class="card__icon">💧</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">清洗操作</div>
|
||||
<div class="card__desc">清洗流程确认、进度跟踪</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="04-history.html">
|
||||
<span class="card__icon">📝</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">清洗记录</div>
|
||||
<div class="card__desc">历史清洗记录查看与筛选</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
<a class="card" href="05-settings.html">
|
||||
<span class="card__icon">⚙️</span>
|
||||
<div class="card__info">
|
||||
<div class="card__title">设备设置</div>
|
||||
<div class="card__desc">设备信息查看、系统版本</div>
|
||||
</div>
|
||||
<span class="card__arrow">›</span>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user