gaoluyang
2025-09-20 d85836bf6b1574122830f6db8770e98184edd51c
巡检上传页面
已添加5个文件
已修改4个文件
1659 ■■■■■ 文件已修改
src/api/equipmentManagement/inspection.js 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inspectionManagement/index.js 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inspectionUpload/index.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/equipmentManagement/inspection/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/components/formDia.vue 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/components/qrCodeFormDia.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/inspectionUpload/index.vue 597 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/equipmentManagement/inspection.js
@@ -1,220 +1,377 @@
import request from "@/utils/request";
import request from '@/utils/request'
// ==================== å·¡æ£€ä»»åŠ¡ç®¡ç† ====================
/**
 * @desc èŽ·å–å·¡æ£€æ¸…å•åˆ—è¡¨
 * @param {Object} params - æŸ¥è¯¢å‚æ•°
 * @param {string} params.date - å·¡æ£€æ—¥æœŸ
 * @param {string} params.inspector - å·¡æ£€å‘˜
 * @param {number} params.status - å·¡æ£€çŠ¶æ€ 0:待巡检 1:巡检中 2:已完成
 * @desc æŸ¥è¯¢å·¡æ£€ä»»åŠ¡åˆ—è¡¨
 * @param {Object} query - æŸ¥è¯¢å‚æ•°
 * @param {string} query.date - å·¡æ£€æ—¥æœŸ
 * @param {string} query.status - ä»»åŠ¡çŠ¶æ€
 * @param {string} query.inspector - å·¡æ£€å‘˜
 * @returns {Promise}
 */
export const getInspectionList = (params) => {
export function getInspectionTaskList(query) {
  return request({
    url: "/device/inspection/list",
    method: "get",
    params,
  });
};
    url: '/equipment/inspection/task/list',
    method: 'get',
    params: query
  })
}
/**
 * @desc èŽ·å–å·¡æ£€è¯¦æƒ…
 * @param {string|number} id - å·¡æ£€ID
 * @desc æŸ¥è¯¢å·¡æ£€ä»»åŠ¡è¯¦ç»†
 * @param {string|number} id - ä»»åŠ¡ID
 * @returns {Promise}
 */
export const getInspectionDetail = (id) => {
export function getInspectionTask(id) {
  return request({
    url: `/device/inspection/${id}`,
    method: "get",
  });
};
    url: '/equipment/inspection/task/' + id,
    method: 'get'
  })
}
/**
 * @desc å¼€å§‹å·¡æ£€
 * @param {Object} data - å·¡æ£€æ•°æ®
 * @param {string|number} data.inspectionId - å·¡æ£€ID
 * @param {string} data.startTime - å¼€å§‹æ—¶é—´
 * @desc åˆ›å»ºå·¡æ£€ä»»åŠ¡
 * @param {Object} data - ä»»åŠ¡æ•°æ®
 * @returns {Promise}
 */
export const startInspection = (data) => {
export function createInspectionTask(data) {
  return request({
    url: "/device/inspection/start",
    method: "post",
    data,
  });
};
    url: '/equipment/inspection/task',
    method: 'post',
    data: data
  })
}
/**
 * @desc æ›´æ–°å·¡æ£€ä»»åŠ¡
 * @param {Object} data - ä»»åŠ¡æ•°æ®
 * @returns {Promise}
 */
export function updateInspectionTask(data) {
  return request({
    url: '/equipment/inspection/task',
    method: 'put',
    data: data
  })
}
/**
 * @desc åˆ é™¤å·¡æ£€ä»»åŠ¡
 * @param {string|number} id - ä»»åŠ¡ID
 * @returns {Promise}
 */
export function deleteInspectionTask(id) {
  return request({
    url: '/equipment/inspection/task/' + id,
    method: 'delete'
  })
}
/**
 * @desc å¼€å§‹å·¡æ£€ä»»åŠ¡
 * @param {string|number} taskId - ä»»åŠ¡ID
 * @returns {Promise}
 */
export function startInspectionTask(taskId) {
  return request({
    url: '/equipment/inspection/task/' + taskId + '/start',
    method: 'post'
  })
}
/**
 * @desc å®Œæˆå·¡æ£€ä»»åŠ¡
 * @param {string|number} taskId - ä»»åŠ¡ID
 * @param {Object} data - å®Œæˆæ•°æ®
 * @returns {Promise}
 */
