gaoluyang
2025-12-31 98b00c97565276acd51a9e188f8f2bf9ed0f6ebc
src/pages/inspectionUpload/index.vue
@@ -1,597 +1,430 @@
<template>
  <view class="inspection-upload-page">
    <!-- 页面头部 -->
    <PageHeader title="巡检上传" />
    <!-- 标签页 -->
    <view class="tabs-container">
      <view class="custom-tabs">
        <view
          v-for="(tab, index) in tabs"
          :key="index"
          class="tab-item"
          :class="{ 'tab-active': currentTabIndex === index }"
          @click="handleTabChange(index)"
        >
          {{ tab.name }}
        </view>
        <view class="tab-line" :style="{ left: currentTabIndex * 50 + '%' }"></view>
      </view>
    </view>
    <!-- 扫码模块 -->
    <view v-if="activeTab === 'qrCode'" class="scan-section">
      <view class="scan-controls">
        <u-button
          :type="isScanning ? 'error' : 'primary'"
          :loading="scanLoading"
          @click="toggleScan"
        >
          {{ scanButtonText }}
        </u-button>
      </view>
      <!-- 扫码区域 -->
      <view v-show="isScanning" class="qr-scan-container">
        <camera
          class="qr-camera"
          device-position="back"
          flash="off"
          @scancode="handleScanCode"
          @error="handleCameraError"
        ></camera>
        <view class="scan-overlay">
          <view class="scan-frame"></view>
          <view class="scan-tip">请将二维码放入框内</view>
        </view>
      </view>
      <!-- 状态提示 -->
      <view class="status-info">
        <u-alert
          v-if="cameraError"
          :title="cameraError"
          type="error"
          :showIcon="true"
          :closable="true"
          @close="cameraError = ''"
        ></u-alert>
        <view v-if="isScanning" class="scanning-text">
          <u-loading-icon mode="circle" color="#1890ff" size="20"></u-loading-icon>
          <text class="scanning-label">正在扫描二维码...</text>
        </view>
      </view>
    </view>
    <!-- 数据列表 -->
    <view class="table-section">
      <!-- 生产巡检列表 -->
      <view v-if="activeTab === 'task'" class="task-list">
        <view
          v-for="(item, index) in tableData"
          :key="index"
          class="task-item"
          @click="handleAdd(item)"
        >
          <view class="task-header">
            <view class="task-info">
              <text class="task-name">{{ item.taskName }}</text>
              <text class="task-location">{{ item.inspectionLocation }}</text>
            </view>
            <view class="task-actions">
              <u-button
                type="primary"
                size="small"
                @click.stop="handleAdd(item)"
                :customStyle="{
                  borderRadius: '15px',
                  height: '30px',
                  fontSize: '12px'
                }"
              >
                上传
              </u-button>
            </view>
          </view>
          <view class="task-details">
            <view class="detail-item">
              <text class="detail-label">备注:</text>
              <text class="detail-value">{{ item.remarks || '无' }}</text>
            </view>
            <view class="detail-item">
              <text class="detail-label">执行人:</text>
              <text class="detail-value">{{ item.inspector }}</text>
            </view>
          </view>
        </view>
      </view>
      <!-- 现场巡检列表 -->
      <view v-if="activeTab === 'qrCode'" class="qr-list">
        <view
          v-for="(item, index) in tableData"
          :key="index"
          class="qr-item"
          @click="viewFile(item)"
        >
          <view class="qr-header">
            <view class="qr-info">
              <text class="device-name">{{ item.qrCode?.deviceName }}</text>
              <text class="device-location">{{ item.qrCode?.location }}</text>
            </view>
            <view class="qr-actions">
              <u-button
                type="primary"
                size="small"
                @click.stop="viewFile(item)"
                :customStyle="{
                  borderRadius: '15px',
                  height: '30px',
                  fontSize: '12px'
                }"
              >
                查看附件
              </u-button>
            </view>
          </view>
          <view class="qr-details">
            <view class="detail-item">
              <text class="detail-label">巡检人:</text>
              <text class="detail-value">{{ item.scanner }}</text>
            </view>
            <view class="detail-item">
              <text class="detail-label">巡检时间:</text>
              <text class="detail-value">{{ item.scanTime }}</text>
            </view>
          </view>
        </view>
      </view>
      <!-- 空状态 -->
      <view v-if="tableData.length === 0 && !tableLoading" class="empty-state">
        <u-empty
          mode="data"
          text="暂无数据"
          :iconSize="80"
        ></u-empty>
      </view>
      <!-- 加载状态 -->
      <view v-if="tableLoading" class="loading-state">
        <u-loading-icon mode="circle" color="#1890ff" size="40"></u-loading-icon>
        <text class="loading-text">加载中...</text>
      </view>
    </view>
    <!-- 分页 -->
    <view v-if="total > 0" class="pagination-container">
      <u-pagination
        :total="total"
        :current="pageNum"
        :pageSize="pageSize"
        @change="handlePageChange"
        :showTotal="true"
        :showSizer="false"
        :showJumper="false"
      ></u-pagination>
    </view>
    <!-- 弹窗组件 -->
    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
    <qr-code-form-dia ref="qrCodeFormDia" @closeDia="handleQuery"></qr-code-form-dia>
  </view>
   <view class="app-container">
      <!-- 页面头部 -->
      <PageHeader title="巡检上传" @back="goBack"/>
      <view class="card-container">
         <!-- 标签页 -->
         <u-tabs
            :list="tabs"
            :current="activeTabIndex"
            @change="handleTabChange"
            :scrollable="false"
            lineWidth="30"
            lineColor="#2979ff"
            :activeStyle="{
               color: '#2979ff',
               fontWeight: 'bold'
            }"
         />
         <view>
            <!-- 扫码模块 -->
            <view v-if="activeTab === 'qrCode'" class="scan-section">
               <view class="scan-controls">
                  <u-button
                     type="primary"
                     :loading="scanLoading"
                     @click="toggleScan"
                  >
                     {{ scanButtonText }}
                  </u-button>
               </view>
               <!-- 状态提示 -->
               <view class="status-info">
                  <u-alert
                     v-if="cameraError"
                     :title="cameraError"
                     type="error"
                     :show-icon="true"
                     :closable="true"
                     @close="cameraError = null"
                  />
                  <view v-if="isScanning" class="scanning-text">
                     <u-loading-icon mode="spinner" :color="statusColor" />
                     <text>正在扫描二维码...</text>
                  </view>
               </view>
            </view>
            <view>
               <!-- 加载状态 -->
               <view v-if="tableLoading" class="loading-container">
                  <u-loading-icon text="加载中..." />
               </view>
               <!-- 生产巡检列表 -->
               <view v-else-if="activeTab !== 'qrCode' && tableData.length > 0" class="list-container">
                  <view v-for="(item, index) in tableData" :key="index" class="list-item">
                     <view class="item-content">
                        <view class="item-row">
                           <text class="item-label">巡检任务名称:</text>
                           <text class="item-value">{{ item.taskName || '-' }}</text>
                        </view>
                        <view class="item-row">
                           <text class="item-label">地点:</text>
                           <text class="item-value">{{ item.inspectionLocation || '-' }}</text>
                        </view>
                        <view class="item-row">
                           <text class="item-label">备注:</text>
                           <text class="item-value">{{ item.remarks || '-' }}</text>
                        </view>
                        <view class="item-row">
                           <text class="item-label">执行巡检人:</text>
                           <text class="item-value">{{ item.inspector || '-' }}</text>
                        </view>
                     </view>
                     <view class="item-actions">
                        <u-button type="primary" size="small" @click="handleAdd(item)">上传</u-button>
                     </view>
                  </view>
               </view>
               <!-- 现场巡检列表 -->
               <view v-else-if="activeTab === 'qrCode' && tableData.length > 0" class="list-container">
                  <view v-for="(item, index) in tableData" :key="index" class="list-item">
                     <view class="item-content">
                        <view class="item-row">
                           <text class="item-label">设备名称:</text>
                           <text class="item-value">{{ item.deviceName || '-' }}</text>
                        </view>
                        <view class="item-row">
                           <text class="item-label">所在位置描述:</text>
                           <text class="item-value">{{ item.location || '-' }}</text>
                        </view>
                        <view class="item-row">
                           <text class="item-label">巡检人:</text>
                           <text class="item-value">{{ item.scanner || '-' }}</text>
                        </view>
                        <view class="item-row">
                           <text class="item-label">巡检时间:</text>
                           <text class="item-value">{{ item.scanTime || '-' }}</text>
                        </view>
                     </view>
                     <view class="item-actions">
                        <u-button type="primary" size="small" @click="viewFile(item)">查看附件</u-button>
                     </view>
                  </view>
               </view>
               <!-- 空数据 -->
               <view v-else-if="!tableLoading && tableData.length === 0" class="no-data">
                  <text>暂无数据</text>
               </view>
            </view>
         </view>
      </view>
      <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>
   </view>
