三拍:巡检excle插入

Inspect.vue

<script>
export default {
  name: "Inspect"
}
</script>

<template>
  <div class="inspect-container">
    <el-card class="inspect-card">
      <template #header>
        <div class="card-header">
          <div class="header-left">
            <span>巡检计划管理</span>
          </div>
          <div class="header-right">
            <el-button type="success" @click="exportToExcel" style="margin-right: 10px">导出Excel</el-button>
            <el-button type="warning" @click="downloadTemplate" style="margin-right: 10px">下载导入模板</el-button>
            <el-upload
              class="import-upload"
              action=""
              :show-file-list="false"
              :auto-upload="false"
              accept=".xlsx,.xls"
              :on-change="handleImportExcel"
            >
              <el-button type="info" style="margin-right: 10px">批量导入</el-button>
            </el-upload>
            <el-button type="primary" @click="showAddDialog">新增计划</el-button>
          </div>
        </div>
      </template>

      <!-- 批量操作按钮 -->
      <div class="batch-actions" style="margin-bottom: 15px">
        <el-button 
          type="success" 
          :disabled="!selectedPlans.length" 
          @click="handleBatchStatus('启用')"
        >批量启用</el-button>
        <el-button 
          type="warning" 
          :disabled="!selectedPlans.length" 
          @click="handleBatchStatus('停用')"
        >批量停用</el-button>
      </div>

      <!-- 修改表格,添加多选功能 -->
      <el-table 
        :data="planList" 
        border 
        style="width: 100%" 
        height="450"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="55" />
        <!-- 其他列保持不变 -->
        <el-table-column prop="planId" label="计划编号" width="100" />
        <el-table-column prop="deviceId" label="设备编号" width="120" />
        <el-table-column prop="location" label="巡检位置" min-width="150" />
        <el-table-column prop="frequency" label="巡检频率" width="100" />
        <el-table-column prop="startTime" label="开始时间" width="160">
          <template #default="scope">
            {{ formatDateTime(scope.row.startTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="endTime" label="结束时间" width="160">
          <template #default="scope">
            {{ formatDateTime(scope.row.endTime) }}
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">{{ scope.row.status }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
          <template #default="scope">
            <el-button 
              size="small" 
              @click="handleEdit(scope.row)"
              :disabled="scope.row.status === '启用'"
            >编辑</el-button>
            <el-button 
              size="small" 
              type="danger" 
              @click="handleDelete(scope.row)"
              :disabled="scope.row.status === '启用'"
            >删除</el-button>
          </template>
        </el-table-column>

        <!-- 在表单中修改状态选择器 -->
        <el-form-item label="状态">
          <el-select 
            v-model="planForm.status" 
            style="width: 100%"
            :disabled="true"
          >
            <el-option label="待启用" value="待启用" />
            <el-option label="启用" value="启用" />
            <el-option label="停用" value="停用" />
          </el-select>
        </el-form-item>
      </el-table>
    </el-card>

    <!-- 添加/编辑对话框 -->
    <el-dialog
      :title="dialogTitle"
      v-model="dialogVisible"
      width="500px"
    >
      <el-form :model="planForm" label-width="100px">
        <el-form-item label="设备编号">
          <el-select 
            v-model="planForm.deviceId" 
            style="width: 100%"
            filterable
            placeholder="请选择设备"
            @change="handleDeviceChange"
          >
            <el-option 
              v-for="item in deviceOptions" 
              :key="item.value" 
              :label="item.label" 
              :value="item.value" 
            />
          </el-select>
        </el-form-item>
        <el-form-item label="巡检位置">
          <el-input v-model="planForm.location" />
        </el-form-item>
        <el-form-item label="巡检频率">
          <el-select v-model="planForm.frequency" style="width: 100%">
            <el-option label="日" value="日" />
            <el-option label="周" value="周" />
            <el-option label="月" value="月" />
            <el-option label="季度" value="季度" />
            <el-option label="半年" value="半年" />
            <el-option label="年" value="年" />
          </el-select>
        </el-form-item>
        <el-form-item label="开始时间">
          <el-date-picker
            v-model="planForm.startTime"
            type="datetime"
            style="width: 100%"
          />
        </el-form-item>
        <el-form-item label="结束时间">
          <el-date-picker
            v-model="planForm.endTime"
            type="datetime"
            style="width: 100%"
          />
        </el-form-item>
        
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="handleSubmit">确定</el-button>
        </span>
      </template>
    </el-dialog>
    
    <!-- 添加导入预览对话框 -->
    <el-dialog
      title="Excel导入预览"
      v-model="importDialogVisible"
      width="80%"
    >
      <div v-if="importData.length > 0">
        <p>共检测到 {{ importData.length }} 条数据,请确认后导入:</p>
        <el-table :data="importData" border style="width: 100%" height="400">
          <el-table-column prop="deviceId" label="设备编号" width="120" />
          <el-table-column prop="location" label="巡检位置" min-width="150" />
          <el-table-column prop="frequency" label="巡检频率" width="100" />
          <el-table-column prop="startTime" label="开始时间" width="160" />
          <el-table-column prop="endTime" label="结束时间" width="160" />
          <el-table-column prop="status" label="状态" width="100">
            <template #default="scope">
              <el-tag :type="getStatusType(scope.row.status)">{{ scope.row.status }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="validationMsg" label="验证信息" min-width="150">
            <template #default="scope">
              <span v-if="scope.row.validationMsg" style="color: red">{{ scope.row.validationMsg }}</span>
              <span v-else style="color: green">数据有效</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <div v-else>
        <p>未检测到有效数据,请检查Excel文件格式是否正确</p>
      </div>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="importDialogVisible = false">取消</el-button>
          <el-button type="primary" @click="confirmImport" :disabled="!hasValidData">确认导入</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getAllPlans, addPlan, updatePlan, deletePlan, updatePlanStatus } from '@/api/inspection'
import { getDeviceList } from '@/api/device'
import { generateOrders } from '@/api/inspectionOrder'
// 导入Excel相关库
import ExcelJS from 'exceljs'
import FileSaver from 'file-saver'

const planList = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('')
const isEdit = ref(false)

const planForm = ref({
  deviceId: '',
  location: '',
  frequency: '日',
  startTime: '',
  endTime: '',
  status: '待启用'
})

const deviceOptions = ref([])

// 添加获取设备列表的方法
const fetchDeviceList = async () => {
  try {
    const res = await getDeviceList()
    if (res.code === 200) {
      deviceOptions.value = res.data.map(device => ({
        value: device.deviceId,
        label: `${device.deviceId} - ${device.deviceType}`,
        location: device.location
      }))
    }
  } catch (error) {
    console.error('获取设备列表失败:', error)
  }
}

// 在 onMounted 中添加
onMounted(() => {
  fetchPlanList()
  fetchDeviceList()  // 添加这行
})

const fetchPlanList = async () => {
  try {
    const res = await getAllPlans()
    if (res.code === 200) {
      planList.value = res.data
    } else {
      ElMessage.error(res.msg || '获取计划列表失败')
    }
  } catch (error) {
    console.error('获取计划列表失败:', error)
    ElMessage.error('获取计划列表失败')
  }
}

// 格式化日期时间
const formatDateTime = (datetime) => {
  if (!datetime) return ''
  return new Date(datetime).toLocaleString('zh-CN')
}

// 获取状态标签类型
const getStatusType = (status) => {
  const statusMap = {
    '待启用': 'info',
    '启用': 'success',
    '停用': 'danger'
  }
  return statusMap[status] || ''
}

// 显示添加对话框
const showAddDialog = () => {
  isEdit.value = false
  dialogTitle.value = '新增巡检计划'
  planForm.value = {
    deviceId: '',
    location: '',
    frequency: '日',
    startTime: '',
    endTime: '',
    status: '待启用'
  }
  dialogVisible.value = true
}

// 显示编辑对话框
const handleEdit = (row) => {
  isEdit.value = true
  dialogTitle.value = '编辑巡检计划'
  planForm.value = { ...row }
  dialogVisible.value = true
}

// 处理删除
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除该巡检计划吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(async () => {
    try {
      const res = await deletePlan(row.planId)
      if (res.code === 200) {
        ElMessage.success('删除成功')
        fetchPlanList()
      } else {
        ElMessage.error(res.msg || '删除失败')
      }
    } catch (error) {
      console.error('删除失败:', error)
      ElMessage.error('删除失败')
    }
  })
}

// 提交表单
const handleSubmit = async () => {
  try {
    const api = isEdit.value ? updatePlan : addPlan
    const res = await api(planForm.value)
    if (res.code === 200) {
      ElMessage.success(isEdit.value ? '更新成功' : '添加成功')
      dialogVisible.value = false
      fetchPlanList()
    } else {
      ElMessage.error(res.msg || (isEdit.value ? '更新失败' : '添加失败'))
    }
  } catch (error) {
    console.error('操作失败:', error)
    ElMessage.error(isEdit.value ? '更新失败' : '添加失败')
  }
}

// 添加选中计划的数组
const selectedPlans = ref([])

// 处理表格选择变化
const handleSelectionChange = (selection) => {
  selectedPlans.value = selection
}

// 修改批量状态更新方法
const handleBatchStatus = async (status) => {
  try {
    const promises = selectedPlans.value.map(plan => 
      updatePlanStatus(plan.planId, status)
    )
    
    const results = await Promise.all(promises)
    const allSuccess = results.every(res => res.code === 200)
    
    if (allSuccess) {
      if (status === '启用') {
        // 获取所有选中计划的ID
        const planIds = selectedPlans.value.map(plan => plan.planId)
        // 调用生成工单的接口
        const generateResult = await generateOrders(planIds)
        if (generateResult.code === 200) {
          ElMessage.success('批量启用成功,已生成相应工单')
        } else {
          ElMessage.warning('计划已启用,但工单生成失败')
        }
      } else {
        ElMessage.success(`批量${status}成功`)
      }
      fetchPlanList()
    } else {
      ElMessage.error(`部分计划${status}失败`)
    }
  } catch (error) {
    console.error('批量操作失败:', error)
    ElMessage.error('批量操作失败')
  }
}
// 处理设备选择变化
const handleDeviceChange = (deviceId) => {
  const selectedDevice = deviceOptions.value.find(device => device.value === deviceId)
  if (selectedDevice) {
    planForm.value.location = selectedDevice.location
  }
}

// 导出Excel方法
const exportToExcel = async () => {
  try {
    // 创建工作簿
    const workbook = new ExcelJS.Workbook()
    workbook.creator = 'SanPai系统'
    workbook.created = new Date()
    
    // 创建工作表
    const worksheet = workbook.addWorksheet('巡检计划')
    
    // 设置列
    worksheet.columns = [
      { header: '计划编号', key: 'planId', width: 15 },
      { header: '设备编号', key: 'deviceId', width: 15 },
      { header: '巡检位置', key: 'location', width: 20 },
      { header: '巡检频率', key: 'frequency', width: 15 },
      { header: '开始时间', key: 'startTime', width: 20 },
      { header: '结束时间', key: 'endTime', width: 20 },
      { header: '状态', key: 'status', width: 15 }
    ]
    
    // 设置表头样式
    worksheet.getRow(1).font = { bold: true }
    worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' }
    
    // 添加数据
    const data = planList.value.map(plan => {
      return {
        planId: plan.planId,
        deviceId: plan.deviceId,
        location: plan.location,
        frequency: plan.frequency,
        startTime: formatDateTime(plan.startTime),
        endTime: formatDateTime(plan.endTime),
        status: plan.status
      }
    })
    
    // 添加数据行
    worksheet.addRows(data)
    
    // 设置每个单元格居中
    for (let i = 2; i <= data.length + 1; i++) {
      worksheet.getRow(i).alignment = { vertical: 'middle', horizontal: 'center' }
    }
    
    // 生成Excel文件并下载
    const buffer = await workbook.xlsx.writeBuffer()
    const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
    FileSaver.saveAs(blob, `巡检计划_${new Date().toLocaleDateString()}.xlsx`)
    
    ElMessage.success('导出成功')
  } catch (error) {
    console.error('导出Excel失败:', error)
    ElMessage.error('导出Excel失败')
  }
}

// 导入相关变量
const importDialogVisible = ref(false)
const importData = ref([])
const hasValidData = computed(() => {
  return importData.value.some(item => !item.validationMsg)
})

// 下载导入模板
const downloadTemplate = async () => {
  try {
    // 创建工作簿
    const workbook = new ExcelJS.Workbook()
    workbook.creator = 'SanPai系统'
    workbook.created = new Date()
    
    // 创建工作表
    const worksheet = workbook.addWorksheet('巡检计划导入模板')
    
    // 设置列
    worksheet.columns = [
      { header: '设备编号', key: 'deviceId', width: 15 },
      { header: '巡检位置', key: 'location', width: 20 },
      { header: '巡检频率', key: 'frequency', width: 15 },
      { header: '开始时间', key: 'startTime', width: 20 },
      { header: '结束时间', key: 'endTime', width: 20 },
      { header: '状态', key: 'status', width: 15 }
    ]
    
    // 设置表头样式
    worksheet.getRow(1).font = { bold: true }
    worksheet.getRow(1).alignment = { vertical: 'middle', horizontal: 'center' }
    
    // 添加示例数据
    worksheet.addRow({
      deviceId: 'D001',
      location: '设备A区域',
      frequency: '日',
      startTime: '2023-01-01 08:00:00',
      endTime: '2023-12-31 18:00:00',
      status: '待启用'
    })
    
    // 添加说明行
    worksheet.addRow({
      deviceId: '必填,请输入有效的设备编号',
      location: '必填,巡检位置',
      frequency: '必填,可选值:日、周、月、季度、半年、年',
      startTime: '必填,格式:YYYY-MM-DD HH:MM:SS',
      endTime: '必填,格式:YYYY-MM-DD HH:MM:SS',
      status: '可选值:待启用、启用、停用,默认为待启用'
    })
    
    // 设置说明行样式
    worksheet.getRow(3).font = { italic: true, color: { argb: 'FF0000FF' } }
    
    // 生成Excel文件并下载
    const buffer = await workbook.xlsx.writeBuffer()
    const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
    FileSaver.saveAs(blob, `巡检计划导入模板_${new Date().toLocaleDateString()}.xlsx`)
    
    ElMessage.success('模板下载成功')
  } catch (error) {
    console.error('模板下载失败:', error)
    ElMessage.error('模板下载失败')
  }
}

// 处理Excel导入
const handleImportExcel = async (file) => {
  if (!file) return
  
  // 检查文件类型
  const fileType = file.name.split('.').pop().toLowerCase()
  if (fileType !== 'xlsx' && fileType !== 'xls') {
    ElMessage.error('请上传Excel文件(.xlsx或.xls)')
    return
  }
  
  try {
    // 读取文件
    const reader = new FileReader()
    reader.onload = async (e) => {
      try {
        const data = e.target.result
        const workbook = new ExcelJS.Workbook()
        await workbook.xlsx.load(data)
        
        // 获取第一个工作表
        const worksheet = workbook.getWorksheet(1)
        if (!worksheet) {
          ElMessage.error('Excel文件中没有找到工作表')
          return
        }
        
        // 解析数据
        const rows = []
        const headerRow = worksheet.getRow(1)
        const headers = []
        
        // 获取表头
        headerRow.eachCell((cell, colNumber) => {
          headers[colNumber - 1] = cell.value
        })
        
        // 验证表头
        const requiredHeaders = ['设备编号', '巡检位置', '巡检频率', '开始时间', '结束时间', '状态']
        const missingHeaders = requiredHeaders.filter(header => !headers.includes(header))
        
        if (missingHeaders.length > 0) {
          ElMessage.error(`Excel文件缺少必要的列: ${missingHeaders.join(', ')}`)
          return
        }
        
        // 获取设备列表用于验证
        await fetchDeviceList()
        const validDeviceIds = deviceOptions.value.map(device => device.value)
        
        // 解析数据行
        worksheet.eachRow((row, rowNumber) => {
          // 跳过表头行
          if (rowNumber === 1) return
          
          // 跳过空行或说明行
          if (rowNumber === 3 && row.getCell(1).value?.toString().includes('必填')) return
          
          const rowData = {}
          let isEmptyRow = true
          
          // 获取每个单元格的值
          row.eachCell((cell, colNumber) => {
            const header = headers[colNumber - 1]
            if (header) {
              let value = cell.value
              
              // 处理日期类型
              if (cell.type === ExcelJS.ValueType.Date) {
              // 使用ISO格式字符串,确保后端可以正确解析
              value = new Date(value).toISOString();
              }
              
              rowData[header] = value
              if (value) isEmptyRow = false
            }
          })
          
          // 跳过空行
          if (isEmptyRow) return
          
          // 转换为API格式
          const planData = {
            deviceId: rowData['设备编号'],
            location: rowData['巡检位置'],
            frequency: rowData['巡检频率'],
            startTime: rowData['开始时间'],
            endTime: rowData['结束时间'],
            status: rowData['状态'] || '待启用',
            validationMsg: ''
          }
          
          // 验证数据
          const validationErrors = []
          
          // 验证设备编号
          if (!planData.deviceId) {
            validationErrors.push('设备编号不能为空')
          } else if (!validDeviceIds.includes(planData.deviceId)) {
            validationErrors.push('设备编号不存在')
          }
          
          // 验证巡检位置
          if (!planData.location) {
            validationErrors.push('巡检位置不能为空')
          }
          
          // 验证巡检频率
          const validFrequencies = ['日', '周', '月', '季度', '半年', '年']
          if (!planData.frequency) {
            validationErrors.push('巡检频率不能为空')
          } else if (!validFrequencies.includes(planData.frequency)) {
            validationErrors.push(`巡检频率必须是以下值之一: ${validFrequencies.join(', ')}`)
          }
          
          // 验证开始时间
          if (!planData.startTime) {
            validationErrors.push('开始时间不能为空')
          } else if (isNaN(new Date(planData.startTime).getTime())) {
            validationErrors.push('开始时间格式无效')
          }
          
          // 验证结束时间
          if (!planData.endTime) {
            validationErrors.push('结束时间不能为空')
          } else if (isNaN(new Date(planData.endTime).getTime())) {
            validationErrors.push('结束时间格式无效')
          }
          
          // 验证状态
          const validStatuses = ['待启用', '启用', '停用']
          if (planData.status && !validStatuses.includes(planData.status)) {
            validationErrors.push(`状态必须是以下值之一: ${validStatuses.join(', ')}`)
          }
          
          // 设置验证消息
          if (validationErrors.length > 0) {
            planData.validationMsg = validationErrors.join('; ')
          }
          
          rows.push(planData)
        })
        
        // 更新导入数据
        importData.value = rows
        
        // 显示导入预览对话框
        if (rows.length > 0) {
          importDialogVisible.value = true
        } else {
          ElMessage.warning('没有找到有效的数据行')
        }
      } catch (error) {
        console.error('解析Excel文件失败:', error)
        ElMessage.error('解析Excel文件失败')
      }
    }
    
    reader.readAsArrayBuffer(file.raw)
  } catch (error) {
    console.error('读取Excel文件失败:', error)
    ElMessage.error('读取Excel文件失败')
  }
}

// 确认导入
const confirmImport = async () => {
  try {
    // 过滤出有效的数据
    const validData = importData.value.filter(item => !item.validationMsg)
    
    if (validData.length === 0) {
      ElMessage.warning('没有有效的数据可以导入')
      return
    }
    
    // 批量添加前添加日志
    console.log('准备导入的数据:', validData);
    const promises = validData.map(item => {
      // Create a new object with formatted dates
      const formattedItem = {
        ...item,
        startTime: item.startTime ? new Date(item.startTime).toISOString() : null,
        endTime: item.endTime ? new Date(item.endTime).toISOString() : null
      };
      return addPlan(formattedItem);
    });
    const results = await Promise.all(promises)
    
    // 检查结果
    const successCount = results.filter(res => res.code === 200).length
    
    if (successCount === validData.length) {
      ElMessage.success(`成功导入 ${successCount} 条巡检计划`)
      importDialogVisible.value = false
      fetchPlanList() // 刷新列表
    } else {
      ElMessage.warning(`部分导入成功,成功 ${successCount} 条,失败 ${validData.length - successCount} 条`)
      importDialogVisible.value = false
      fetchPlanList() // 刷新列表
    }
  } catch (error) {
    console.error('批量导入失败:', error)
    ElMessage.error('批量导入失败')
  }
}
</script>

<style scoped>
.inspect-container {
  padding: 20px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.header-left {
  font-size: 16px;
  font-weight: bold;
}

.header-right {
  display: flex;
  gap: 10px;
}

.batch-actions {
  display: flex;
  gap: 10px;
}

.dialog-footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

/* 添加导入上传样式 */
.import-upload {
  display: inline-block;
}
</style>
posted @ 2025-05-23 17:35  QixunQiu  阅读(15)  评论(0)    收藏  举报