From 4c8d18cc5ed8a7b0e220c91a858d16d0310896df Mon Sep 17 00:00:00 2001
From: zhangwencui <1064582902@qq.com>
Date: 星期一, 29 六月 2026 16:50:03 +0800
Subject: [PATCH] BOM新增修改删除功能开发、以及BOM结构的可编辑

---
 src/pages/inspectionUpload/index.vue | 1244 +++++++++++++++++++++++++++++++++-------------------------
 1 files changed, 710 insertions(+), 534 deletions(-)

diff --git a/src/pages/inspectionUpload/index.vue b/src/pages/inspectionUpload/index.vue
index 5c57b4e..d83eac8 100644
--- a/src/pages/inspectionUpload/index.vue
+++ b/src/pages/inspectionUpload/index.vue
@@ -1,597 +1,773 @@
 <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">姝e湪鎵弿浜岀淮鐮�...</text>
-        </view>
-      </view>
-    </view>
-    
+    <PageHeader title="璁惧宸℃"
+                @back="goBack" />
     <!-- 鏁版嵁鍒楄〃 -->
     <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-list">
+        <view v-for="(item, index) in taskTableData"
+              :key="index"
+              class="task-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 type="primary"
+                        size="small"
+                        @click.stop="startScanForTask(item)"
+                        :customStyle="{
+                borderRadius: '15px',
+                height: '30px',
+                fontSize: '12px',
+                marginRight: '8px'
+              }">
+                鎵爜涓婁紶
+              </u-button> -->
+              <u-button type="primary"
+                        size="small"
+                        @click.stop="startUploadForTask(item)"
+                        :customStyle="{
+                borderRadius: '15px',
+                height: '30px',
+                fontSize: '12px',
+                marginRight: '8px'
+              }">
+                鍥剧墖涓婁紶
+              </u-button>
+              <u-button type="success"
+                        size="small"
+                        @click.stop="viewAttachments(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-label">浠诲姟ID</text>
+              <text class="detail-value">{{ item.taskId || item.id }}</text>
+            </view>
+            <view class="detail-item">
+              <text class="detail-label">宸℃椤圭洰</text>
+              <text class="detail-value">{{ item.inspectionProject || '鏃�' }}</text>
+            </view>
+            <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-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>
+              <text class="detail-label">浠诲姟涓嬪彂鏃ユ湡</text>
+              <text class="detail-value">{{ item.dateStr }}</text>
             </view>
             <view class="detail-item">
-              <text class="detail-label">宸℃鏃堕棿锛�</text>
-              <text class="detail-value">{{ item.scanTime }}</text>
+              <text class="detail-label">宸℃鐘舵��</text>
+              <view class="detail-value">
+                <uni-tag v-if="item.fileStatus==2"
+                         text="宸插畬鎴�"
+                         size="small"
+                         type="success"
+                         inverted></uni-tag>
+                <uni-tag v-else-if="item.fileStatus==1"
+                         text="宸℃涓�"
+                         size="small"
+                         type="primary"
+                         inverted></uni-tag>
+                <uni-tag v-else
+                         text="鏈贰妫�"
+                         size="small"
+                         type="warning"
+                         inverted></uni-tag>
+              </view>
             </view>
           </view>
         </view>
+        <uni-load-more :status="loadMoreStatus"></uni-load-more>
       </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 v-if="taskTableData?.length === 0"
+            class="no-data">
+        <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 v-if="showVideoDialog"
+          class="video-modal-overlay"
+          @click="closeVideoPreview">
+      <view class="video-modal-container"
+            @click.stop>
+        <view class="video-modal-header">
+          <text class="video-modal-title">{{ currentVideoFile?.originalFilename || '瑙嗛棰勮' }}</text>
+          <view class="close-btn-video"
+                @click="closeVideoPreview">
+            <u-icon name="close"
+                    size="16"
+                    color="#fff"></u-icon>
+          </view>
+        </view>
+        <view class="video-modal-body">
+          <video v-if="currentVideoFile"
+                 :src="currentVideoFile.url || currentVideoFile.downloadUrl"
+                 class="video-player"
+                 controls
+                 autoplay
+                 @error="handleVideoError"></video>
+        </view>
+      </view>
     </view>