export function completeInspectionTask(taskId, data) {
  return request({
    url: '/equipment/inspection/task/' + taskId + '/complete',
    method: 'post',
    data: data
  })
}
// ==================== å·¡æ£€è®°å½•管理 ====================
/**
 * @desc æäº¤å·¡æ£€è®°å½•
 * @param {Object} data - å·¡æ£€è®°å½•数据
 * @param {string|number} data.deviceId - è®¾å¤‡ID
 * @param {string} data.deviceCode - è®¾å¤‡ç¼–码
 * @param {string} data.inspectionDate - å·¡æ£€æ—¥æœŸ
 * @param {string} data.inspector - å·¡æ£€å‘˜
 * @param {string} data.scanTime - æ‰«ç æ—¶é—´
 * @param {Array} data.items - å·¡æ£€é¡¹ç›®åˆ—表
 * @param {string} data.completedAt - å®Œæˆæ—¶é—´
 * @returns {Promise}
 */
export const submitInspectionRecord = (data) => {
export function submitInspectionRecord(data) {
  return request({
    url: "/device/inspection/submit",
    method: "post",
    data,
  });
};
    url: '/equipment/inspection/record/submit',
    method: 'post',
    data: data
  })
}
/**
 * @desc æ›´æ–°å·¡æ£€é¡¹ç›®
 * @param {Object} data - å·¡æ£€é¡¹ç›®æ•°æ®
 * @param {string|number} data.inspectionId - å·¡æ£€ID
 * @param {string|number} data.itemId - é¡¹ç›®ID
 * @param {string} data.result - å·¡æ£€ç»“æžœ normal:正常 abnormal:异常
 * @param {string} data.abnormalDesc - å¼‚常描述
 * @param {Array} data.images - å›¾ç‰‡åˆ—表
 * @param {Array} data.videos - è§†é¢‘列表
 * @param {string} data.remark - å¤‡æ³¨
 * @desc æŸ¥è¯¢å·¡æ£€è®°å½•列表
 * @param {Object} query - æŸ¥è¯¢å‚æ•°
 * @returns {Promise}
 */
export const updateInspectionItem = (data) => {
export function getInspectionRecordList(query) {
  return request({
    url: "/device/inspection/item/update",
    method: "put",
    data,
  });
};
    url: '/equipment/inspection/record/list',
    method: 'get',
    params: query
  })
}
/**
 * @desc æ‰«ç æ‰“卡
 * @param {Object} data - æ‰“卡数据
 * @param {string|number} data.inspectionId - å·¡æ£€ID
 * @param {string} data.deviceCode - è®¾å¤‡ç¼–码
 * @param {string} data.qrCode - äºŒç»´ç å†…容
 * @param {string} data.checkInTime - æ‰“卡时间
 * @param {string} data.location - æ‰“卡位置
 * @desc æŸ¥è¯¢å·¡æ£€è®°å½•详情
 * @param {string|number} id - è®°å½•ID
 * @returns {Promise}
 */
export const checkInByQRCode = (data) => {
export function getInspectionRecord(id) {
  return request({
    url: "/device/inspection/checkin",
    method: "post",
    data,
  });
};
    url: '/equipment/inspection/record/' + id,
    method: 'get'
  })
}
// ==================== æ–‡ä»¶ä¸Šä¼  ====================
/**
 * @desc ä¸Šä¼ å·¡æ£€æ–‡ä»¶ï¼ˆå›¾ç‰‡/视频)
 * @desc ä¸Šä¼ å·¡æ£€å›¾ç‰‡
 * @param {File} file - å›¾ç‰‡æ–‡ä»¶
 * @returns {Promise}
 */
