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>
              正在扫描二维码...
            </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'" border style="width: 100%;height: calc(100vh - 20em)">
            <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'" border style="width: 100%;height: calc(100vh - 23em)">
            <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 '正在初始化...'
  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>