-    
-    <!-- 寮圭獥缁勪欢 -->
-    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
-    <qr-code-form-dia ref="qrCodeFormDia" @closeDia="handleQuery"></qr-code-form-dia>
   </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, onUnmounted, ref, nextTick, computed, reactive } from "vue";
+  import { onShow, onReachBottom, onPullDownRefresh } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  import { getLedgerById } from "@/api/equipmentManagement/ledger.js";
+  import {
+    inspectionTaskList,
+    uploadInspectionTask,
+  } from "@/api/inspectionManagement";
+  import { getToken } from "@/utils/auth";
+  import config from "@/config";
 
-// 缁勪欢寮曠敤
-const formDia = ref()
-const qrCodeFormDia = ref()
+  // 缁勪欢寮曠敤宸茬Щ闄�
 
-// 褰撳墠鏍囩
-const activeTab = ref('task')
+  // 鍔犺浇鎻愮ず鏂规硶
+  const showLoadingToast = message => {
+    uni.showLoading({
+      title: message,
+      mask: true,
+    });
+  };
+  const closeToast = () => {
+    uni.hideLoading();
+  };
 
-const tabName = ref('task')
-const currentTabIndex = ref(0)
+  // 琛ㄦ牸鏁版嵁
+  const taskTableData = ref([]); // 鐢熶骇宸℃鏁版嵁
 
-// 鏍囩椤垫暟鎹�
-const tabs = reactive([
-  { name: '鐢熶骇宸℃' },
-  { name: '鐜板満宸℃' }
-])
+  // 褰撳墠鎵弿鐨勪换鍔�
+  const currentScanningTask = ref(null);
+  const infoData = ref(null);
 
-// 琛ㄦ牸鏁版嵁
-const tableData = ref([])
-const tableLoading = ref(false)
-const total = ref(0)
-const pageNum = ref(1)
-const pageSize = ref(10)
+  // 瑙嗛棰勮鐩稿叧鐘舵��
+  const showVideoDialog = ref(false);
+  const currentVideoFile = ref(null);
 
-// 鎵爜鐩稿叧鐘舵��
-const isScanning = ref(false)
-const scanLoading = ref(false)
-const cameraError = ref('')
+  // 璇锋眰鍙栨秷鏍囧織锛岀敤浜庡彇娑堟鍦ㄨ繘琛岀殑璇锋眰
+  let isRequestCancelled = false;
 
-// 璁$畻灞炴��
-const scanButtonText = computed(() => {
-  if (scanLoading.value) return '姝e湪鍒濆鍖�...'
-  return isScanning.value ? '鍋滄鎵爜' : '寮�濮嬫壂鐮�'
-})
+  const pagesPames = reactive({
+    size: 10,
+    current: 1,
+  });
 
