gongchunyi
7 天以前 a7a9f53ef126659f664f5cbfe4eb10bebdfc4a6a
feat: 设备巡检修改
已修改4个文件
611 ■■■■■ 文件已修改
src/api/inspectionManagement/index.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/formDia.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue 431 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/inspectionManagement/index.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inspectionManagement/index.js
@@ -25,6 +25,15 @@
        data: query
    })
}
// 巡检验收
export function acceptInspectionTask(data) {
    return request({
        url: `/inspectionTask/accept`,
        method: 'post',
        data: data
    })
}
// 定时巡检任务表删除
export function delTimingTask(query) {
    return request({
src/views/equipmentManagement/inspectionManagement/components/formDia.vue
@@ -23,6 +23,13 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="巡检验收人" prop="inspectionAcceptor">
              <el-select v-model="form.inspectionAcceptor" placeholder="请选择" clearable>
                <el-option v-for="item in userList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col :span="12">
@@ -129,6 +136,8 @@
    taskName: undefined,
    inspector: '',
    inspectorIds: '',
    inspectionAcceptor: undefined,
    inspectionAcceptorId: undefined,
    remarks: '',
    frequencyType: '',
    frequencyDetail: '',
@@ -139,6 +148,7 @@
    rules: {
        taskId: [{ required: true, message: "请选择设备", trigger: "change" },],
        inspector: [{ required: true, message: "请输入巡检人", trigger: "blur" },],
        inspectionAcceptor: [{ required: true, message: "请选择巡检验收人", trigger: "change" },],
        dateStr: [{ required: true, message: "请选择登记时间", trigger: "change" }],
        frequencyType: [{ required: true, message: "请选择任务频率", trigger: "change" }],
        frequencyDetail: [
@@ -239,6 +249,10 @@
    } else {
      form.value.inspector = []
    }
    // 确保验收入ID和姓名正确回显
    if (form.value.inspectionAcceptorId) {
      form.value.inspectionAcceptor = form.value.inspectionAcceptorId
    }
    // 确保 isActive 有值,默认启用
    if (form.value.isActive === undefined || form.value.isActive === null) {
      form.value.isActive = 1
@@ -283,6 +297,8 @@
    taskName: undefined,
    inspector: '',
    inspectorIds: '',
    inspectionAcceptor: undefined,
    inspectionAcceptorId: undefined,
    remarks: '',
    frequencyType: '',
    frequencyDetail: '',
@@ -303,6 +319,15 @@
          delete form.value.inspector
        }
        
        // 处理验收入
        if (form.value.inspectionAcceptor) {
          const selectedUser = userList.value.find(u => u.userId === form.value.inspectionAcceptor)
          if (selectedUser) {
            form.value.inspectionAcceptorId = form.value.inspectionAcceptor
            form.value.inspectionAcceptor = selectedUser.nickName
          }
        }
        if (form.value.frequencyType === 'WEEKLY') {
          let frequencyDetail = ''
          frequencyDetail = form.value.week + ',' + form.value.time
src/views/equipmentManagement/inspectionManagement/components/viewFiles.vue
@@ -1,278 +1,241 @@
<template>
  <div>
    <el-dialog title="查看附件"
               v-model="dialogVisitable" width="800px" @close="cancel">
      <div class="upload-container">
        <!-- 生产前 -->
        <div class="form-container">
          <div class="title">生产前</div>
          <!-- 图片列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in beforeProductionImgs" :key="index"
                 @click="showMedia(beforeProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
    <el-dialog title="巡检详情"
               v-model="dialogVisitable" width="700px" @close="cancel">
      <div class="detail-container">
        <!-- 基本信息 -->
        <div class="info-section">
          <div class="section-title">基本信息</div>
          <el-descriptions :column="2" border>
            <el-descriptions-item label="设备名称">{{ rowData.taskName || '-' }}</el-descriptions-item>
            <el-descriptions-item label="巡检地点">{{ rowData.inspectionLocation || '-' }}</el-descriptions-item>
            <el-descriptions-item label="执行巡检人">{{ rowData.inspector || '-' }}</el-descriptions-item>
            <el-descriptions-item label="巡检时间">{{ rowData.dateStr || '-' }}</el-descriptions-item>
            <el-descriptions-item label="巡检状态">
              <el-tag v-if="rowData.inspectionStatus === 1" type="success" size="small">正常</el-tag>
              <el-tag v-else-if="rowData.inspectionStatus === 2" type="danger" size="small">异常</el-tag>
              <el-tag v-else size="small">未巡检</el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="登记人">{{ rowData.registrant || '-' }}</el-descriptions-item>
            <el-descriptions-item label="验收状态">
              <el-tag v-if="rowData.acceptStatus === 1" type="success" size="small">已通过</el-tag>
              <el-tag v-else-if="rowData.acceptStatus === 2" type="danger" size="small">已退回</el-tag>
              <el-tag v-else-if="rowData.inspectionStatus > 0" type="warning" size="small">待验收</el-tag>
              <span v-else>--</span>
            </el-descriptions-item>
            <el-descriptions-item label="验收人">{{ rowData.inspectionAcceptor || '-' }}</el-descriptions-item>
          </el-descriptions>
          </div>
          
          <!-- 视频列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in beforeProductionVideos"
                :key="index"
                @click="showMedia(beforeProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
        <!-- 异常描述 -->
        <div v-if="rowData.inspectionStatus === 2" class="info-section">
          <div class="section-title">异常描述</div>
          <div class="exception-content">
            {{ rowData.inspectionRemark || '无' }}
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
        </div>
        <!-- 附件列表 -->
        <div class="info-section">
          <div class="section-title">附件 ({{ attachmentList.length }}个)</div>
          <div v-if="attachmentList.length > 0" class="attachment-list">
            <div v-for="(file, index) in attachmentList" :key="index" class="attachment-item">
              <div class="attachment-preview" @click="previewFile(file, index)">
                <img v-if="isImage(file)" :src="getFileUrl(file)" alt="附件" />
                <video v-else-if="isVideo(file)" :src="getFileUrl(file)"></video>
                <div v-else class="file-icon">
                  <i class="el-icon-document"></i>
                </div>
              </div>
              <div class="attachment-info">
                <span class="file-name">{{ file.originalFilename || file.name || '附件' }}</span>
              </div>
            </div>
          </div>
          <div v-else class="empty-attachment">
            暂无附件
            </div>
          </div>
        </div>
        
        <!-- 生产后 -->
        <div class="form-container">
          <div class="title">生产后</div>
          <!-- 图片列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in afterProductionImgs" :key="index"
                 @click="showMedia(afterProductionImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- 视频列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in afterProductionVideos"
                :key="index"
                @click="showMedia(afterProductionVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
        <!-- 生产问题 -->
        <div class="form-container">
          <div class="title">生产问题</div>
          <!-- 图片列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <img v-for="(item, index) in productionIssuesImgs" :key="index"
                 @click="showMedia(productionIssuesImgs, index, 'image')"
                 :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt="">
          </div>
          <!-- 视频列表 -->
          <div style="display: flex; flex-wrap: wrap;">
            <div
                v-for="(videoUrl, index) in productionIssuesVideos"
                :key="index"
                @click="showMedia(productionIssuesVideos, index, 'video')"
                style="position: relative; margin: 10px; cursor: pointer;"
            >
              <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;">
                <img src="@/assets/images/video.png" alt="播放" style="width: 30px; height: 30px; opacity: 0.8;" />
              </div>
              <div style="text-align: center; font-size: 12px; color: #666;">点击播放</div>
            </div>
          </div>
        </div>
      </div>
      <template #footer>
        <el-button @click="cancel">关闭</el-button>
      </template>
    </el-dialog>
    
    <!-- 统一媒体查看器 -->
    <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer">
      <div class="media-viewer-content" @click.stop>
        <!-- 图片 -->
    <!-- 图片预览 -->
        <vue-easy-lightbox
            v-if="mediaType === 'image'"
            :visible="isMediaViewerVisible"
            :imgs="mediaList"
            :index="currentMediaIndex"
            @hide="closeMediaViewer"
        v-if="showLightbox"
        :visible="showLightbox"
        :imgs="previewImages"
        :index="previewIndex"
        @hide="closeLightbox"
        ></vue-easy-lightbox>
        
        <!-- 视频 -->
        <div v-else-if="mediaType === 'video'" style="position: relative;">
    <!-- 视频预览 -->
    <el-dialog v-if="showVideoDialog" v-model="showVideoDialog" title="视频预览" width="800px" append-to-body>
          <video
              :src="mediaList[currentMediaIndex]"
              autoplay
          ref="videoPlayer"
          :src="videoUrl"
              controls
              style="max-width: 90vw; max-height: 80vh;"
          />
        </div>
      </div>
    </div>
          autoplay
          style="width: 100%; max-height: 60vh;"
      ></video>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref } from 'vue';
import { ref, getCurrentInstance } from 'vue';
import VueEasyLightbox from 'vue-easy-lightbox';
const { proxy } = getCurrentInstance();
// 控制弹窗显示
const dialogVisitable = ref(false);
// 图片数组
const beforeProductionImgs = ref([]);
const afterProductionImgs = ref([]);
const productionIssuesImgs = ref([]);
// 行数据
const rowData = ref({});
// 视频数组
const beforeProductionVideos = ref([]);
const afterProductionVideos = ref([]);
const productionIssuesVideos = ref([]);
// 附件列表
const attachmentList = ref([]);
// 媒体查看器状态
const isMediaViewerVisible = ref(false);
const currentMediaIndex = ref(0);
const mediaList = ref([]); // 存储当前要查看的媒体列表(含图片和视频对象)
const mediaType = ref('image'); // image | video
// 图片预览
const showLightbox = ref(false);
const previewImages = ref([]);
const previewIndex = ref(0);
// 视频预览
const showVideoDialog = ref(false);
const videoUrl = ref('');
const javaApi = proxy.javaApi;
// 处理 URL:将 Windows 路径转换为可访问的 URL
// 处理文件URL
function processFileUrl(fileUrl) {
  if (!fileUrl) return '';
  
  // 如果 URL 是 Windows 路径格式(包含反斜杠),需要转换
  if (fileUrl && fileUrl.indexOf('\\') > -1) {
    // 查找 uploads 关键字的位置,从那里开始提取相对路径
    const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads');
    if (uploadsIndex > -1) {
      // 从 uploads 开始提取路径,并将反斜杠替换为正斜杠
      const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/');
      fileUrl = '/' + relativePath;
    } else {
      // 如果没有找到 uploads,提取最后一个目录和文件名
      const parts = fileUrl.split('\\');
      const fileName = parts[parts.length - 1];
      fileUrl = '/uploads/' + fileName;
    }
  }
  
  // 确保所有非 http 开头的 URL 都拼接 baseUrl
  if (fileUrl && !fileUrl.startsWith('http')) {
    // 确保路径以 / 开头
    if (!fileUrl.startsWith('/')) {
      fileUrl = '/' + fileUrl;
    }
    // 拼接 baseUrl
    fileUrl = javaApi + fileUrl;
  }
  
  return fileUrl;
}
// 处理每一类数据:分离图片和视频
function processItems(items) {
  const images = [];
  const videos = [];
  // 检查 items 是否存在且为数组
  if (!items || !Array.isArray(items)) {
    return { images, videos };
// 获取文件访问URL
function getFileUrl(file) {
  // 优先使用 link 字段
  let url = file?.link;
  if (!url) {
    url = file?.url || file?.downloadUrl || file?.tempPath || '';
  }
  return processFileUrl(url);
  }
  
  items.forEach(item => {
    if (!item || !item.link) return;
    const fileUrl = processFileUrl(item.link);
    // if (!item || !item.url) return;
    // // 处理文件 URL
    // const fileUrl = processFileUrl(item.url);
    // 根据文件扩展名判断是图片还是视频
    const urlLower = fileUrl.toLowerCase();
    if (urlLower.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/)) {
      images.push(fileUrl);
    } else if (urlLower.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/)) {
      videos.push(fileUrl);
    } else if (item.contentType) {
      // 如果有 contentType,使用 contentType 判断
      if (item.contentType.startsWith('image/')) {
        images.push(fileUrl);
      } else if (item.contentType.startsWith('video/')) {
        videos.push(fileUrl);
      }
    }
  });
  return { images, videos };
// 判断是否为图片
function isImage(file) {
  const name = file?.originalFilename || file?.bucketFilename || file?.name || '';
  const ext = name.split('.').pop()?.toLowerCase();
  return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext);
}
// 打开弹窗并加载数据
const openDialog = async (row) => {
  // 使用正确的字段名:commonFileListBefore, commonFileListAfter
  // productionIssues 可能不存在,使用空数组
  const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBefore || []);
  const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfter || []);
  const { images: issueImgs, videos: issueVids } = processItems(row.productionIssues || []);
// 判断是否为视频
function isVideo(file) {
  const name = file?.originalFilename || file?.bucketFilename || file?.name || '';
  const ext = name.split('.').pop()?.toLowerCase();
  return ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm'].includes(ext);
}
  
  beforeProductionImgs.value = beforeImgs;
  beforeProductionVideos.value = beforeVids;
// 预览文件
function previewFile(file, index) {
  if (isImage(file)) {
    // 图片预览
    previewImages.value = attachmentList.value
      .filter(f => isImage(f))
      .map(f => getFileUrl(f));
    previewIndex.value = previewImages.value.indexOf(getFileUrl(file));
    showLightbox.value = true;
  } else if (isVideo(file)) {
    // 视频预览
    videoUrl.value = getFileUrl(file);
    showVideoDialog.value = true;
  }
}
  
  afterProductionImgs.value = afterImgs;
  afterProductionVideos.value = afterVids;
// 关闭图片预览
function closeLightbox() {
  showLightbox.value = false;
  previewImages.value = [];
  previewIndex.value = 0;
}
  
  productionIssuesImgs.value = issueImgs;
  productionIssuesVideos.value = issueVids;
// 打开弹窗
const openDialog = (row) => {
  rowData.value = { ...row };
  // 收集所有附件
  let files = [];
  if (row?.commonFileList) {
    files = files.concat(row.commonFileList);
  }
  if (row?.commonFileListBefore) {
    files = files.concat(row.commonFileListBefore);
  }
  if (row?.commonFileListAfter) {
    files = files.concat(row.commonFileListAfter);
  }
  attachmentList.value = files;
  
  dialogVisitable.value = true;
};
// 显示媒体(图片 or 视频)
function showMedia(mediaArray, index, type) {
  mediaList.value = mediaArray;
  currentMediaIndex.value = index;
  mediaType.value = type;
  isMediaViewerVisible.value = true;
}
// 关闭媒体查看器
function closeMediaViewer() {
  isMediaViewerVisible.value = false;
  mediaList.value = [];
  mediaType.value = 'image';
}
// 表单关闭方法
// 关闭弹窗
const cancel = () => {
  dialogVisitable.value = false;
  showVideoDialog.value = false;
  rowData.value = {};
  attachmentList.value = [];
};
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.upload-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
  border: 1px solid #dcdfe6;
  box-sizing: border-box;
.detail-container {
  max-height: 60vh;
  overflow-y: auto;
}
  
  .form-container {
    flex: 1;
    width: 100%;
.info-section {
    margin-bottom: 20px;
  &:last-child {
    margin-bottom: 0;
  }
}
.title {
.section-title {
  font-size: 14px;
  color: #165dff;
  line-height: 20px;
  font-weight: 600;
  padding-left: 10px;
  position: relative;
  margin: 6px 0;
  margin-bottom: 12px;
  
  &::before {
    content: "";
@@ -285,23 +248,81 @@
  }
}
.media-viewer-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.8);
  z-index: 9999;
.exception-content {
  padding: 12px;
  background-color: #fff2f0;
  border: 1px solid #ffccc7;
  border-radius: 4px;
  color: #ff4d4f;
  font-size: 14px;
  line-height: 1.6;
}
.attachment-list {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.attachment-item {
  width: 100px;
  text-align: center;
}
.attachment-preview {
  width: 100px;
  height: 100px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f5f7fa;
  transition: all 0.3s;
  &:hover {
    border-color: #409eff;
    transform: scale(1.02);
}
.media-viewer-content {
  position: relative;
  max-width: 90vw;
  max-height: 90vh;
  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  video {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .file-icon {
    font-size: 32px;
    color: #909399;
  }
}
.attachment-info {
  margin-top: 4px;
  .file-name {
    font-size: 12px;
    color: #606266;
    display: block;
  overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}
.empty-attachment {
  padding: 30px;
  text-align: center;
  color: #909399;
  background-color: #f5f7fa;
  border-radius: 4px;
}
</style>
src/views/equipmentManagement/inspectionManagement/index.vue
@@ -1,37 +1,23 @@
<template>
  <div class="app-container">
    <el-form :inline="true"
             :model="queryParams"
             class="search-form">
    <el-form :inline="true" :model="queryParams" class="search-form">
      <el-form-item label="巡检任务名称">
        <el-input v-model="queryParams.taskName"
                  placeholder="请输入巡检任务名称"
                  clearable
                  style="width: 200px " />
        <el-input v-model="queryParams.taskName" placeholder="请输入巡检任务名称" clearable style="width: 200px " />
      </el-form-item>
      <el-form-item>
        <el-button type="primary"
                   @click="handleQuery">查询</el-button>
        <el-button type="primary" @click="handleQuery">查询</el-button>
        <el-button @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-card>
      <div style="display: flex;flex-direction: row;justify-content: space-between;margin-bottom: 10px;">
        <el-radio-group v-model="activeRadio"
                        @change="radioChange">
          <el-radio-button v-for="tab in radios"
                           :key="tab.name"
                           :label="tab.label"
                           :value="tab.name" />
        <el-radio-group v-model="activeRadio" @change="radioChange">
          <el-radio-button v-for="tab in radios" :key="tab.name" :label="tab.label" :value="tab.name" />
        </el-radio-group>
        <!-- 操作按钮区 -->
        <el-space v-if="activeRadio !== 'task'">
          <el-button type="primary"
                     :icon="Plus"
                     @click="handleAdd(undefined)">新建</el-button>
          <el-button type="danger"
                     :icon="Delete"
                     @click="handleDelete">删除</el-button>
          <el-button type="primary" :icon="Plus" @click="handleAdd(undefined)">新建</el-button>
          <el-button type="danger" :icon="Delete" @click="handleDelete">删除</el-button>
          <el-button @click="handleOut">导出</el-button>
        </el-space>
        <el-space v-else>
@@ -39,35 +25,33 @@
        </el-space>
      </div>
      <div>
        <PIMTable :table-loading="tableLoading"
                  :table-data="tableData"
                  :column="tableColumns"
                  @selection-change="handleSelectionChange"
                  @pagination="handlePagination"
                  :is-selection="true"
                  :border="true"
        <PIMTable :table-loading="tableLoading" :table-data="tableData" :column="tableColumns"
          @selection-change="handleSelectionChange" @pagination="handlePagination" :is-selection="true" :border="true"
                  :page="{
                  current: pageNum,
                  size: pageSize,
                  total: total,
                  layout: 'total, sizes, prev, pager, next, jumper'
                }"
                  :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }">
          }" :table-style="{ width: '100%', height: 'calc(100vh - 23em)' }">
          <template #inspector="{ row }">
            <div class="person-tags">
              <template v-if="row.inspector && row.inspector.length > 0">
                <el-tag v-for="(person, index) in row.inspector"
                        :key="index"
                        size="small"
                        type="primary"
                <el-tag v-for="(person, index) in row.inspector" :key="index" size="small" type="primary"
                        class="person-tag">
                  {{ person }}
                </el-tag>
              </template>
              <span v-else
                    class="no-data">--</span>
              <span v-else class="no-data">--</span>
            </div>
          </template>
          <template #acceptStatusSlot="{ row }">
            <el-tag v-if="row.acceptStatus === 1" type="success">已通过</el-tag>
            <el-tag v-else-if="row.acceptStatus === 2" type="danger">已退回</el-tag>
            <el-tag v-else-if="row.inspectionStatus > 0" type="warning">待验收</el-tag>
            <span v-else>--</span>
          </template>
          <template #statusRef="{ row }">
            <el-tag v-if="row.isActive === true || row.isActive === 1" type="success">启用</el-tag>
            <el-tag v-else type="danger">停用</el-tag>
@@ -75,8 +59,7 @@
        </PIMTable>
      </div>
    </el-card>
    <form-dia ref="formDia"
              @closeDia="handleQuery"></form-dia>
    <form-dia ref="formDia" @closeDia="handleQuery"></form-dia>
    <view-files ref="viewFiles"></view-files>
  </div>
</template>
@@ -98,6 +81,7 @@
    inspectionTaskList,
    timingTaskList,
    addOrEditTimingTask,
  acceptInspectionTask,
  } from "@/api/inspectionManagement/index.js";
  // 全局变量
@@ -132,10 +116,11 @@
    { prop: "taskName", label: "巡检任务名称", minWidth: 160 },
    { prop: "remarks", label: "备注", minWidth: 150 },
    { prop: "inspector", label: "执行巡检人", minWidth: 150, slot: "inspector" },
  { prop: "inspectionAcceptor", label: "验收人", minWidth: 100 },
    {
      prop: "frequencyType",
      label: "频次",
      minWidth: 150,
    minWidth: 100,
      formatData: params => {
        return params === "DAILY"
          ? "每日"
@@ -150,10 +135,9 @@
    },
    {
      prop: "frequencyDetail",
      label: "开始日期与时间",
      minWidth: 150,
    label: "开始时间",
    minWidth: 120,
      formatter: (row, column, cellValue) => {
        // 先判断是否是字符串
        if (typeof cellValue !== "string") return "";
        let val = cellValue;
        const replacements = {
@@ -165,7 +149,6 @@
          SAT: "周六",
          SUN: "周日",
        };
        // 使用正则一次性替换所有匹配项
        return val.replace(
          /MON|TUE|WED|THU|FRI|SAT|SUN/g,
          match => replacements[match]
@@ -173,8 +156,37 @@
      },
    },
    { prop: "registrant", label: "登记人", minWidth: 100 },
    { prop: "createTime", label: "登记日期", minWidth: 120, formatData: (cell) => cell ? dayjs(cell).format("YYYY-MM-DD HH:MM:ss") : "-" },
  { prop: "createTime", label: "登记日期", minWidth: 120, formatData: (cell) => cell ? dayjs(cell).format("YYYY-MM-DD") : "-" },
  ]);
const inspectionStatusColumn = {
  prop: "inspectionStatus",
  label: "巡检状态",
  minWidth: 100,
  dataType: "tag",
  formatData: params => {
    if (params === 0) return "未巡检";
    if (params === 1) return "正常";
    if (params === 2) return "异常";
    return "--";
  },
  formatType: params => {
    if (params === 0) return "warning";
    if (params === 1) return "success";
    if (params === 2) return "danger";
    return "info";
  }
};
const inspectionAcceptorColumn = { prop: "inspectionAcceptor", label: "验收人", minWidth: 100 };
const acceptStatusColumn = {
  prop: "acceptStatus",
  label: "验收状态",
  minWidth: 100,
  dataType: "slot",
  slot: "acceptStatusSlot",
};
  const statusColumn = {
    prop: "isActive",
@@ -200,9 +212,16 @@
            };
          case "viewFile":
            return {
              name: "查看附件",
            name: "巡检详情",
              clickFun: viewFile,
              color: "#67C23A",
          };
        case "accept":
            return {
              name: "验收",
              clickFun: handleAccept,
              color: "#E6A23C",
              showHide: (row) => row.inspectionStatus > 0 && (!row.acceptStatus || row.acceptStatus === 0),
            };
          case "toggleActive":
            return {
@@ -233,6 +252,7 @@
  // 单选变化
  const radioChange = value => {
  activeRadio.value = value;
    if (value === "taskManage") {
      const operationColumn = getOperationColumn(["edit", "toggleActive"]);
      tableColumns.value = [
@@ -242,12 +262,14 @@
      ];
      operationsArr.value = ["edit", "toggleActive"];
    } else if (value === "task") {
      const operationColumn = getOperationColumn(["viewFile"]);
    const operationColumn = getOperationColumn(["viewFile", "accept"]);
      tableColumns.value = [
        ...columns.value,
      inspectionStatusColumn,
      acceptStatusColumn,
        ...(operationColumn ? [operationColumn] : []),
      ];
      operationsArr.value = ["viewFile"];
    operationsArr.value = ["viewFile", "accept"];
    }
    pageNum.value = 1;
    pageSize.value = 10;
@@ -334,13 +356,41 @@
    });
  };
  // 查看附件
// 巡检详情
  const viewFile = row => {
    nextTick(() => {
      viewFiles.value?.openDialog(row);
    });
  };
  // 验收操作
  const handleAccept = (row) => {
    ElMessageBox.confirm(`请选择对任务【${row.taskName}】的验收结果`, "系统提示", {
      distinguishCancelAndClose: true,
      confirmButtonText: '验收通过',
      cancelButtonText: '退回重检',
      type: 'warning'
    }).then(async () => {
      try {
        await acceptInspectionTask({ id: row.id, acceptStatus: 1 });
        proxy.$modal.msgSuccess("已验收通过");
        handleQuery();
      } catch (error) {
        console.error("验收失败:", error);
      }
    }).catch(async (action) => {
      if (action === 'cancel') {
        try {
          await acceptInspectionTask({ id: row.id, acceptStatus: 2 });
          proxy.$modal.msgWarning("已退回,等待重新巡检");
          handleQuery();
        } catch (error) {
          console.error("退回失败:", error);
        }
      }
    });
  };
  // 删除操作
  const handleDelete = () => {
    if (!selectedRows.value.length) {