Files
smart-inspection/agv_app/templates/running.html
T
2026-06-09 13:53:37 +08:00

245 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>运行监控 - AGV 拍摄系统</title>
<link rel="stylesheet" href="/static/css/style.css?v=20260529a">
</head>
<body>
<div id="app">
<header class="topbar">
<div class="logo">▶️ 任务运行</div>
<nav class="nav">
<a href="/" class="nav-link">🏠 首页</a>
<a href="/setting" class="nav-link">⚙️ 设置</a>
<a href="/running" class="nav-link active">▶️ 运行</a>
</nav>
</header>
<main class="container">
<!-- 状态概览 -->
<section class="card">
<div class="running-header">
<div class="running-status" :class="missionState">
<span class="pulse"></span>
{% raw %}{{ missionStateText }}{% endraw %}
</div>
<div class="running-progress" v-if="missionState === 'running' || missionState === 'waiting_qr'">
<span>进度 {% raw %}{{ Math.round(progress) }}{% endraw %}%</span>
<div class="progress-bar">
<div class="progress-fill" :style="{width: progress + '%'}"></div>
</div>
</div>
</div>
<div class="btn-row">
<button class="btn btn-success btn-large" @click="startMission" :disabled="missionState !== 'idle'">
▶️ 开始任务
</button>
<button class="btn btn-warning btn-large" @click="pauseMission" :disabled="missionState !== 'running'">
⏸️ 暂停
</button>
<button class="btn btn-primary btn-large" @click="resumeMission" :disabled="missionState !== 'paused'">
▶️ 继续
</button>
<button class="btn btn-error btn-large" @click="stopMission" :disabled="missionState === 'idle'">
⏹️ 停止
</button>
</div>
</section>
<!-- 任务步骤控制开关 -->
<section class="card">
<h2>🎛️ 任务步骤控制</h2>
<p class="hint" style="margin-bottom:12px">关闭的步骤将在本次任务中跳过</p>
<div class="toggle-grid">
<div class="toggle-item">
<label class="toggle-switch">
<input type="checkbox" v-model="agvMoveEnabled">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">🚗 AGV移动</span>
<span class="toggle-hint">含机械臂初始化,按之字形路线移动到各点位</span>
</div>
<div class="toggle-item">
<label class="toggle-switch">
<input type="checkbox" v-model="qrScanEnabled">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">📷 识别二维码</span>
<span class="toggle-hint">调整机械臂姿态扫描二维码</span>
</div>
<div class="toggle-item">
<label class="toggle-switch">
<input type="checkbox" v-model="frontPhotoEnabled">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">📸 拍正面照</span>
<span class="toggle-hint">按机型正面姿态拍照</span>
</div>
<div class="toggle-item">
<label class="toggle-switch">
<input type="checkbox" v-model="backPhotoEnabled">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">📸 拍背面照</span>
<span class="toggle-hint">按机型背面姿态拍照</span>
</div>
</div>
</section>
<!-- 速度控制 -->
<section class="card">
<h2>🚀 速度控制</h2>
<p class="hint" style="margin-bottom:12px">调节任务执行时的 AGV 和机械臂速度</p>
<div class="speed-panel">
<div class="speed-row">
<label class="speed-label">
<span>🚗 AGV 移动速度</span>
<span class="speed-val">{% raw %}{{ agvSpeed.toFixed(1) }}{% endraw %} m/s</span>
</label>
<input type="range" class="speed-slider" min="0.1" max="1.0" step="0.1" v-model.number="agvSpeed">
</div>
<div class="speed-row">
<label class="speed-label">
<span>🦾 机械臂速度</span>
<span class="speed-val">{% raw %}{{ armSpeed }}{% endraw %}</span>
</label>
<input type="range" class="speed-slider" min="100" max="1000" step="50" v-model.number="armSpeed">
</div>
</div>
</section>
<!-- 任务清单 — 网格视图 -->
<section class="card" v-if="missionRows > 0">
<h2>📋 任务清单 ({% raw %}{{ missionRows }}{% endraw %}行×{% raw %}{{ missionCols }}{% endraw %}列,点位+机器网格)</h2>
<div class="mission-grid-wrap" style="margin-top:12px">
<div class="mission-grid" :style="{ gridTemplateColumns: '90px repeat(' + missionCols + ', 105px)' }">
<!-- 表头 -->
<div class="grid-cell grid-header"></div>
<div v-for="c in missionCols" :key="'h'+c" class="grid-cell grid-header">第{% raw %}{{ c }}{% endraw %}列</div>
<!-- 第一个点位行 (pointRow=0): 所有机器正面拍摄点 -->
<div class="grid-cell grid-header">点位行 1</div>
<div v-for="c in missionCols" :key="'p0_'+c" class="grid-cell point-cell" :class="'nav-'+getPointStatus(0, c-1)">
<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>
<!-- 中间: 机器行 + 点位行 交替 -->
<template v-for="ri in missionRows" :key="'block'+ri">
<!-- 机器行 ri -->
<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') }}{% endraw %}</div>
</template>
<div v-else class="empty-cell"></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 class="grid-legend" style="margin-top:10px;font-size:12px;color:#8899aa">
⏳等待 🔄执行中 ✅完成 ⏭️跳过 ⚠️手动 🔍扫码 📸拍摄
</div>
</section>
<!-- 实时日志 -->
<section class="card">
<h2>📜 任务日志</h2>
<div class="log-box" ref="logBox">
<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>
</section>
<!-- 实时预览 -->
<section class="card">
<h2>📷 摄像头预览</h2>
<div class="camera-dual">
<div class="camera-box">
<div class="camera-label">🎥 AGV 摄像头</div>
<img :src="agvPreviewUrl" @error="onAgvPreviewError" class="camera-img">
</div>
<div class="camera-box" v-if="armCameraOpened">
<div class="camera-label">🦾 机械臂摄像头</div>
<img :src="armPreviewUrl" @error="onArmPreviewError" class="camera-img">
</div>
<div class="camera-box" v-else>
<div class="camera-label">🦾 机械臂摄像头</div>
<div class="camera-placeholder">未连接(机械臂未就绪)</div>
</div>
</div>
</section>
<!-- 任务报告 -->
<section class="card" v-if="report">
<h2>📊 任务报告</h2>
<div class="report-summary">
<div class="stat ok">✅ 完成: {% raw %}{{ report.completed }}{% endraw %}</div>
<div class="stat error">❌ 失败: {% raw %}{{ report.failed }}{% endraw %}</div>
<div class="stat">总计: {% raw %}{{ report.total_points }}{% endraw %}</div>
</div>
</section>
<!-- 手动输入二维码弹窗 -->
<div class="modal-overlay" v-if="showQrModal">
<div class="modal">
<h3>⌨️ 手动输入二维码</h3>
<p>所有姿态均未识别到二维码,请手动输入:</p>
<input type="text" v-model="qrValue" placeholder="输入二维码内容" autofocus @keyup.enter="submitQr">
<div class="modal-actions">
<button class="btn btn-success" @click="rescanQr" style="margin-right:auto">🔄 重新扫描</button>
<button class="btn btn-primary" @click="submitQr">确认</button>
<button class="btn" @click="cancelQr">跳过</button>
</div>
</div>
</div>
<!-- 错误弹窗 -->
<div class="modal-overlay" v-if="waitingError">
<div class="modal">
<h3>⚠️ 执行错误</h3>
<p>{% raw %}{{ errorMsg }}{% endraw %}</p>
<div class="modal-actions">
<button class="btn btn-warning" @click="skipError">跳过</button>
<button class="btn btn-error" @click="abortError">中断</button>
</div>
</div>
</div>
<!-- 步骤确认弹窗 -->
<div class="modal-overlay" v-if="waitingStep">
<div class="modal">
<h3>🦶 单步执行确认</h3>
<p>机器 {% raw %}{{ stepLabel }}{% endraw %} 执行完成,请确认结果是否正确:</p>
<div class="modal-actions">
<button class="btn btn-success" @click="confirmStep">✅ 正确,继续</button>
<button class="btn btn-warning" @click="retryStep">🔄 不正确,重试</button>
<button class="btn btn-error" @click="abortStep">⏹️ 中断</button>
</div>
</div>
</div>
</main>
</div>
<script src="/static/js/vue3.global.prod.js"></script>
<script src="/static/js/running.js?v=20260605b"></script>
</body>
</html>