-// 鐢熷懡鍛ㄦ湡
-onMounted(() => {
-  // 寤惰繜鍒濆鍖栵紝纭繚DOM宸叉覆鏌�
-  nextTick(() => {
-    handleTabClick({ props: { name: 'task' } })
-  })
-})
-
-onShow(() => {
-  // 椤甸潰鏄剧ず鏃跺埛鏂版暟鎹�
-  getList()
-})
-
-// 鏍囩椤靛垏鎹�
-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 handleTabClick = (tab) => {
-  tabName.value = tab.props.name
-  activeTab.value = tab.props.name
-  tableData.value = []
-  getList()
-}
-
-// 鏌ヨ鏁版嵁
-const handleQuery = () => {
-  pageNum.value = 1
-  pageSize.value = 10
-  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()
-}
-
-// 涓婁紶
-const handleAdd = (row) => {
-  nextTick(() => {
-    formDia.value?.openDialog(row)
-  })
-}
-
-// 鏌ョ湅闄勪欢
-const viewFile = (row) => {
-  console.log('鏌ョ湅闄勪欢:', row)
-  uni.showToast({
-    title: '鏌ョ湅闄勪欢鍔熻兘寮�鍙戜腑',
-    icon: 'none'
-  })
-}
-
-// 鎵爜鐩稿叧鏂规硶
-const toggleScan = async () => {
-  if (isScanning.value) {
-    await stopScan()
-  } else {
-    await startScan()
-  }
-}
-
-const startScan = async () => {
-  try {
-    // 浣跨敤uniapp鐨勬壂鐮丄PI
-    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 stopScan = async () => {
-  isScanning.value = false
-}
-
-// 鎵爜鎴愬姛澶勭悊
-const handleScanSuccess = async (result) => {
-  try {
-    uni.showToast({
-      title: '璇嗗埆鎴愬姛',
-      icon: 'success'
-    })
-    
-    // 瑙f瀽浜岀淮鐮佹暟鎹�
-    let qrData
-    try {
-      qrData = JSON.parse(result.result)
-    } catch (e) {
-      qrData = { deviceName: result.result, location: '' }
+  const loadMoreStatus = computed(() => {
+    if (loading.value) {
+      return "loading";
     }
-    
-    callBackendAPI(qrData)
-  } catch (error) {
+    if (noMore.value) {
+      return "noMore";
+    }
+    return "more";
+  });
+  const totalSize = ref(0);
+  const noMore = computed(() => {
+    return taskTableData.value.length >= totalSize.value;
+  });
+  const loading = ref(false);
+
+  const reloadPage = () => {
+    pagesPames.current = 1;
+    taskTableData.value = [];
+    getList();
+  };
+  const loadPage = () => {
+    if (noMore.value || loading.value) return;
+    pagesPames.current += 1;
+    getList();
+  };
+
+  // 鐢熷懡鍛ㄦ湡
+  onMounted(() => {
+    // 寤惰繜鍒濆鍖栵紝纭繚DOM宸叉覆鏌�
+    // nextTick(() => {
+    //   getList()
+    // })
+  });
+
+  onReachBottom(() => {
+    loadPage();
+  });
+  onPullDownRefresh(() => {
+    reloadPage();
+    uni.stopPullDownRefresh();
+  });
+
+  onShow(() => {
+    // 椤甸潰鏄剧ず鏃跺埛鏂版暟鎹�
+    reloadPage();
+  });
+
+  // 缁勪欢閿�姣佹椂鐨勬竻鐞�
+  onUnmounted(() => {
+    // 璁剧疆鍙栨秷鏍囧織锛岄樆姝㈠悗缁殑寮傛鎿嶄綔
+    isRequestCancelled = true;
+  });
+
+  // 杩斿洖涓婁竴椤�
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  // 鑾峰彇鍒楄〃鏁版嵁
+  const getList = () => {
+    // 鏄剧ず鍔犺浇鎻愮ず
+    // showLoadingToast('鍔犺浇涓�...')
+
+    // 璁剧疆鍙栨秷鏍囧織
+    isRequestCancelled = false;
+    loading.value = true;
+    inspectionTaskList({ ...pagesPames })
+      .then(res => {
+        // 妫�鏌ョ粍浠舵槸鍚﹁繕瀛樺湪涓旇姹傛湭琚彇娑�
+        if (!isRequestCancelled) {
+          // 澶勭悊涓嶅悓鐨勬暟鎹粨鏋�
+          let records = [];
+          if (res && res.data) {
+            // 灏濊瘯澶氱鍙兘鐨勬暟鎹粨鏋�
+            totalSize.value = res.data.total;
+            if (Array.isArray(res.data.records)) {
+              records = res.data.records;
+            } else if (Array.isArray(res.data.rows)) {
+              records = res.data.rows;
+            } else if (Array.isArray(res.data)) {
+              records = res.data;
+            } else if (Array.isArray(res.data.list)) {
+              records = res.data.list;
+            }
+          }
+
+          if (records.length > 0) {
+            taskTableData.value = [
+              ...taskTableData.value,
+              ...records.map(record => {
+                record.fileStatus = getFileStatus(record);
+                return record;
+              }),
+            ];
+          } else {
+            taskTableData.value = [];
+            uni.showToast({
+              title: "鏆傛棤宸℃浠诲姟鏁版嵁",
+              icon: "none",
+            });
+          }
+        }
+        loading.value = false;
+        // 鍏抽棴鍔犺浇鎻愮ず
+        // closeToast()
+      })
+      .catch(err => {
+        // 妫�鏌ョ粍浠舵槸鍚﹁繕瀛樺湪涓旇姹傛湭琚彇娑�
+        if (!isRequestCancelled) {
+          taskTableData.value = [];
+          // 娣诲姞閿欒鎻愮ず
+          uni.showToast({
+            title: "鑾峰彇鏁版嵁澶辫触",
+            icon: "error",
+          });
+        }
+        loading.value = false;
+        // 鍏抽棴鍔犺浇鎻愮ず
+        // closeToast()
+      });
+  };
+
+  const getFileStatus = record => {
+    // 妫�鏌ユ槸鍚︽湁宸℃鐓х墖 (commonFileListBeforeVO)
+    const hasFiles =
+      (record.commonFileListBeforeVO &&
+        record.commonFileListBeforeVO.length > 0) ||
+      (record.commonFileListAfterVO && record.commonFileListAfterVO.length > 0) ||
+      (record.commonFileListVO && record.commonFileListVO.length > 0);
+
+    if (hasFiles) {
+      return 2; // 宸插畬鎴�
+    } else if (
+      record.inspectionResult !== undefined &&
+      record.inspectionResult !== null
+    ) {
+      return 1; // 宸℃涓� (宸叉湁缁撴灉浣嗘病鐓х墖锛屾垨鑰呮牴鎹笟鍔¢�昏緫瀹氫箟)
+    } else {
+      return 0; // 鏈贰妫�
+    }
+  };
+
+  // 涓烘寚瀹氫换鍔″紑濮嬫壂鐮侊紙鐪熸満锛�
+  const startScanForTask = async task => {
+    try {
+      currentScanningTask.value = task;
+      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 handleScanSuccess = result => {
+    try {
+      // 瑙f瀽浜岀淮鐮佹暟鎹紝鎻愬彇deviceId
+      let deviceId = "";
+
+      if (result?.result && typeof result.result === "string") {
+        if (result.result.includes("deviceId=")) {
+          const match = result.result.match(/deviceId=(\d+)/);
+          if (match && match[1]) deviceId = match[1];
+        } else {
+          try {
+            const qrData = JSON.parse(result.result);
+            deviceId = qrData.deviceId || qrData.qrCodeId || "";
+          } catch (e) {
+            deviceId = result.result;
+          }
+        }
+      }
+
+      if (!deviceId) {
+        uni.showToast({ title: "鏈瘑鍒埌璁惧ID", icon: "error" });
+        return;
+      }
+
+      const currentTaskId =
+        currentScanningTask.value?.taskId || currentScanningTask.value?.id;
+      if (!currentTaskId) {
+        uni.showToast({ title: "浠诲姟淇℃伅缂哄け", icon: "error" });
+        return;
+      }
+
+      if (deviceId === currentTaskId.toString()) {
+        uni.showToast({ title: "璇嗗埆鎴愬姛", icon: "success" });
+        openUploadDialog(currentScanningTask.value);
+      } else {
+        uni.showToast({ title: "璇锋壂鎻忔纭殑璁惧", icon: "error" });
+      }
+    } catch (error) {
+      console.error("鎵爜缁撴灉澶勭悊澶辫触:", error);
+      uni.showToast({
+        title: error?.message || "鏁版嵁瑙f瀽澶辫触",
+        icon: "error",
+      });
+    }
+  };
+
+  // 鎵撳紑涓婁紶椤甸潰
+  const openUploadDialog = task => {
+    // 灏嗕换鍔′俊鎭紶閫掑埌涓婁紶椤甸潰
+    const taskData = encodeURIComponent(JSON.stringify(task));
+    uni.navigateTo({
+      url: `/pages/inspectionUpload/upload?taskInfo=${taskData}`,
+    });
+  };
+
+  // 鍥剧墖涓婁紶(鍙�夋嫨鍥剧墖涓婁紶鎴栬�呮槸鐩告満鎷嶇収)
+  const startUploadForTask = async (task, type) => {
+    // 鎵撳紑涓婁紶椤甸潰
+    openUploadDialog(task);
+  };
+
+  // 鏌ョ湅闄勪欢 - 璺宠浆鍒伴檮浠堕〉闈�
+  const viewAttachments = async task => {
+    // 浠呬紶閫掑繀瑕佺殑浠诲姟淇℃伅鍜� commonFileListBeforeVO 闄勪欢鍒楄〃
+    const taskInfoToPass = {
+      taskName: task.taskName,
+      commonFileListBeforeVO: task.commonFileListBeforeVO || [],
+    };
+    const taskData = encodeURIComponent(JSON.stringify(taskInfoToPass));
+    uni.navigateTo({
+      url: `/pages/inspectionUpload/attachment?taskInfo=${taskData}`,
+    });
+  };
+
+  // 鍒ゆ柇鏄惁涓哄浘鐗囨枃浠�
+  const isImageFile = file => {
+    // 妫�鏌ontentType瀛楁
+    if (file.contentType && file.contentType.startsWith("image/")) {
+      return true;
+    }
+
+    // 妫�鏌ュ師鏈夌殑type瀛楁
+    if (file.type === "image") return true;
+
+    // 妫�鏌ユ枃浠舵墿灞曞悕
+    const name = file.bucketFilename || file.originalFilename || file.name || "";
+    const ext = name.split(".").pop()?.toLowerCase();
+    return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext);
+  };
+
+  // 鏂囦欢璁块棶鍩虹鍩燂紙鍚庣瑕佹眰鍓嶇紑锛�
+  const filePreviewBase = config.fileUrl;
+
+  // 灏嗗悗绔繑鍥炵殑鏂囦欢鍦板潃瑙勮寖鎴愬彲璁块棶URL
+  // 鍏煎鍦烘櫙锛�
+  // - 宸茬粡鏄� http/https锛氱洿鎺ヨ繑鍥�
+  // - 浠� / 寮�澶达細鎷兼帴 filePreviewBase
+  // - Windows 鏈湴璺緞锛堝 D:\ruoyi\prod\uploads...\xx.jpg锛夛細灏濊瘯鎴彇 prod 涔嬪悗鐨勭浉瀵硅矾寰勫苟鎷兼帴 filePreviewBase
+  const normalizeFileUrl = rawUrl => {
+    try {
+      if (!rawUrl || typeof rawUrl !== "string") return "";
+      const url = rawUrl.trim();
+      if (!url) return "";
+      if (/^https?:\/\//i.test(url)) return url;
+      if (url.startsWith("/")) return `${filePreviewBase}${url}`;
+
+      // Windows path -> web path
+      if (/^[a-zA-Z]:\\/.test(url)) {
+        const normalized = url.replace(/\\/g, "/");
+        const idx = normalized.indexOf("/prod/");
+        if (idx >= 0) {
+          const relative = normalized.slice(idx + "/prod/".length);
+          return `${filePreviewBase}/${relative}`;
+        }
+        // 鍏滃簳锛氭棤娉曟帹鏂槧灏勮鍒欐椂锛岃嚦灏戞妸鍙嶆枩鏉犲彉鎴愭鏂滄潬
+        return normalized;
+      }
+
+      // 鍏朵粬鐩稿璺緞锛氱洿鎺ョ敤 baseUrl 鎷间竴涓�
+      return `${filePreviewBase}/${url.replace(/^\//, "")}`;
+    } catch (e) {
+      return rawUrl || "";
+    }
+  };
+
+  // 棰勮闄勪欢
+  const previewAttachment = file => {
+    if (isImageFile(file)) {
+      // 棰勮鍥剧墖
+      const imageUrls = getCurrentViewAttachments()
+        .filter(f => isImageFile(f))
+        .map(f => f.url || f.downloadUrl);
+
+      uni.previewImage({
+        urls: imageUrls,
+        current: file.url || file.downloadUrl,
+      });
+    } else {
+      // 棰勮瑙嗛 - 鏄剧ず瑙嗛鎾斁寮圭獥
+      showVideoPreview(file);
+    }
+  };
+
+  // 鏄剧ず瑙嗛棰勮
+  const showVideoPreview = file => {
+    currentVideoFile.value = file;
+    showVideoDialog.value = true;
+  };
+
+  // 鍏抽棴瑙嗛棰勮
+  const closeVideoPreview = () => {
+    showVideoDialog.value = false;
+    currentVideoFile.value = null;
+  };
+
+  // 瑙嗛鎾斁閿欒澶勭悊
+  const handleVideoError = error => {
     uni.showToast({
-      title: error.message || '鏁版嵁瑙f瀽澶辫触',
-      icon: 'error'
-    })
-  }
-}
-
-const callBackendAPI = (result) => {
-  nextTick(() => {
-    qrCodeFormDia.value?.openDialog(result)
-  })
-}
-
-// 鎵爜澶勭悊
-const handleScanCode = (result) => {
-  console.log('鎵爜缁撴灉:', result)
-  handleScanSuccess(result)
-}
-
-// 鎽勫儚澶撮敊璇鐞�
-const handleCameraError = (error) => {
-  console.error('鎽勫儚澶撮敊璇�:', error)
-  cameraError.value = '鎽勫儚澶磋闂け璐ワ紝璇锋鏌ユ潈闄愯缃�'
-}
+      title: "瑙嗛鎾斁澶辫触",
+      icon: "error",
+    });
+  };
 </script>
 
-<style scoped lang="scss">
-.inspection-upload-page {
-  min-height: 100vh;
-  background-color: #f5f5f5;
-}
-
-.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;
-}
-
-.scan-section {
-  background-color: #fff;
-	padding: 10px;
-}
-
-.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; }
-}
-
-.status-info {
-  margin-top: 16px;
-  text-align: center;
-}
-
-.scanning-text {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: #1890ff;
-  margin-top: 8px;
-}
-
-.scanning-label {
-  margin-left: 8px;
-  font-size: 14px;
-}
-
-.table-section {
-  padding: 0 15px;
-}
-
-.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);
+<style scoped>
+  .inspection-upload-page {
+    min-height: 100vh;
+    background-color: #f5f5f5;
   }
