From 95f44bd509e286290a18639531de3e05768c3a90 Mon Sep 17 00:00:00 2001 From: gaoluyang <2820782392@qq.com> Date: 星期二, 24 六月 2025 16:46:07 +0800 Subject: [PATCH] 1.巡检管理-二维码管理联调 2.巡检管理-现场巡检记录开发联调 3.巡检上传-现场巡检开发联调 --- src/views/inspectionUpload/index.vue | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 279 insertions(+), 11 deletions(-) diff --git a/src/views/inspectionUpload/index.vue b/src/views/inspectionUpload/index.vue index 559c05e..560fc01 100644 --- a/src/views/inspectionUpload/index.vue +++ b/src/views/inspectionUpload/index.vue @@ -15,8 +15,46 @@ /> </el-tabs> <div> + <!-- 鎵爜妯″潡 --> + <div v-if="activeTab === 'qrCode'" class="scan-section"> + <div class="scan-controls"> + <el-button + type="primary" + :loading="scanLoading" + @click="toggleScan" + > + {{ scanButtonText }} + </el-button> + </div> + + <!-- 鎵爜瑙嗛瀹瑰櫒 --> + <div v-show="isScanning" class="qr-video-container"> + <video + ref="qrVideo" + class="qr-video" + playsinline + webkit-playsinline + ></video> + <div class="scan-overlay"></div> + </div> + + <!-- 鐘舵�佹彁绀� --> + <div class="status-info"> + <el-alert + v-if="cameraError" + :title="cameraError" + type="error" + show-icon + closable + /> + <div v-if="isScanning" class="scanning-text"> + <el-icon :color="statusColor"><Loading /></el-icon> + 姝e湪鎵弿浜岀淮鐮�... + </div> + </div> + </div> <div> - <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading"> + <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" v-if="activeTab !== 'qrCode'"> <el-table-column label="搴忓彿" type="index" width="60" align="center" /> <el-table-column prop="taskName" label="宸℃浠诲姟鍚嶇О" :show-overflow-tooltip="true"></el-table-column> <el-table-column prop="port" label="鍦扮偣" :show-overflow-tooltip="true"></el-table-column> @@ -25,6 +63,26 @@ <el-table-column fixed="right" label="鎿嶄綔"> <template #default="scope"> <el-button link type="primary" @click="handleAdd(scope.row)">涓婁紶</el-button> + </template> + </el-table-column> + </el-table> + <el-table ref="table" :data="tableData" height="480" v-loading="tableLoading" v-if="activeTab === 'qrCode'"> + <el-table-column label="搴忓彿" type="index" width="60" align="center" /> + <el-table-column prop="deviceName" label="璁惧鍚嶇О" :show-overflow-tooltip="true"> + <template #default="scope"> + {{scope.row.qrCode.deviceName}} + </template> + </el-table-column> + <el-table-column prop="location" label="鎵�鍦ㄤ綅缃弿杩�" :show-overflow-tooltip="true"> + <template #default="scope"> + {{scope.row.qrCode.location}} + </template> + </el-table-column> + <el-table-column prop="scanner" label="宸℃浜�"></el-table-column> + <el-table-column prop="scanTime" label="宸℃鏃堕棿"></el-table-column> + <el-table-column fixed="right" label="鎿嶄綔"> + <template #default="scope"> + <el-button link type="primary" @click="viewFile(scope.row)">鏌ョ湅闄勪欢</el-button> </template> </el-table-column> </el-table> @@ -40,22 +98,31 @@ </div> </el-card> <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> + <qr-code-form-dia ref="qrCodeFormDia" @closeDia="handleQuery"></qr-code-form-dia> + <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files> </div> </template> <script setup> import Pagination from "@/components/Pagination/index.vue"; -import {inspectionTaskList} from "@/api/inspectionManagement/index.js"; import {onMounted, ref} from "vue"; import FormDia from "@/views/inspectionUpload/components/formDia.vue"; +import {ElMessage} from "element-plus"; +import QrScanner from 'qr-scanner' +import QrCodeFormDia from "@/views/inspectionUpload/components/qrCodeFormDia.vue"; +import {qrCodeList, qrCodeScanRecordList} from "@/api/inspectionUpload/index.js"; +import {inspectionTaskList} from "@/api/inspectionManagement/index.js"; +import ViewQrCodeFiles from "@/views/inspectionManagement/components/viewQrCodeFiles.vue"; const formDia = ref() +const qrCodeFormDia = ref() +const viewQrCodeFiles = ref() // 褰撳墠鏍囩 const activeTab = ref("task"); const tabName = ref("task"); // 鏍囩椤垫暟鎹� const tabs = reactive([ - { name: "task", label: "浠诲姟涓嬪彂" }, - { name: "qrCode", label: "浜岀淮鐮佺鐞�" }, + { name: "task", label: "鐢熶骇宸℃" }, + { name: "qrCode", label: "鐜板満宸℃" }, ]); // 琛ㄦ牸 const tableData = ref([]); @@ -63,10 +130,32 @@ const total = ref(0); const pageNum = ref(1); const pageSize = ref(10); +// 鎵爜鐩稿叧鐘舵�� +const qrVideo = ref(null) +const isScanning = ref(false) +const scanLoading = ref(false) +const cameraError = ref(null) +const scanner = ref(null) +const hasInit = ref(false) -onMounted(() => { +const statusColor = computed(() => { + return isScanning.value ? '#67C23A' : '#F56C6C' +}) +// 鐢熷懡鍛ㄦ湡绠$悊浼樺寲 +onMounted(async () => { handleTabClick({ props: { name: "task" } }); -}); + if (!import.meta.env.SSR && QrScanner) { // [!code focus] + await initScanner() + } +}) + +onBeforeUnmount(async () => { + if (scanner.value) { + await scanner.value.destroy() + scanner.value = null + } + hasInit.value = false +}) // 鏍囩椤电偣鍑� const handleTabClick = (tab) => { tabName.value = tab.props.name; @@ -81,11 +170,19 @@ } const getList = () => { tableLoading.value = true; - inspectionTaskList({size: pageSize.value, current: pageNum.value}).then(res => { - tableLoading.value = false; - tableData.value = res.data.records; - total.value = res.data.total; - }) + if (tabName.value === "task") { + inspectionTaskList({size: pageSize.value, current: pageNum.value}).then(res => { + tableLoading.value = false; + tableData.value = res.data.records; + total.value = res.data.total; + }) + } else { + qrCodeScanRecordList({size: pageSize.value, current: pageNum.value}).then(res => { + tableLoading.value = false; + tableData.value = res.data.records; + total.value = res.data.total; + }) + } }; // 涓婁紶 const handleAdd = (row) => { @@ -93,10 +190,181 @@ formDia.value?.openDialog(row) }) } +// 鏌ョ湅闄勪欢 +const viewFile = (row) => { + nextTick(() => { + viewQrCodeFiles.value?.openDialog(row) + }) +} +// 鎵爜鎸夐挳鏂囨湰 +const scanButtonText = computed(() => { + if (scanLoading.value) return '姝e湪鍒濆鍖�...' + return isScanning.value ? '鍋滄鎵爜' : '寮�濮嬫壂鐮�' +}) +// 澧炲己鍨嬪垵濮嬪寲 +const initScanner = async () => { + try { + await nextTick() // 纭繚DOM鏇存柊 + // 鏂板澶氶噸绌哄�兼牎楠� + if (!qrVideo.value || !QrScanner) { + throw new Error('渚濊禆鏈纭垵濮嬪寲') + } + // 澧炲姞鎽勫儚澶存潈闄愰妫�鏌� + const hasCamera = await QrScanner.hasCamera() + if (!hasCamera) { + throw new Error('鏈娴嬪埌鍙敤鎽勫儚澶�') + } + // 鏄惧紡閿�姣佹棫瀹炰緥 + if (scanner.value) { + await scanner.value.destroy() + } + // 鍒涘缓鏂板疄渚� + scanner.value = new QrScanner( + qrVideo.value, + result => { + handleScanSuccess(result) + // stopScan() + }, + { + preferredCamera: 'environment', + maxScansPerSecond: 5, + returnDetailedScanResult: true + } + ) + // 鏂板纭欢鍔犻�熸娴� + if (!scanner.value._qrWorker) { + throw new Error('纭欢鍔犻�熶笉鍙敤') + } + hasInit.value = true + } catch (e) { + // handleInitError(e) + } +} +// 鎵弿鎴愬姛澶勭悊 +const handleScanSuccess = async (result) => { + try { + // 娣诲姞鏁版嵁鏍¢獙 + ElMessage.success('璇嗗埆鎴愬姛') + callBackendAPI(JSON.parse(result.data)) + await stopScan() + } catch (error) { + ElMessage.warning(error.message) + await startScan() // 鏁版嵁鏃犳晥鏃剁户缁壂鎻� + } +} +const callBackendAPI = (result) => { + nextTick(() => { + qrCodeFormDia.value?.openDialog(result) + }) +} +// 鍒囨崲鎵爜鐘舵�� +const toggleScan = async () => { + if (isScanning.value) { + await stopScan() + } else { + await startScan() + } +} + +// 澧炲己鍚姩鏂规硶 +const startScan = async () => { + if (!scanner.value || !hasInit.value) { // 鏂板鐘舵�佹鏌� + await initScanner() + } + + try { + await scanner.value.start() + isScanning.value = true + } catch (e) { + ElMessage.error(`鍚姩澶辫触: ${e.message}`) + hasInit.value = false + } +} + +// 鍋滄鎵爜 +const stopScan = async () => { + try { + await scanner.value.stop() + isScanning.value = false + } catch (err) { + console.error('鍋滄鎽勫儚澶村け璐�:', err) + } +} + + +// 閿欒澶勭悊澧炲己 +const handleInitError = (error) => { + console.error('鍒濆鍖栧け璐�:', error) + const msg = { + 'NotAllowedError': '璇峰厑璁告憚鍍忓ご鏉冮檺', + 'NotFoundError': '鏈壘鍒版憚鍍忓ご璁惧', + 'NotSupportedError': '娴忚鍣ㄤ笉鏀寔鎵爜鍔熻兘' + }[error.name] || error.message + + ElMessage.error(`鍒濆鍖栧け璐�: ${msg}`) +} </script> <style scoped> +.qr-video-container { + position: relative; + width: 100%; + max-width: 500px; + margin: 0 auto; + background: #000; + border-radius: 8px; + overflow: hidden; +} +.qr-video { + width: 100%; + height: auto; + object-fit: cover; +} + +.scan-overlay { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 70%; + height: 70%; + border: 3px solid #409eff; + border-radius: 8px; + box-shadow: 0 0 20px rgba(64, 158, 255, 0.3); + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { opacity: 0.8; } + 50% { opacity: 0.4; } + 100% { opacity: 0.8; } +} + +.status-info { + margin-top: 16px; + text-align: center; +} + +.scanning-text { + color: #409eff; + margin-top: 8px; +} + +.table-section { + margin-top: 24px; +} + +/* 绉诲姩绔紭鍖� */ +@media (max-width: 768px) { + .qr-video-container { + height: 60vh; + } + + .el-table { + font-size: 12px; + } +} </style> \ No newline at end of file -- Gitblit v1.9.3