export function uploadInspectionImage(file) {
  return request({
    url: '/equipment/inspection/upload/image',
    method: 'post',
    data: file,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
}
/**
 * @desc ä¸Šä¼ å·¡æ£€è§†é¢‘
 * @param {File} file - è§†é¢‘文件
 * @returns {Promise}
 */
export function uploadInspectionVideo(file) {
  return request({
    url: '/equipment/inspection/upload/video',
    method: 'post',
    data: file,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
}
/**
 * @desc æ‰¹é‡ä¸Šä¼ æ–‡ä»¶
 * @param {FormData} formData - æ–‡ä»¶æ•°æ®
 * @returns {Promise}
 */
export const uploadInspectionFile = (formData) => {
export function batchUploadFiles(formData) {
  return request({
    url: "/device/inspection/upload",
    method: "post",
    url: '/equipment/inspection/upload/batch',
    method: 'post',
    data: formData,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  });
};
  })
}
// ==================== å¼‚常报备管理 ====================
/**
 * @desc æŠ¥å¤‡å¼‚常情况
 * @param {Object} data - å¼‚常报备数据
 * @returns {Promise}
 */
export function reportAbnormalSituation(data) {
  return request({
    url: '/equipment/inspection/abnormal/report',
    method: 'post',
    data: data
  })
}
/**
 * @desc æŸ¥è¯¢å¼‚常报备列表
 * @param {Object} query - æŸ¥è¯¢å‚æ•°
 * @returns {Promise}
 */
export function getAbnormalReportList(query) {
  return request({
    url: '/equipment/inspection/abnormal/list',
    method: 'get',
    params: query
  })
}
/**
 * @desc æŸ¥è¯¢å¼‚常报备详情
 * @param {string|number} id - æŠ¥å¤‡ID
 * @returns {Promise}
 */
export function getAbnormalReport(id) {
  return request({
    url: '/equipment/inspection/abnormal/' + id,
    method: 'get'
  })
}
/**
 * @desc å¤„理异常报备
 * @param {string|number} id - æŠ¥å¤‡ID
 * @param {Object} data - å¤„理数据
 * @returns {Promise}
 */
export function handleAbnormalReport(id, data) {
  return request({
    url: '/equipment/inspection/abnormal/' + id + '/handle',
    method: 'post',
    data: data
  })
}
/**
 * @desc è½¬æ´¾å¼‚常报备
 * @param {string|number} id - æŠ¥å¤‡ID
 * @param {Object} data - è½¬æ´¾æ•°æ®
 * @returns {Promise}
 */
export function transferAbnormalReport(id, data) {
  return request({
    url: '/equipment/inspection/abnormal/' + id + '/transfer',
    method: 'post',
    data: data
  })
}
/**
 * @desc å…³é—­å¼‚常报备
 * @param {string|number} id - æŠ¥å¤‡ID
 * @param {Object} data - å…³é—­æ•°æ®
 * @returns {Promise}
 */
export function closeAbnormalReport(id, data) {
  return request({
    url: '/equipment/inspection/abnormal/' + id + '/close',
    method: 'post',
    data: data
  })
}
// ==================== ç»Ÿè®¡åˆ†æž ====================
/**
 * @desc èŽ·å–å·¡æ£€ç»Ÿè®¡æ•°æ®
 * @param {Object} params - æŸ¥è¯¢å‚æ•°
 * @param {string} params.startDate - å¼€å§‹æ—¥æœŸ
 * @param {string} params.endDate - ç»“束日期
 * @param {string} params.inspector - å·¡æ£€å‘˜
 * @returns {Promise}
 */
export const getInspectionStats = (params) => {
export function getInspectionStats(params) {
  return request({
    url: "/device/inspection/stats",
    method: "get",
    params,
  });
};
    url: '/equipment/inspection/stats',
    method: 'get',
    params: params
  })
}
/**
 * @desc èŽ·å–å·¡æ£€åŽ†å²è®°å½•
 * @desc èŽ·å–å¼‚å¸¸ç»Ÿè®¡æ•°æ®
 * @param {Object} params - æŸ¥è¯¢å‚æ•°
 * @param {string|number} params.deviceId - è®¾å¤‡ID
 * @param {number} params.current - å½“前页
 * @param {number} params.size - é¡µé¢å¤§å°
 * @returns {Promise}
 */
export const getInspectionHistory = (params) => {
export function getAbnormalStats(params) {
  return request({
    url: "/device/inspection/history",
    method: "get",
    params,
  });
};
    url: '/equipment/inspection/abnormal/stats',
    method: 'get',
    params: params
  })
}
/**
 * @desc å¯¼å‡ºå·¡æ£€è®°å½•
 * @desc å¯¼å‡ºå·¡æ£€æŠ¥å‘Š
 * @param {Object} params - å¯¼å‡ºå‚æ•°
 * @param {string} params.startDate - å¼€å§‹æ—¥æœŸ
 * @param {string} params.endDate - ç»“束日期
 * @param {string} params.inspector - å·¡æ£€å‘˜
 * @param {Array} params.deviceIds - è®¾å¤‡ID列表
 * @returns {Promise}
 */
