修改运行时显示

This commit is contained in:
ywb
2026-05-29 13:37:02 +08:00
parent a556a0858b
commit f64068475f
6 changed files with 229 additions and 37 deletions
Binary file not shown.
+29 -1
View File
@@ -1376,6 +1376,23 @@ def api_mission_state():
if r < len(grid) and c < len(grid[r]) and grid[r][c]: if r < len(grid) and c < len(grid[r]) and grid[r][c]:
path.append((r, c)) path.append((r, c))
# 网格级别任务数据
result["rows"] = rows
result["cols"] = cols
result["grid"] = grid
result["point_status"] = {f"{pr}_{c}": "pending" for pr in range(rows+1) for c in range(cols)}
result["machine_status"] = {}
for r in range(rows):
for c in range(cols):
if r < len(grid) and c < len(grid[r]) and grid[r][c]:
result["machine_status"][f"{r}_{c}"] = {
"has_machine": True,
"qr": "pending", "qr_val": None,
"front": "pending", "front_cnt": 0,
"back": "pending", "back_cnt": 0,
"status": "pending", "step": "等待",
}
# 保留旧的 tasks 列表(兼容)
tlist = [] tlist = []
for (r, c) in path: for (r, c) in path:
tlist.append({ tlist.append({
@@ -1390,15 +1407,26 @@ def api_mission_state():
}) })
result["tasks"] = tlist result["tasks"] = tlist
except Exception: except Exception:
result["rows"] = 1
result["cols"] = 1
result["grid"] = []
result["point_status"] = {}
result["machine_status"] = {}
result["tasks"] = [] result["tasks"] = []
# 错误弹窗状态 # 错误弹窗状态和实时网格状态
if hasattr(MissionExecutorV3, "_instance") and MissionExecutorV3._instance: if hasattr(MissionExecutorV3, "_instance") and MissionExecutorV3._instance:
ex = MissionExecutorV3._instance ex = MissionExecutorV3._instance
st = ex.get_status() st = ex.get_status()
result["error_msg"] = st.get("error", "") result["error_msg"] = st.get("error", "")
result["waiting_step"] = (st.get("status") == "waiting_step") result["waiting_step"] = (st.get("status") == "waiting_step")
result["waiting_error"] = (st.get("status") == "waiting_error") result["waiting_error"] = (st.get("status") == "waiting_error")
# 从 executor.report 读取实时点/机器状态
rpt = ex.report
if rpt.get("point_status"):
result["point_status"] = rpt["point_status"]
if rpt.get("machine_status"):
result["machine_status"] = rpt["machine_status"]
else: else:
result["error_msg"] = "" result["error_msg"] = ""
result["waiting_step"] = False result["waiting_step"] = False
+33
View File
@@ -1043,3 +1043,36 @@ a:hover { text-decoration: underline; }
font-size: 12px; font-size: 12px;
color: #666; color: #666;
} }
/* ===== 运行页网格任务状态 ===== */
/* 点位单元格 - 导航状态 */
.point-cell { min-height: 52px; }
.cell-nav-icon { font-size: 18px; text-align: center; }
.cell-nav-label { font-size: 11px; color: #8899aa; text-align: center; margin-top: 2px; }
.point-cell.nav-pending { background: #141e28; border-color: #2a3a4a; }
.point-cell.nav-active { background: #1a3020; border-color: #4caf50; animation: navPulse 1.5s infinite; }
.point-cell.nav-done { background: #152522; border-color: #2e7d32; }
.point-cell.nav-done .cell-nav-icon { color: #4caf50; }
.point-cell.nav-skipped { background: #141e28; border-color: #2a3a4a; opacity: 0.5; }
@keyframes navPulse { 0%,100% { border-color: #4caf50; } 50% { border-color: #1b5e20; } }
/* 机器单元格状态 */
.machine-cell { min-height: 62px; padding: 6px 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; }
.machine-label { font-size: 12px; font-weight: 600; color: #ccc; }
.machine-steps-mini { display: flex; gap: 8px; font-size: 13px; }
.machine-qr-mini { font-size: 10px; color: #4fc3f7; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.empty-cell { color: #445566; font-size: 12px; }
/* 步骤圆点 */
.step-dot { opacity: 0.4; transition: opacity 0.3s; }
.step-dot.dot-done { opacity: 1; }
.step-dot.dot-scanning,
.step-dot.dot-shooting { opacity: 0.8; animation: dotPulse 0.8s infinite; }
@keyframes dotPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }
.step-dot.dot-manual { opacity: 0.7; }
.step-dot.dot-skipped { opacity: 0.3; }
/* 机器状态背景 */
.machine-cell.mstatus-pending { background: #141e28; border-color: #2a3a4a; }
.machine-cell.mstatus-active { background: #1a2535; border-color: #4fc3f7; }
.machine-cell.mstatus-completed { background: #152522; border-color: #2e7d32; }
+51 -4
View File
@@ -2,8 +2,6 @@ const { createApp } = Vue
const API = '' const API = ''
createApp({ createApp({
delimiters: ['[[', ']]'],
data() { data() {
return { return {
missionState: 'idle', missionState: 'idle',
@@ -15,7 +13,14 @@ createApp({
polling: null, polling: null,
logs: [], logs: [],
showQrModal: false, showQrModal: false,
qrSubmitting: false,
qrValue: '', qrValue: '',
// 网格任务显示
missionRows: 0,
missionCols: 0,
missionGrid: [],
pointStatus: {},
machineStatus: {},
// 错误弹窗 / 单步执行 // 错误弹窗 / 单步执行
waitingError: false, waitingError: false,
errorMsg: '', errorMsg: '',
@@ -68,6 +73,13 @@ createApp({
this.progress = data.progress || 0 this.progress = data.progress || 0
if (data.tasks) this.tasks = data.tasks if (data.tasks) this.tasks = data.tasks
// 网格数据
if (data.rows) this.missionRows = data.rows
if (data.cols) this.missionCols = data.cols
if (data.grid) this.missionGrid = data.grid
if (data.point_status) this.pointStatus = data.point_status
if (data.machine_status) this.machineStatus = data.machine_status
// 错误弹窗 // 错误弹窗
if (data.waiting_error) { if (data.waiting_error) {
this.waitingError = true this.waitingError = true
@@ -84,8 +96,11 @@ createApp({
this.waitingStep = false this.waitingStep = false
} }
// QR 弹窗 // QR 弹窗(防止提交后重复弹出)
if (this.missionState === 'waiting_qr' && !this.showQrModal) { if (this.missionState !== 'waiting_qr') {
this.qrSubmitting = false
}
if (this.missionState === 'waiting_qr' && !this.showQrModal && !this.qrSubmitting) {
this.showQrModal = true this.showQrModal = true
this.qrValue = '' this.qrValue = ''
} }
@@ -190,10 +205,12 @@ createApp({
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ qr: val || ' ' }) body: JSON.stringify({ qr: val || ' ' })
}) })
this.qrSubmitting = true
this.showQrModal = false this.showQrModal = false
this.qrValue = '' this.qrValue = ''
}, },
cancelQr() { cancelQr() {
this.qrSubmitting = true
this.showQrModal = false this.showQrModal = false
this.qrValue = '' this.qrValue = ''
fetch(API + '/api/mission/manual-qr', { fetch(API + '/api/mission/manual-qr', {
@@ -207,6 +224,36 @@ createApp({
}, },
onArmPreviewError(e) { onArmPreviewError(e) {
e.target.style.display = 'none' e.target.style.display = 'none'
},
// ===== 网格任务显示方法 =====
getPointStatus(pr, c) {
return (this.pointStatus && this.pointStatus[pr + '_' + c]) || 'pending'
},
navIcon(s) {
const m = { pending: '⏳', active: '🔄', done: '✅', skipped: '⏭️' }
return m[s] || '⏳'
},
navLabel(s) {
const m = { pending: '等待', active: '导航中', done: '到达', skipped: '空位' }
return m[s] || '等待'
},
hasMachine(r, c) {
const key = r + '_' + c
const ms = this.machineStatus && this.machineStatus[key]
return ms && ms.has_machine
},
getMachineClass(r, c) {
const key = r + '_' + c
const ms = this.machineStatus && this.machineStatus[key]
if (!ms) return ''
const s = ms.status || 'pending'
return 'mstatus-' + s
},
getMachineField(r, c, field) {
const key = r + '_' + c
const ms = this.machineStatus && this.machineStatus[key]
if (!ms) return ''
return ms[field] || ''
} }
} }
}).mount('#app') }).mount('#app')
+55 -31
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>运行监控 - AGV 拍摄系统</title> <title>运行监控 - AGV 拍摄系统</title>
<link rel="stylesheet" href="/static/css/style.css?v=20260527b"> <link rel="stylesheet" href="/static/css/style.css?v=20260529a">
</head> </head>
<body> <body>
<div id="app"> <div id="app">
@@ -23,10 +23,10 @@
<div class="running-header"> <div class="running-header">
<div class="running-status" :class="missionState"> <div class="running-status" :class="missionState">
<span class="pulse"></span> <span class="pulse"></span>
[[ missionStateText ]] {% raw %}{{ missionStateText }}{% endraw %}
</div> </div>
<div class="running-progress" v-if="missionState === 'running' || missionState === 'waiting_qr'"> <div class="running-progress" v-if="missionState === 'running' || missionState === 'waiting_qr'">
<span>进度 [[ Math.round(progress) ]]%</span> <span>进度 {% raw %}{{ Math.round(progress) }}{% endraw %}%</span>
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-fill" :style="{width: progress + '%'}"></div> <div class="progress-fill" :style="{width: progress + '%'}"></div>
</div> </div>
@@ -96,50 +96,74 @@
<div class="speed-row"> <div class="speed-row">
<label class="speed-label"> <label class="speed-label">
<span>🚗 AGV 移动速度</span> <span>🚗 AGV 移动速度</span>
<span class="speed-val">[[ agvSpeed.toFixed(1) ]] m/s</span> <span class="speed-val">{% raw %}{{ agvSpeed.toFixed(1) }}{% endraw %} m/s</span>
</label> </label>
<input type="range" class="speed-slider" min="0.1" max="1.0" step="0.1" v-model.number="agvSpeed"> <input type="range" class="speed-slider" min="0.1" max="1.0" step="0.1" v-model.number="agvSpeed">
</div> </div>
<div class="speed-row"> <div class="speed-row">
<label class="speed-label"> <label class="speed-label">
<span>🦾 机械臂速度</span> <span>🦾 机械臂速度</span>
<span class="speed-val">[[ armSpeed ]]</span> <span class="speed-val">{% raw %}{{ armSpeed }}{% endraw %}</span>
</label> </label>
<input type="range" class="speed-slider" min="100" max="1000" step="50" v-model.number="armSpeed"> <input type="range" class="speed-slider" min="100" max="1000" step="50" v-model.number="armSpeed">
</div> </div>
</div> </div>
</section> </section>
<!-- 任务清单 --> <!-- 任务清单 — 网格视图 -->
<section class="card" v-if="tasks.length > 0"> <section class="card" v-if="missionRows > 0">
<h2>📋 任务清单 ([[ tasks.length ]] 台机器)</h2> <h2>📋 任务清单 ({% raw %}{{ missionRows }}{% endraw %}行×{% raw %}{{ missionCols }}{% endraw %}列,点位+机器网格)</h2>
<div class="task-grid"> <div class="mission-grid-wrap" style="margin-top:12px">
<div v-for="task in tasks" :key="task.machine_id" <div class="mission-grid" :style="{ gridTemplateColumns: '90px repeat(' + missionCols + ', 105px)' }">
class="task-cell" :class="'task-' + task.status" <!-- 表头 -->
:title="task.step"> <div class="grid-cell grid-header"></div>
<div class="task-pos">[[ task.label ]]</div> <div v-for="c in missionCols" :key="'h'+c" class="grid-cell grid-header">第{% raw %}{{ c }}{% endraw %}列</div>
<div class="task-status-icon">
<span v-if="task.status === 'pending'"></span> <!-- 第一个点位行 (pointRow=0): 所有机器正面拍摄点 -->
<span v-else-if="task.status === 'active'" class="pulse-icon">🔄</span> <div class="grid-cell grid-header">点位行 1</div>
<span v-else-if="task.status === 'completed'"></span> <div v-for="c in missionCols" :key="'p0_'+c" class="grid-cell point-cell" :class="'nav-'+getPointStatus(0, c-1)">
<span v-else></span> <div class="cell-nav-icon">{% raw %}{{ navIcon(getPointStatus(0, c-1)) }}{% endraw %}</div>
<div class="cell-nav-label">{% raw %}{{ navLabel(getPointStatus(0, c-1)) }}{% endraw %}</div>
</div> </div>
<div class="task-step-text">[[ task.step ]]</div>
<div class="task-info"> <!-- 中间: 机器行 + 点位行 交替 -->
<div v-if="task.qr_value" class="task-qr">🏷 [[ task.qr_value.substring(0,8) ]]</div> <template v-for="ri in missionRows" :key="'block'+ri">
<div class="task-photos" v-if="task.photos_front || task.photos_back"> <!-- 机器行 ri -->
📷 [[ task.photos_front ]]正 [[ task.photos_back ]]背 <div class="grid-cell grid-header">机器行 {% raw %}{{ ri }}{% endraw %}</div>
<div v-for="c in missionCols" :key="'mr'+ri+'_'+c"
class="grid-cell machine-cell"
:class="getMachineClass(ri-1, c-1)">
<template v-if="hasMachine(ri-1, c-1)">
<div class="machine-label">机器{% raw %}{{ ri }}-{{ c }}{% endraw %}</div>
<div class="machine-steps-mini">
<span class="step-dot" :class="'dot-'+getMachineField(ri-1,c-1,'qr')" title="二维码">🔍</span>
<span class="step-dot" :class="'dot-'+getMachineField(ri-1,c-1,'front')" title="正面照">📸正</span>
<span class="step-dot" :class="'dot-'+getMachineField(ri-1,c-1,'back')" title="背面照">📸背</span>
</div>
<div v-if="getMachineField(ri-1,c-1,'qr_val')" class="machine-qr-mini">🏷 {% raw %}{{ getMachineField(ri-1,c-1,'qr_val').substring(0,6) }}{% endraw %}</div>
</template>
<div v-else class="empty-cell"></div>
</div> </div>
</div>
<!-- 点位行 ri (pointRow=ri): 上一个机器的背面 / 下一个机器的正面 -->
<div class="grid-cell grid-header">点位行 {% raw %}{{ ri+1 }}{% endraw %}</div>
<div v-for="c in missionCols" :key="'p'+ri+'_'+c" class="grid-cell point-cell" :class="'nav-'+getPointStatus(ri, c-1)">
<div class="cell-nav-icon">{% raw %}{{ navIcon(getPointStatus(ri, c-1)) }}{% endraw %}</div>
<div class="cell-nav-label">{% raw %}{{ navLabel(getPointStatus(ri, c-1)) }}{% endraw %}</div>
</div>
</template>
</div> </div>
</div> </div>
<div class="grid-legend" style="margin-top:10px;font-size:12px;color:#8899aa">
⏳等待 🔄执行中 ✅完成 ⏭️跳过 ⚠️手动 🔍扫码 📸拍摄
</div>
</section> </section>
<!-- 实时日志 --> <!-- 实时日志 -->
<section class="card"> <section class="card">
<h2>📜 任务日志</h2> <h2>📜 任务日志</h2>
<div class="log-box" ref="logBox"> <div class="log-box" ref="logBox">
<div v-for="(log, i) in logs" :key="i" class="log-line">[[ log ]]</div> <div v-for="(log, i) in logs" :key="i" class="log-line">{% raw %}{{ log }}{% endraw %}</div>
<div v-if="logs.length === 0" class="log-empty">等待任务开始...</div> <div v-if="logs.length === 0" class="log-empty">等待任务开始...</div>
</div> </div>
</section> </section>
@@ -163,9 +187,9 @@
<section class="card" v-if="report"> <section class="card" v-if="report">
<h2>📊 任务报告</h2> <h2>📊 任务报告</h2>
<div class="report-summary"> <div class="report-summary">
<div class="stat ok">✅ 完成: [[ report.completed ]]</div> <div class="stat ok">✅ 完成: {% raw %}{{ report.completed }}{% endraw %}</div>
<div class="stat error">❌ 失败: [[ report.failed ]]</div> <div class="stat error">❌ 失败: {% raw %}{{ report.failed }}{% endraw %}</div>
<div class="stat">总计: [[ report.total_points ]]</div> <div class="stat">总计: {% raw %}{{ report.total_points }}{% endraw %}</div>
</div> </div>
</section> </section>
@@ -186,7 +210,7 @@
<div class="modal-overlay" v-if="waitingError"> <div class="modal-overlay" v-if="waitingError">
<div class="modal"> <div class="modal">
<h3>⚠️ 执行错误</h3> <h3>⚠️ 执行错误</h3>
<p>[[ errorMsg ]]</p> <p>{% raw %}{{ errorMsg }}{% endraw %}</p>
<div class="modal-actions"> <div class="modal-actions">
<button class="btn btn-warning" @click="skipError">跳过</button> <button class="btn btn-warning" @click="skipError">跳过</button>
<button class="btn btn-error" @click="abortError">中断</button> <button class="btn btn-error" @click="abortError">中断</button>
@@ -198,7 +222,7 @@
<div class="modal-overlay" v-if="waitingStep"> <div class="modal-overlay" v-if="waitingStep">
<div class="modal"> <div class="modal">
<h3>🦶 单步执行确认</h3> <h3>🦶 单步执行确认</h3>
<p>机器 [[ stepLabel ]] 执行完成,请确认结果是否正确:</p> <p>机器 {% raw %}{{ stepLabel }}{% endraw %} 执行完成,请确认结果是否正确:</p>
<div class="modal-actions"> <div class="modal-actions">
<button class="btn btn-success" @click="confirmStep">✅ 正确,继续</button> <button class="btn btn-success" @click="confirmStep">✅ 正确,继续</button>
<button class="btn btn-warning" @click="retryStep">🔄 不正确,重试</button> <button class="btn btn-warning" @click="retryStep">🔄 不正确,重试</button>
@@ -211,6 +235,6 @@
</div> </div>
<script src="/static/js/vue3.global.prod.js"></script> <script src="/static/js/vue3.global.prod.js"></script>
<script src="/static/js/running.js?v=20260527b"></script> <script src="/static/js/running.js?v=20260529a"></script>
</body> </body>
</html> </html>
+61 -1
View File
@@ -219,6 +219,27 @@ class MissionExecutorV3:
"photos_back": 0, "photos_back": 0,
}) })
# 初始化点位状态和机器状态(实时推送给前端)
self.report["point_status"] = {}
for _pr in range(rows + 1):
for _c in range(cols):
pk = f"{_pr}_{_c}"
has_f = _pr < rows and MissionExecutorV3._has_machine(grid, _pr, _c)
has_b = _pr > 0 and MissionExecutorV3._has_machine(grid, _pr - 1, _c)
self.report["point_status"][pk] = "pending" if (has_f or has_b) else "skipped"
self.report["machine_status"] = {}
for _r in range(rows):
for _c in range(cols):
if MissionExecutorV3._has_machine(grid, _r, _c):
self.report["machine_status"][f"{_r}_{_c}"] = {
"has_machine": True,
"qr": "pending", "qr_val": None,
"front": "pending", "front_cnt": 0,
"back": "pending", "back_cnt": 0,
"status": "pending", "step": "等待",
}
self._log(f"📍 点位蛇形路径: {len(path)} 个点位, {total_machines} 台机器") self._log(f"📍 点位蛇形路径: {len(path)} 个点位, {total_machines} 台机器")
# 任务步骤控制开关 # 任务步骤控制开关
@@ -300,12 +321,19 @@ class MissionExecutorV3:
except Exception as e: except Exception as e:
self._log(f" ⚠️ 机械臂初始化失败: {e}") self._log(f" ⚠️ 机械臂初始化失败: {e}")
# 更新点位状态:开始导航
pk = f"{pr}_{c}"
if pk in self.report.get("point_status", {}):
self.report["point_status"][pk] = "active"
# 导航到该点位的坐标 # 导航到该点位的坐标
nav_ok = False
if opt_agv_move: if opt_agv_move:
# 找该点位的任意有效坐标(正面/背面坐标相同) # 找该点位的任意有效坐标(正面/背面坐标相同)
pos = MissionExecutorV3._find_any_position(positions, pr, c) pos = MissionExecutorV3._find_any_position(positions, pr, c)
if pos and MissionExecutorV3._has_coords(pos): if pos and MissionExecutorV3._has_coords(pos):
if not self._navigate(pos, f"点位({pr},{c})"): nav_ok = self._navigate(pos, f"点位({pr},{c})")
if not nav_ok:
self._log(f"⚠️ 导航失败,尝试继续") self._log(f"⚠️ 导航失败,尝试继续")
choice = self._wait_error(f"点位({pr},{c})导航失败") choice = self._wait_error(f"点位({pr},{c})导航失败")
if choice == "abort": if choice == "abort":
@@ -314,11 +342,21 @@ class MissionExecutorV3:
self._log(f"⚠️ 点位({pr},{c})无有效坐标") self._log(f"⚠️ 点位({pr},{c})无有效坐标")
else: else:
self._log(" ⏭️ 跳过AGV移动") self._log(" ⏭️ 跳过AGV移动")
nav_ok = True # 无移动视为到达
# 更新点位状态:到达
if pk in self.report.get("point_status", {}):
self.report["point_status"][pk] = "done"
# --- 正面操作(机器 pr,c 的正面) --- # --- 正面操作(机器 pr,c 的正面) ---
qr_value = None qr_value = None
if has_front and not self._stop.is_set(): if has_front and not self._stop.is_set():
self._wait_pause() self._wait_pause()
# 更新机器状态:正面开始
mk = f"{pr}_{c}"
if mk in self.report.get("machine_status", {}):
self.report["machine_status"][mk]["status"] = "active"
self.report["machine_status"][mk]["step"] = "正面扫码"
if opt_qr_scan: if opt_qr_scan:
qr_value = self._scan_qr_with_poses(qr_configs) qr_value = self._scan_qr_with_poses(qr_configs)
if self._stop.is_set(): if self._stop.is_set():
@@ -326,6 +364,12 @@ class MissionExecutorV3:
else: else:
self._log(" ⏭️ 跳过二维码识别(正面)") self._log(" ⏭️ 跳过二维码识别(正面)")
qr_cache[(pr, c)] = qr_value qr_cache[(pr, c)] = qr_value
# 更新机器状态:扫码完成
mk2 = f"{pr}_{c}"
if mk2 in self.report.get("machine_status", {}):
self.report["machine_status"][mk2]["qr"] = "done" if qr_value else "skipped"
self.report["machine_status"][mk2]["qr_val"] = qr_value
self.report["machine_status"][mk2]["step"] = "正面拍照"
task = self._get_task(pr, c) task = self._get_task(pr, c)
if task and qr_value: if task and qr_value:
@@ -345,11 +389,20 @@ class MissionExecutorV3:
else: else:
self._log(" ⏭️ 跳过正面拍照") self._log(" ⏭️ 跳过正面拍照")
completed_actions += 1 completed_actions += 1
# 更新机器状态:正面拍照完成
mk3 = f"{pr}_{c}"
if mk3 in self.report.get("machine_status", {}):
self.report["machine_status"][mk3]["front"] = "done" if opt_front_photo else "skipped"
self.report["machine_status"][mk3]["front_cnt"] = self.report["machine_status"][mk3].get("front_cnt", 0) + 1
# --- 背面操作(机器 pr-1,c 的背面) --- # --- 背面操作(机器 pr-1,c 的背面) ---
if has_back and not self._stop.is_set(): if has_back and not self._stop.is_set():
self._wait_pause() self._wait_pause()
back_qr = qr_cache.get((pr - 1, c), "unknown") back_qr = qr_cache.get((pr - 1, c), "unknown")
# 更新机器状态:背面开始
mk_b = f"{pr-1}_{c}"
if mk_b in self.report.get("machine_status", {}):
self.report["machine_status"][mk_b]["step"] = "背面拍照"
task = self._get_task(pr - 1, c) task = self._get_task(pr - 1, c)
if task: if task:
@@ -367,6 +420,13 @@ class MissionExecutorV3:
else: else:
self._log(" ⏭️ 跳过背面拍照") self._log(" ⏭️ 跳过背面拍照")
completed_actions += 1 completed_actions += 1
# 更新机器状态:背面完成
mk_b2 = f"{pr-1}_{c}"
if mk_b2 in self.report.get("machine_status", {}):
self.report["machine_status"][mk_b2]["back"] = "done" if opt_back_photo else "skipped"
self.report["machine_status"][mk_b2]["back_cnt"] = self.report["machine_status"][mk_b2].get("back_cnt", 0) + 1
self.report["machine_status"][mk_b2]["status"] = "completed"
self.report["machine_status"][mk_b2]["step"] = "完成"
if task: if task:
task["status"] = "completed" task["status"] = "completed"
task["step"] = "完成" task["step"] = "完成"