-}
 
-.task-header, .qr-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-  margin-bottom: 10px;
-}
+  .table-section {
+    padding: 15px;
+  }
 
-.task-info, .qr-info {
-  flex: 1;
-}
+  .task-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
 
-.task-name, .device-name {
-  font-size: 16px;
-  font-weight: 600;
-  color: #333;
-  margin-bottom: 4px;
-}
+  .task-item {
+    background: #fff;
+    border-radius: 12px;
+    padding: 15px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    transition: all 0.3s ease;
+  }
 
-.task-location, .device-location {
-  font-size: 14px;
-  color: #666;
-}
+  .task-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 12px;
+  }
 
-.task-actions, .qr-actions {
-  margin-left: 10px;
-}
+  .task-info {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
 
-.task-details, .qr-details {
+  .task-name {
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+  }
+
+  .task-location {
+    font-size: 14px;
+    color: #666;
+  }
+
+  .task-actions {
+    display: flex;
+    gap: 8px;
+  }
+
+  .task-details {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+
   .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;
-    }
+    justify-content: space-between;
+    align-items: center;
   }
-}
 
-.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;
-}
+  .detail-label {
+    font-size: 12px;
+    color: #999;
+  }
 
-.loading-text {
-  margin-top: 10px;
-  font-size: 14px;
-  color: #666;
-}
+  .detail-value {
+    font-size: 12px;
+    color: #666;
+    font-weight: 500;
+  }
 