export const exportInspectionRecords = (params) => {
export function exportInspectionReport(params) {
  return request({
    url: "/device/inspection/export",
    method: "get",
    params,
    url: '/equipment/inspection/export/report',
    method: 'get',
    params: params,
    responseType: 'blob'
  });
};
  })
}
/**
 * @desc åˆ é™¤å·¡æ£€è®°å½•
 * @param {string|number} id - å·¡æ£€è®°å½•ID
 * @desc å¯¼å‡ºå¼‚常报备记录
 * @param {Object} params - å¯¼å‡ºå‚æ•°
 * @returns {Promise}
 */
export const deleteInspectionRecord = (id) => {
export function exportAbnormalRecords(params) {
  return request({
    url: `/device/inspection/${id}`,
    method: "delete",
  });
};
    url: '/equipment/inspection/abnormal/export',
    method: 'get',
    params: params,
    responseType: 'blob'
  })
}
/**
 * @desc æ‰¹é‡åˆ é™¤å·¡æ£€è®°å½•
 * @param {Array} ids - å·¡æ£€è®°å½•ID列表
 * @returns {Promise}
 */
export const batchDeleteInspectionRecords = (ids) => {
  return request({
    url: "/device/inspection/batch/delete",
    method: "delete",
    data: { ids },
  });
};
// ==================== è®¾å¤‡äºŒç»´ç  ====================
/**
 * @desc èŽ·å–è®¾å¤‡äºŒç»´ç 
 * @param {string|number} deviceId - è®¾å¤‡ID
 * @returns {Promise}
 */
export const getDeviceQRCode = (deviceId) => {
export function getDeviceQRCode(deviceId) {
  return request({
    url: `/device/qrcode/${deviceId}`,
    method: "get",
  });
};
    url: `/equipment/device/${deviceId}/qrcode`,
    method: 'get'
  })
}
/**
 * @desc éªŒè¯è®¾å¤‡äºŒç»´ç 
 * @param {Object} data - éªŒè¯æ•°æ®
 * @param {string} data.qrCode - äºŒç»´ç å†…容
 * @param {string|number} data.deviceId - è®¾å¤‡ID
 * @returns {Promise}
 */
export const verifyDeviceQRCode = (data) => {
export function verifyDeviceQRCode(data) {
  return request({
    url: "/device/qrcode/verify",
    method: "post",
    data,
  });
};
    url: '/equipment/device/qrcode/verify',
    method: 'post',
    data: data
  })
}
// ==================== æ¨¡æ¿ç®¡ç† ====================
/**
 * @desc èŽ·å–å·¡æ£€æ¨¡æ¿åˆ—è¡¨
 * @param {Object} query - æŸ¥è¯¢å‚æ•°
 * @returns {Promise}
 */
export function getInspectionTemplateList(query) {
  return request({
    url: '/equipment/inspection/template/list',
    method: 'get',
    params: query
  })
}
/**
 * @desc èŽ·å–å·¡æ£€æ¨¡æ¿è¯¦æƒ…
 * @param {string|number} id - æ¨¡æ¿ID
 * @returns {Promise}
 */
