270 lines
13 KiB
HTML
270 lines
13 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=20260616a">
|
|
</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" v-if="inspection">
|
|
<h2>🔍 查验进度 — {% raw %}{{ inspection.customsName }}{% endraw %}</h2>
|
|
<p class="hint" style="margin-bottom:12px">
|
|
总进度: {% raw %}{{ inspectionTotal }}{% endraw %} / {% raw %}{{ inspectionTarget }}{% endraw %} 台
|
|
<span v-if="inspectionTotal >= inspectionTarget && inspectionTarget > 0" style="color:#4caf50;font-weight:bold"> ✅ 已完成</span>
|
|
</p>
|
|
<div class="inspection-grid">
|
|
<div v-for="(item, ii) in inspection.items" :key="ii" class="inspection-item" :class="{ 'insp-done': item.inspected >= item.quantify, 'insp-active': item.inspected > 0 && item.inspected < item.quantify }">
|
|
<div class="insp-name">{% raw %}{{ item.inventoryName }}{% endraw %}</div>
|
|
<div class="insp-code">{% raw %}{{ item.inventoryCode }}{% endraw %}</div>
|
|
<div class="insp-spec">{% raw %}{{ item.spec }}{% endraw %}</div>
|
|
<div class="insp-count">
|
|
<span class="insp-num">{% raw %}{{ item.inspected }}{% endraw %}</span>
|
|
<span class="insp-sep">/</span>
|
|
<span class="insp-total">{% raw %}{{ item.quantify }}{% endraw %}</span>
|
|
</div>
|
|
<div class="insp-bar">
|
|
<div class="insp-fill" :style="{width: (item.quantify > 0 ? (item.inspected / item.quantify * 100) : 0) + '%'}"></div>
|
|
</div>
|
|
</div>
|
|
</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 摄像头 <span v-if="!hasAgvCamera" style="font-size:0.8em;color:#999">(不可用)</span></div>
|
|
<img v-if="hasAgvCamera" :src="agvPreviewUrl" @error="onAgvPreviewError" class="camera-img">
|
|
<div v-else class="camera-placeholder">AGV 无可用彩色摄像头</div>
|
|
</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>{% raw %}{{ qrMessage }}{% endraw %}</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=20260616c"></script>
|
|
</body>
|
|
</html> |