三拍:巡检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>
浙公网安备 33010602011771号