export function getInspectionTemplate(id) {
  return request({
    url: '/equipment/inspection/template/' + id,
    method: 'get'
  })
}
src/api/inspectionManagement/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
// å·¡æ£€ç®¡ç†
import request from '@/utils/request'
// å·¡æ£€ä»»åŠ¡è¡¨è¡¨æŸ¥è¯¢
export function inspectionTaskList(query) {
    return request({
        url: '/inspectionTask/list',
        method: 'get',
        params: query
    })
}
// å·¡æ£€ä»»åŠ¡è¡¨æ–°å¢žä¿®æ”¹
export function addOrEditInspectionTask(query) {
    return request({
        url: '/inspectionTask/addOrEditInspectionTask',
        method: 'post',
        data: query
    })
}
// å·¡æ£€ä»»åŠ¡è¡¨åˆ é™¤
export function delInspectionTask(query) {
    return request({
        url: '/inspectionTask/delInspectionTask',
        method: 'delete',
        data: query
    })
}
// å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨åˆ é™¤
export function delTimingTask(query) {
    return request({
        url: '/timingTask/delTimingTask',
        method: 'delete',
        data: query
    })
}
// /inspectionTask/addOrEditInspectionTask
// å·¡æ£€ä¸Šä¼ 
export function uploadInspectionTask(query) {
    return request({
        url: '/inspectionTask/addOrEditInspectionTask',
        method: 'post',
        data: query
    })
}
// å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨æŸ¥è¯¢
export function timingTaskList(query) {
    return request({
        url: '/timingTask/list',
        method: 'get',
        params: query
    })
}
// å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨æ–°å¢žä¿®æ”¹
export function addOrEditTimingTask(query) {
    return request({
        url: '/timingTask/addOrEditTimingTask',
        method: 'post',
        data: query
    })
}
src/api/inspectionUpload/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
// å·¡æ£€ä¸Šä¼ 
import request from '@/utils/request'
// äºŒç»´ç ç®¡ç†è¡¨æŸ¥è¯¢
export function qrCodeList(query) {
    return request({
        url: '/qrCode/list',
        method: 'get',
        params: query
    })
}
// äºŒç»´ç æ‰«ç è®°å½•表查询
export function qrCodeScanRecordList(query) {
    return request({
        url: '/qrCodeScanRecord/list',
        method: 'get',
        params: query
    })
}
// äºŒç»´ç ç®¡ç†è¡¨æ–°å¢žä¿®æ”¹
export function addOrEditQrCode(query) {
    return request({
        url: '/qrCode/addOrEditQrCode',
        method: 'post',
        data: query
    })
}
// äºŒç»´ç æ‰«ç è®°å½•表新增修改
export function addOrEditQrCodeRecord(query) {
    return request({
        url: '/qrCodeScanRecord/addOrEditQrCodeRecord',
        method: 'post',
        data: query
    })
}
// äºŒç»´ç æ‰«ç è®°å½•表新增修改
export function delQrCode(query) {
    return request({
        url: '/qrCode/delQrCode',
        method: 'delete',
        data: query
    })
}
src/pages.json
@@ -385,6 +385,13 @@
        "navigationBarTitleText": "结果验证",
        "navigationStyle": "custom"
      }
    },
    {
      "path": "pages/inspectionUpload/index",
      "style": {
        "navigationBarTitleText": "巡检上传",
        "navigationStyle": "custom"
      }
    }
  ],
  "subPackages": [
src/pages/equipmentManagement/inspection/index.vue
@@ -96,7 +96,7 @@
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import PageHeader from '@/components/PageHeader.vue'
import { getInspectionList } from '@/api/equipmentManagement/inspection'
// import { getInspectionList } from '@/api/inspectionUpload/index'
import dayjs from 'dayjs'
// é€‰ä¸­çš„æ—¥æœŸ
src/pages/index.vue
@@ -269,7 +269,7 @@
    },
    {
        icon: '/static/images/icon/shebeixunjian@2x.png',
        label: '设备巡检',
        label: '巡检上传',
    },
    {
        icon: 'flash',
@@ -377,9 +377,9 @@
                url: '/pages/equipmentManagement/upkeep/index'
            });
            break;
        case '设备巡检':
        case '巡检上传':
            uni.navigateTo({
                url: '/pages/equipmentManagement/inspection/index'
                url: '/pages/inspectionUpload/index'
            });
            break;
        case '智能派单':
src/pages/inspectionUpload/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,256 @@
<template>
  <u-popup
    v-model="dialogVisitable"
    mode="center"
    :round="10"
    :closeable="true"
    @close="cancel"
  >
    <view class="popup-content">
      <view class="popup-header">
        <text class="popup-title">上传</text>
      </view>
      <view class="upload-container">
        <view class="form-container">
          <view class="title">生产前</view>
          <u-upload
            :fileList="beforeModelValue"
            @afterRead="afterRead"
            @delete="deleteFile"
            name="before"
            multiple
            :maxCount="10"
            :maxSize="1024 * 1024"
            accept="video/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
        <view class="form-container">
          <view class="title">生产后</view>
          <u-upload
            :fileList="afterModelValue"
            @afterRead="afterRead"
            @delete="deleteFile"
            name="after"
            multiple
            :maxCount="10"
            :maxSize="1024 * 1024"
            accept="video/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
        <view class="form-container">
          <view class="title">生产问题</view>
          <u-upload
            :fileList="issueModelValue"
            @afterRead="afterRead"
            @delete="deleteFile"
            name="issue"
            multiple
            :maxCount="10"
            :maxSize="1024 * 1024"
            accept="video/*"
            :previewFullImage="true"
          ></u-upload>
        </view>
      </view>
      <view class="popup-footer">
        <u-button @click="cancel" :customStyle="{ marginRight: '10px' }">取消</u-button>
        <u-button type="primary" @click="submitForm">保存</u-button>
      </view>
    </view>
  </u-popup>