</template>
<script setup>
import { onMounted, ref, reactive, computed, nextTick } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import FormDia from './components/formDia.vue'
import QrCodeFormDia from './components/qrCodeFormDia.vue'
import { qrCodeScanRecordList } from '@/api/inspectionUpload/index.js'
import { getInspectionTaskList } from '@/api/equipmentManagement/inspection.js'
import {inspectionTaskList} from "@/api/inspectionManagement";
// import ViewQrCodeFiles from '@/pages/inspectionManagement/components/viewQrCodeFiles.vue'
import { onMounted, ref, reactive, computed, nextTick } from "vue";
import FormDia from "@/pages/inspectionUpload/components/formDia.vue";
import { useToast } from '@/utils/uviewplus';
import QrCodeFormDia from "@/pages/inspectionUpload/components/qrCodeFormDia.vue";
import { qrCodeList, qrCodeScanRecordList } from "@/api/inspectionUpload/index.js";
import { inspectionTaskList } from "@/api/inspectionManagement/index.js";
import ViewQrCodeFiles from "@/components/imageUpload/viewQrCodeFiles.vue";
// 组件引用
const formDia = ref()
const qrCodeFormDia = ref()
const { showToast } = useToast();
const formDia = ref();
const qrCodeFormDia = ref();
const viewQrCodeFiles = ref();
// 当前标签
const activeTab = ref('task')
const tabName = ref('task')
const currentTabIndex = ref(0)
const activeTab = ref("task");
const activeTabIndex = ref(0);
const tabName = ref("task");
// 标签页数据
const tabs = reactive([
  { name: '生产巡检' },
  { name: '现场巡检' }
])
   { name: "生产巡检", value: "task" },
   { name: "现场巡检", value: "qrCode" },
]);
// 表格数据
const tableData = ref([])
const tableLoading = ref(false)
const total = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
const tableData = ref([]);
const tableLoading = ref(false);
// 扫码相关状态
const isScanning = ref(false)
const scanLoading = ref(false)
const cameraError = ref('')
const isScanning = ref(false);
const scanLoading = ref(false);
const cameraError = ref(null);
// 计算属性
const scanButtonText = computed(() => {
  if (scanLoading.value) return '正在初始化...'
  return isScanning.value ? '停止扫码' : '开始扫码'
})
const statusColor = computed(() => {
   return isScanning.value ? '#67C23A' : '#F56C6C';
});
// 生命周期
onMounted(() => {
  // 延迟初始化,确保DOM已渲染
  nextTick(() => {
    handleTabClick({ props: { name: 'task' } })
  })
})
onShow(() => {
  // 页面显示时刷新数据
  getList()
})
// 生命周期管理
onMounted(async () => {
   handleTabChange({ index: 0 });
});
// 标签页切换
const handleTabChange = (index) => {
  currentTabIndex.value = index
  const tabNames = ['task', 'qrCode']
  activeTab.value = tabNames[index]
  tabName.value = tabNames[index]
  tableData.value = []
  pageNum.value = 1
  getList()
}
const handleTabChange = (e) => {
   const index = typeof e === 'object' && e.index !== undefined ? e.index : e;
   const selectedTab = tabs[index];
   if (selectedTab) {
      activeTab.value = selectedTab.value;
      activeTabIndex.value = index;
      tabName.value = selectedTab.value;
      tableData.value = [];
      getList();
   }
};
// 标签页点击(兼容旧方法)
const handleTabClick = (tab) => {
  tabName.value = tab.props.name
  activeTab.value = tab.props.name
  tableData.value = []
  getList()
}
// 查询数据
// 点击查询
const handleQuery = () => {
  pageNum.value = 1
  pageSize.value = 10
  getList()
}
   getList();
};
// 获取列表数据
const getList = () => {
  tableLoading.value = true
  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 handlePageChange = (page) => {
  pageNum.value = page
  getList()
}
   tableLoading.value = true;
   if (tabName.value === "task") {
      inspectionTaskList({ size: -1, current: -1 }).then(res => {
         tableLoading.value = false;
         tableData.value = res.data.records || [];
      }).catch(err => {
         tableLoading.value = false;
         showToast('获取数据失败', 'error');
      });
   } else {
      qrCodeScanRecordList({ size: -1, current: -1 }).then(res => {
         tableLoading.value = false;
         // 处理数据,格式化字段
         tableData.value = (res.data.records || []).map(item => ({
            ...item,
            deviceName: item.qrCode?.deviceName || '-',
            location: item.qrCode?.location || '-'
         }));
      }).catch(err => {
         tableLoading.value = false;
         showToast('获取数据失败', 'error');
      });
   }
};
// 上传
const handleAdd = (row) => {
  nextTick(() => {
    formDia.value?.openDialog(row)
  })
}
   console.log('handleAdd 被调用', row);
   console.log('formDia.value:', formDia.value);
   if (!formDia.value) {
      showToast('组件未初始化', 'error');
      return;
   }
   nextTick(() => {
      if (formDia.value && formDia.value.openDialog) {
         formDia.value.openDialog(row);
      } else {
         console.error('formDia.value.openDialog 不存在');
         showToast('打开弹窗失败', 'error');
      }
   });
};
// 查看附件
const viewFile = (row) => {
  console.log('查看附件:', row)
  uni.showToast({
    title: '查看附件功能开发中',
    icon: 'none'
  })
}
   nextTick(() => {
      viewQrCodeFiles.value?.openDialog(row);
   });
};
// 扫码相关方法
// 扫码按钮文本
const scanButtonText = computed(() => {
   if (scanLoading.value) return '正在初始化...';
   return isScanning.value ? '停止扫码' : '开始扫码';
});
// 切换扫码状态
const toggleScan = async () => {
  if (isScanning.value) {
    await stopScan()
  } else {
    await startScan()
  }
}
   if (isScanning.value) {
      stopScan();
   } else {
      startScan();
   }
};
const startScan = async () => {
  try {
    // 使用uniapp的扫码API
    uni.scanCode({
      success: (res) => {
        handleScanSuccess(res)
      },
      fail: (err) => {
        console.error('扫码失败:', err)
        uni.showToast({
          title: '扫码失败',
          icon: 'error'
        })
      }
    })
  } catch (e) {
    console.error('启动扫码失败:', e)
    uni.showToast({
      title: '启动扫码失败',
      icon: 'error'
    })
  }
}
// 开始扫码
const startScan = () => {
   if (isScanning.value) {
      showToast('正在扫描中,请稍候...', 'warning');
      return;
   }
   scanLoading.value = true;
   // 调用uni-app的扫码API
   uni.scanCode({
      scanType: ['qrCode', 'barCode'],
      success: (res) => {
         scanLoading.value = false;
         handleScanSuccess(res.result);
      },
      fail: (err) => {
         scanLoading.value = false;
         console.error('扫码失败:', err);
         cameraError.value = '扫码失败,请重试';
         setTimeout(() => {
            cameraError.value = null;
         }, 3000);
      }
   });
};
const stopScan = async () => {
  isScanning.value = false
}
// 扫码成功处理
// 扫描成功处理
const handleScanSuccess = async (result) => {
  try {
    uni.showToast({
      title: '识别成功',
      icon: 'success'
    })
    // 解析二维码数据
    let qrData
    try {
      qrData = JSON.parse(result.result)
    } catch (e) {
      qrData = { deviceName: result.result, location: '' }
    }
    callBackendAPI(qrData)
  } catch (error) {
    uni.showToast({
      title: error.message || '数据解析失败',
      icon: 'error'
    })
  }
}
   try {
      if (!result) {
         showToast('扫码结果为空', 'warning');
         return;
      }
      showToast('识别成功', 'success');
      // 解析二维码数据
      let qrData;
      try {
         qrData = JSON.parse(result);
      } catch (e) {
         // 如果不是JSON格式,直接使用原始数据
         qrData = { data: result };
      }
      callBackendAPI(qrData);
   } catch (error) {
      showToast(error.message || '处理扫码结果失败', 'error');
   }
};
// 调用后端API
const callBackendAPI = (result) => {
  nextTick(() => {
    qrCodeFormDia.value?.openDialog(result)
  })
}
   nextTick(() => {
      qrCodeFormDia.value?.openDialog(result);
   });
};
// 扫码处理
const handleScanCode = (result) => {
  console.log('扫码结果:', result)
  handleScanSuccess(result)
}
// 摄像头错误处理
const handleCameraError = (error) => {
  console.error('摄像头错误:', error)
  cameraError.value = '摄像头访问失败,请检查权限设置'
}
// 停止扫码
const stopScan = () => {
   isScanning.value = false;
   scanLoading.value = false;
};
// 返回上一页
const goBack = () => {
   uni.navigateBack();
};
</script>
<style scoped lang="scss">
.inspection-upload-page {
  min-height: 100vh;
  background-color: #f5f5f5;
<style scoped>
.app-container {
   padding: 20rpx;
   background-color: #f5f5f5;
   min-height: 100vh;
}
.tabs-container {
  background-color: #fff;
  margin: 0;
  border-bottom: 1px solid #e8e8e8;
}
.custom-tabs {
  display: flex;
  position: relative;
  background-color: #fff;
  width: 100%;
}
.tab-item {
  flex: 1;
  text-align: center;
  padding: 20px 0;
  font-size: 16px;
  font-weight: 500;
  color: #606266;
  transition: all 0.3s ease;
  cursor: pointer;
  position: relative;
  z-index: 2;
}
.tab-item.tab-active {
  color: #1890ff;
  font-weight: 600;
}
.tab-line {
  position: absolute;
  bottom: 0;
  width: 50%;
  height: 3px;
  background-color: #1890ff;
  transition: left 0.3s ease;
.card-container {
   background-color: #fff;
   border-radius: 16rpx;
   padding: 20rpx;
}
.scan-section {
  background-color: #fff;
   padding: 10px;
   margin: 20rpx 0;
}
.scan-controls {
  display: flex;
  justify-content: center;
}
.qr-scan-container {
  position: relative;
  width: 100%;
  max-width: 500px;
  margin: 0 auto;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
}
.qr-camera {
  width: 100%;
  height: 300px;
}
.scan-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 70%;
  height: 70%;
  border: 3px solid #1890ff;
  border-radius: 8px;
  box-shadow: 0 0 20px rgba(24, 144, 255, 0.3);
  animation: pulse 2s infinite;
}
.scan-frame {
  width: 100%;
  height: 100%;
  border: 2px solid #fff;
  border-radius: 4px;
}
.scan-tip {
  position: absolute;
  bottom: -30px;
  left: 50%;
  transform: translateX(-50%);
  color: #fff;
  font-size: 14px;
  text-align: center;
}
@keyframes pulse {
  0% { opacity: 0.8; }
  50% { opacity: 0.4; }
  100% { opacity: 0.8; }
   display: flex;
   justify-content: center;
   margin-bottom: 20rpx;
}
.status-info {
  margin-top: 16px;
  text-align: center;
   margin-top: 32rpx;
   text-align: center;
}
.scanning-text {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #1890ff;
  margin-top: 8px;
   display: flex;
   align-items: center;
   justify-content: center;
   color: #2979ff;
   margin-top: 16rpx;
   font-size: 28rpx;
}
.scanning-label {
  margin-left: 8px;
  font-size: 14px;
.scanning-text text {
   margin-left: 10rpx;
}
.table-section {
  padding: 0 15px;
.loading-container {
   display: flex;
   justify-content: center;
   align-items: center;
   padding: 40rpx 0;
}
.task-list, .qr-list {
  .task-item, .qr-item {
    background-color: #fff;
    border-radius: 8px;
    margin-bottom: 10px;
    padding: 15px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
.list-container {
   margin-top: 20rpx;
}
.task-header, .qr-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 10px;
.list-item {
   background-color: #fff;
   border-radius: 16rpx;
   padding: 24rpx;
   margin-bottom: 20rpx;
   border: 1px solid #f0f0f0;
}
.task-info, .qr-info {
  flex: 1;
.item-content {
   margin-bottom: 20rpx;
}
.task-name, .device-name {
  font-size: 16px;
  font-weight: 600;
  color: #333;
  margin-bottom: 4px;
.item-row {
   display: flex;
   margin-bottom: 16rpx;
   font-size: 28rpx;
   line-height: 40rpx;
}
.task-location, .device-location {
  font-size: 14px;
  color: #666;
.item-row:last-child {
   margin-bottom: 0;
}
.task-actions, .qr-actions {
  margin-left: 10px;
.item-label {
   color: #666;
   width: 200rpx;
   flex-shrink: 0;
}
.task-details, .qr-details {
  .detail-item {
    display: flex;
    margin-bottom: 6px;
    .detail-label {
      font-size: 14px;
      color: #666;
      min-width: 60px;
    }
    .detail-value {
      font-size: 14px;
      color: #333;
      flex: 1;
    }
  }
.item-value {
   color: #333;
   flex: 1;
   word-break: break-all;
}
.empty-state, .loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 20px;
  background-color: #fff;
  border-radius: 8px;
  margin: 10px 15px;
.item-actions {
   display: flex;
   justify-content: flex-end;
   padding-top: 20rpx;
   border-top: 1px solid #f0f0f0;
}
.loading-text {
  margin-top: 10px;
  font-size: 14px;
  color: #666;
.no-data {
   text-align: center;
   padding: 80rpx 0;
   color: #999;
   font-size: 28rpx;
}
.pagination-container {
  padding: 20px 15px;
  background-color: #fff;
  margin-top: 10px;
/* 移动端优化 */
@media (max-width: 768px) {
   .app-container {
      padding: 10rpx;
   }
}
</style>
</style>