-.pagination-container {
-  padding: 20px 15px;
-  background-color: #fff;
-  margin-top: 10px;
-}
+  .no-data {
+    text-align: center;
+    padding: 40px 20px;
+    color: #999;
+    font-size: 14px;
+  }
+
+  /* 鎵爜寮圭獥鏍峰紡 */
+  .qr-scan-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.8);
+    z-index: 9999;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .qr-scan-container {
+    width: 90vw;
+    max-width: 400px;
+    background: #fff;
+    border-radius: 12px;
+    padding: 20px;
+    position: relative;
+  }
+
+  .scan-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 15px;
+  }
+
+  .scan-title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #333;
+  }
+
+  .qr-camera {
+    width: 100%;
+    height: 300px;
+    border-radius: 8px;
+    overflow: hidden;
+    margin-bottom: 15px;
+  }
+
+  .scan-frame-wrapper {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
+  .scan-frame {
+    width: 200px;
+    height: 200px;
+    border: 2px solid #409eff;
+    border-radius: 8px;
+    position: relative;
+  }
+
+  .scan-frame::before,
+  .scan-frame::after {
+    content: "";
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    border: 2px solid #409eff;
+  }
+
+  .scan-frame::before {
+    top: -2px;
+    left: -2px;
+    border-right: none;
+    border-bottom: none;
+  }
+
+  .scan-frame::after {
+    bottom: -2px;
+    right: -2px;
+    border-left: none;
+    border-top: none;
+  }
+
+  .scan-tip {
+    margin-top: 10px;
+    font-size: 14px;
+    color: #666;
+    text-align: center;
+  }
+
+  /* 鑷畾涔夋ā鎬佹鏍峰紡 */
+  .custom-modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    z-index: 10000;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 20px;
+  }
+
+  .custom-modal-container {
+    width: 100%;
+    max-width: 500px;
+    max-height: 80vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  /* 瑙嗛棰勮寮圭獥鏍峰紡 */
+  .video-modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.8);
+    z-index: 10001;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 20px;
+  }
+
+  .video-modal-container {
+    width: 90%;
+    max-width: 800px;
+    max-height: 80vh;
+    background: #000;
+    border-radius: 12px;
+    overflow: hidden;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
+  }
+
+  .video-modal-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 15px 20px;
+    background: rgba(0, 0, 0, 0.7);
+    color: #fff;
+  }
+
+  .video-modal-title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #fff;
+  }
+
+  .close-btn-video {
+    width: 28px;
+    height: 28px;
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.2);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: all 0.3s ease;
+  }
+
+  .close-btn-video:hover {
+    background: rgba(255, 255, 255, 0.3);
+    transform: scale(1.1);
+  }
+
+  .video-modal-body {
+    position: relative;
+    background: #000;
+  }
+
+  .video-player {
+    width: 100%;
+    height: auto;
+    max-height: 60vh;
+    display: block;
+  }
 </style>
\ No newline at end of file

--
Gitblit v1.9.3