</template>
<script setup>
import { ref } from 'vue'
import { submitInspectionRecord } from '@/api/equipmentManagement/inspection.js'
const emit = defineEmits(['closeDia'])
const dialogVisitable = ref(false)
const beforeModelValue = ref([])
const afterModelValue = ref([])
const issueModelValue = ref([])
const infoData = ref(null)
// æ–‡ä»¶ä¸Šä¼ å¤„理
const afterRead = (event) => {
  const { name, file } = event
  // ä¸Šä¼ æ–‡ä»¶åˆ°æœåС噍
  uni.uploadFile({
    url: '/api/upload', // æ›¿æ¢ä¸ºå®žé™…的上传接口
    filePath: file.url,
    name: 'file',
    success: (res) => {
      const data = JSON.parse(res.data)
      if (data.code === 200) {
        const fileItem = {
          url: data.data.url,
          name: file.name,
          status: 'success'
        }
        // æ ¹æ®name添加到对应的数组
        if (name === 'before') {
          beforeModelValue.value.push(fileItem)
        } else if (name === 'after') {
          afterModelValue.value.push(fileItem)
        } else if (name === 'issue') {
          issueModelValue.value.push(fileItem)
        }
        uni.showToast({
          title: '上传成功',
          icon: 'success'
        })
      } else {
        uni.showToast({
          title: '上传失败',
          icon: 'error'
        })
      }
    },
    fail: () => {
      uni.showToast({
        title: '上传失败',
        icon: 'error'
      })
    }
  })
}
// åˆ é™¤æ–‡ä»¶
const deleteFile = (event) => {
  const { name, index } = event
  if (name === 'before') {
    beforeModelValue.value.splice(index, 1)
  } else if (name === 'after') {
    afterModelValue.value.splice(index, 1)
  } else if (name === 'issue') {
    issueModelValue.value.splice(index, 1)
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    let arr = []
    if (beforeModelValue.value.length > 0) {
      arr.push(...beforeModelValue.value.map(item => ({ ...item, statusType: 0 })))
    }
    if (afterModelValue.value.length > 0) {
      arr.push(...afterModelValue.value.map(item => ({ ...item, statusType: 1 })))
    }
    if (issueModelValue.value.length > 0) {
      arr.push(...issueModelValue.value.map(item => ({ ...item, statusType: 2 })))
    }
    // æäº¤æ•°æ®
    infoData.value.storageBlobDTO = arr
    await submitInspectionRecord({ ...infoData.value })
    uni.showToast({
      title: '提交成功',
      icon: 'success'
    })
    cancel()
  } catch (error) {
    console.error('提交失败:', error)
    uni.showToast({
      title: '提交失败',
      icon: 'error'
    })
  }
}
// æ‰“开弹框
const openDialog = async (row) => {
  infoData.value = row
  dialogVisitable.value = true
  // æ¸…空之前的数据
  beforeModelValue.value = []
  afterModelValue.value = []
  issueModelValue.value = []
}
// å…³é—­å¼¹æ¡†
const cancel = () => {
  dialogVisitable.value = false
  emit('closeDia')
}
defineExpose({ openDialog })
</script>
<style scoped lang="scss">
.popup-content {
  width: 90vw;
  max-width: 400px;
  background-color: #fff;
  border-radius: 10px;
  overflow: hidden;
}
.popup-header {
  padding: 20px 20px 10px;
  text-align: center;
  border-bottom: 1px solid #f0f0f0;
}
.popup-title {
  font-size: 18px;
  font-weight: 600;
  color: #333;
}
.upload-container {
  padding: 20px;
  max-height: 60vh;
  overflow-y: auto;
}
.form-container {
  margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0;
  }
}
.title {
  font-size: 14px;
  color: #1890ff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0 10px;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 3px;
    width: 4px;
    height: 14px;
    background-color: #1890ff;
  }
}
.popup-footer {
  display: flex;
  justify-content: center;
  padding: 15px 20px;
  border-top: 1px solid #f0f0f0;
  background-color: #fafafa;
}
</style>
src/pages/inspectionUpload/components/qrCodeFormDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,254 @@
<template>
  <u-popup
    v-model="dialogVisitable"
    mode="center"
    :round="10"
    :closeable="true"
    @close="cancel"
  >
    <view class="popup-content">
      <view class="popup-header">
        <text class="popup-title">巡检</text>
      </view>
      <view class="form-container">
        <u-form :model="form" ref="formRef" :rules="rules">
          <u-form-item label="设备名称" prop="deviceName" labelWidth="80">
            <u-input
              v-model="form.deviceName"
              placeholder="请输入设备名称"
              :maxlength="30"
              :disabled="true"
            />
          </u-form-item>
          <u-form-item label="地点" prop="location" labelWidth="80">
            <u-input
              v-model="form.location"
              placeholder="请输入地点"
              :maxlength="30"
              :disabled="true"
            />
          </u-form-item>
          <u-form-item label="附件" prop="storageBlobDTO" labelWidth="80">
            <u-upload
              :fileList="form.storageBlobDTO"
              @afterRead="afterRead"
              @delete="deleteFile"
              name="files"
              multiple
              :maxCount="10"
              :maxSize="1024 * 1024"
              accept="video/*"
              :previewFullImage="true"
            ></u-upload>
          </u-form-item>
          <u-form-item label="巡检人" prop="scannerName" labelWidth="80">
            <u-input
              v-model="form.scannerName"
              :disabled="true"
              placeholder="请输入"
            />
          </u-form-item>
          <u-form-item label="巡检时间" prop="scanTime" labelWidth="80">
            <u-input
              v-model="form.scanTime"
              :disabled="true"
              placeholder="请输入"
            />
          </u-form-item>
        </u-form>
      </view>
      <view class="popup-footer">
        <u-button @click="cancel" :customStyle="{ marginRight: '10px' }">取消</u-button>
        <u-button type="primary" @click="submitForm">保存</u-button>
      </view>
    </view>
  </u-popup>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { addOrEditQrCodeRecord } from '@/api/inspectionUpload/index.js'
import useUserStore from '@/store/modules/user.ts'
const emit = defineEmits(['closeDia'])
const dialogVisitable = ref(false)
const formRef = ref(null)
const userStore = useUserStore()
const userInfo = ref({})
// èŽ·å–å½“å‰æ—¶é—´
function getCurrentDateTime() {
  const now = new Date()
  const year = now.getFullYear()
  const month = String(now.getMonth() + 1).padStart(2, '0')
  const day = String(now.getDate()).padStart(2, '0')
  const hours = String(now.getHours()).padStart(2, '0')
  const minutes = String(now.getMinutes()).padStart(2, '0')
  const seconds = String(now.getSeconds()).padStart(2, '0')
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// è¡¨å•数据
const form = reactive({
  deviceName: '',
  location: '',
  scannerName: '',
  scannerId: '',
  scanTime: '',
  storageBlobDTO: [],
  qrCode: {
    id: ''
  }
})
// è¡¨å•验证规则
const rules = reactive({
  deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }],
  location: [{ required: true, message: '请输入地点', trigger: 'blur' }]
})
// èŽ·å–ç”¨æˆ·ä¿¡æ¯
onMounted(async () => {
  try {
    const res = await userStore.getInfo()
    userInfo.value = res.user
    form.scannerName = userInfo.value.nickName
    form.scannerId = userInfo.value.userId
    form.scanTime = getCurrentDateTime()
  } catch (error) {
    console.error('获取用户信息失败:', error)
  }
})
// æ–‡ä»¶ä¸Šä¼ å¤„理
const afterRead = (event) => {
  const { file } = event
  // ä¸Šä¼ æ–‡ä»¶åˆ°æœåС噍
  uni.uploadFile({
    url: '/api/upload', // æ›¿æ¢ä¸ºå®žé™…的上传接口
    filePath: file.url,
    name: 'file',
    success: (res) => {
      const data = JSON.parse(res.data)
      if (data.code === 200) {
        const fileItem = {
          url: data.data.url,
          name: file.name,
          status: 'success'
        }
        form.storageBlobDTO.push(fileItem)
        uni.showToast({
          title: '上传成功',
          icon: 'success'
        })
      } else {
        uni.showToast({
          title: '上传失败',
          icon: 'error'
        })
      }
    },
    fail: () => {
      uni.showToast({
        title: '上传失败',
        icon: 'error'
      })
    }
  })
}
// åˆ é™¤æ–‡ä»¶
const deleteFile = (event) => {
  const { index } = event
  form.storageBlobDTO.splice(index, 1)
}
// æ‰“开弹框
const openDialog = async (row) => {
  dialogVisitable.value = true
  form.deviceName = row.deviceName || ''
  form.location = row.location || ''
  form.qrCodeId = row.qrCodeId || row.id || ''
  form.storageBlobDTO = []
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    // è¡¨å•验证
    const valid = await formRef.value.validate()
    if (!valid) return
    form.qrCode.id = form.qrCodeId
    await addOrEditQrCodeRecord({ ...form })
    uni.showToast({
      title: '提交成功',
      icon: 'success'
    })
    cancel()
  } catch (error) {
    console.error('提交失败:', error)
    uni.showToast({
      title: '提交失败',
      icon: 'error'
    })
  }
}
// å…³é—­å¼¹æ¡†
const cancel = () => {
  dialogVisitable.value = false
  emit('closeDia')
}
defineExpose({ openDialog })
</script>
<style scoped lang="scss">
.popup-content {
  width: 90vw;
  max-width: 400px;
  background-color: #fff;
  border-radius: 10px;
  overflow: hidden;
}
.popup-header {
  padding: 20px 20px 10px;
  text-align: center;
  border-bottom: 1px solid #f0f0f0;
}
.popup-title {
  font-size: 18px;
  font-weight: 600;
  color: #333;
}
.form-container {
  padding: 20px;
  max-height: 60vh;
  overflow-y: auto;
}
.popup-footer {
  display: flex;
  justify-content: center;
  padding: 15px 20px;
  border-top: 1px solid #f0f0f0;
  background-color: #fafafa;
}
</style>
src/pages/inspectionUpload/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,597 @@
<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>
</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'
// ç»„件引用
const formDia = ref()
const qrCodeFormDia = ref()
// å½“前标签
const activeTab = ref('task')
const tabName = ref('task')
const currentTabIndex = ref(0)
// æ ‡ç­¾é¡µæ•°æ®
const tabs = reactive([
  { name: '生产巡检' },
  { name: '现场巡检' }
])
// è¡¨æ ¼æ•°æ®
const tableData = ref([])
const tableLoading = ref(false)
const total = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
// æ‰«ç ç›¸å…³çŠ¶æ€
const isScanning = ref(false)
const scanLoading = ref(false)
const cameraError = ref('')
// è®¡ç®—属性
const scanButtonText = computed(() => {
  if (scanLoading.value) return '正在初始化...'
  return isScanning.value ? '停止扫码' : '开始扫码'
})
// ç”Ÿå‘½å‘¨æœŸ
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的扫码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 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'
    })
  }
}
const callBackendAPI = (result) => {
  nextTick(() => {
    qrCodeFormDia.value?.openDialog(result)
  })
}
// æ‰«ç å¤„理
const handleScanCode = (result) => {
  console.log('扫码结果:', result)
  handleScanSuccess(result)
}
// æ‘„像头错误处理
const handleCameraError = (error) => {
  console.error('摄像头错误:', error)
  cameraError.value = '摄像头访问失败,请检查权限设置'
}
</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);
  }
}
.task-header, .qr-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 10px;
}
.task-info, .qr-info {
  flex: 1;
}
.task-name, .device-name {
  font-size: 16px;
  font-weight: 600;
  color: #333;
  margin-bottom: 4px;
}
.task-location, .device-location {
  font-size: 14px;
  color: #666;
}
.task-actions, .qr-actions {
  margin-left: 10px;
}
.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;
    }
  }
}
.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;
}
.loading-text {
  margin-top: 10px;
  font-size: 14px;
  color: #666;
}
.pagination-container {
  padding: 20px 15px;
  background-color: #fff;
  margin-top: 10px;
}
</style>