三拍:巡检工单

InspectionOrderController

package com.example.demo.controller;

import com.example.demo.entity.InspectionOrder;
import com.example.demo.service.InspectionOrderService;
import com.example.demo.common.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/inspection-order")
public class InspectionOrderController {

    @Autowired
    private InspectionOrderService inspectionOrderService;

    @GetMapping("/list")
    public Result getAll() {
        return Result.success(inspectionOrderService.getAllOrders());
    }

    @GetMapping("/{id}")
    public Result getById(@PathVariable("id") String orderId) {
        return Result.success(inspectionOrderService.getOrderById(orderId));
    }

    @PostMapping("/add")
    public Result add(@RequestBody InspectionOrder order) {
        return inspectionOrderService.addOrder(order)
            ? Result.success()
            : Result.error("添加工单失败");
    }

    @PutMapping("/update")
    public Result update(@RequestBody InspectionOrder order) {
        return inspectionOrderService.updateOrder(order)
            ? Result.success()
            : Result.error("更新工单失败");
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") String orderId) {
        return inspectionOrderService.deleteOrder(orderId)
            ? Result.success()
            : Result.error("删除工单失败");
    }

    @PutMapping("/status")
    public Result updateStatus(@RequestParam String orderId, @RequestParam String status) {
        return inspectionOrderService.updateOrderStatus(orderId, status)
            ? Result.success()
            : Result.error("状态更新失败");
    }

    @PostMapping("/generate")
    public Result generateOrders(@RequestBody Map<String, List<Integer>> request) {
        List<Integer> planIds = request.get("planIds");
        return inspectionOrderService.generateOrdersForPlans(planIds)
            ? Result.success()
            : Result.error("工单生成失败");
    }
}

InspectionOrder

package com.example.demo.entity;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class InspectionOrder {
    private String orderId;
    private Integer planId;
    private String engineerId;
    private LocalDateTime planTime;
    private String inspectionDesc;
    private String status;
    private String photos;

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public Integer getPlanId() {
        return planId;
    }

    public void setPlanId(Integer planId) {
        this.planId = planId;
    }

    public String getEngineerId() {
        return engineerId;
    }

    public void setEngineerId(String engineerId) {
        this.engineerId = engineerId;
    }

    public LocalDateTime getPlanTime() {
        return planTime;
    }

    public void setPlanTime(LocalDateTime planTime) {
        this.planTime = planTime;
    }

    public String getInspectionDesc() {
        return inspectionDesc;
    }

    public void setInspectionDesc(String inspectionDesc) {
        this.inspectionDesc = inspectionDesc;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getPhotos() {
        return photos;
    }

    public void setPhotos(String photos) {
        this.photos = photos;
    }
}

InspectionOrderServiceImpl

package com.example.demo.service.impl;

import com.example.demo.entity.InspectionOrder;
import com.example.demo.mapper.InspectionOrderMapper;
import com.example.demo.service.InspectionOrderService;
import com.example.demo.utils.OrderIdGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class InspectionOrderServiceImpl implements InspectionOrderService {

    @Autowired
    private InspectionOrderMapper inspectionOrderMapper;

    @Override
    public List<InspectionOrder> getAllOrders() {
        try {
            return inspectionOrderMapper.selectAll();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public InspectionOrder getOrderById(String orderId) {
        try {
            return inspectionOrderMapper.selectById(orderId);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    @Transactional
    public boolean addOrder(InspectionOrder order) {
        try {
            // 生成工单号
            order.setOrderId(OrderIdGenerator.generateOrderId());
            // 设置默认状态
            if (order.getStatus() == null) {
                order.setStatus("待处理");
            }
            // 设置创建时间
            if (order.getPlanTime() == null) {
                order.setPlanTime(LocalDateTime.now());
            }
            return inspectionOrderMapper.insert(order) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    @Transactional
    public boolean updateOrder(InspectionOrder order) {
        try {
            if (order.getOrderId() == null) {
                return false;
            }
            return inspectionOrderMapper.update(order) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    @Transactional
    public boolean deleteOrder(String orderId) {
        try {
            return inspectionOrderMapper.deleteById(orderId) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    @Transactional
    public boolean updateOrderStatus(String orderId, String status) {
        try {
            if (!isValidStatus(status)) {
                return false;
            }
            return inspectionOrderMapper.updateStatus(orderId, status) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Transactional
    public boolean generateOrdersForPlans(List<Integer> planIds) {
        try {
            for (Integer planId : planIds) {
                InspectionOrder order = new InspectionOrder();
                order.setPlanId(planId);
                order.setPlanTime(LocalDateTime.now());
                order.setStatus("待处理");
                
                // 生成工单
                if (!addOrder(order)) {
                    throw new RuntimeException("工单生成失败");
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private boolean isValidStatus(String status) {
        if (status == null) {
            return false;
        }
        String[] validStatuses = {"待处理", "处理中", "已完成", "待审批", "已关闭"};
        for (String validStatus : validStatuses) {
            if (validStatus.equals(status)) {
                return true;
            }
        }
        return false;
    }
}

InspectionOrderService

package com.example.demo.service;

import com.example.demo.entity.InspectionOrder;
import java.util.List;

public interface InspectionOrderService {
    List<InspectionOrder> getAllOrders();
    InspectionOrder getOrderById(String orderId);
    boolean addOrder(InspectionOrder order);
    boolean updateOrder(InspectionOrder order);
    boolean deleteOrder(String orderId);
    boolean updateOrderStatus(String orderId, String status);
    boolean generateOrdersForPlans(List<Integer> planIds);
}

InspectionDetail.vue

<template>
  <div class="inspection-detail-container">

    <el-card class="inspection-card">
      <template #header>
        <div class="card-header">
          <span>巡检工单详情</span>
          <el-button @click="$router.back()">返回</el-button>
        </div>
      </template>

      <!-- 添加步骤条 -->
      <el-card class="steps-card" shadow="hover">
        <el-steps :active="getActiveStep(orderDetail.status)" finish-status="success" align-center>
          <el-step title="待处理" description="工单已创建"></el-step>
          <el-step title="处理中" description="工程师处理中"></el-step>
          <el-step title="待审批" description="等待审核"></el-step>
          <el-step title="已完成" description="工单已完成"></el-step>
        </el-steps>
      </el-card>

      <el-descriptions :column="1" border>
        <el-descriptions-item label="工单号">{{ orderDetail.orderId }}</el-descriptions-item>
        <el-descriptions-item label="计划编号">{{ orderDetail.planId }}</el-descriptions-item>
        <el-descriptions-item label="设备ID">{{ deviceInfo.deviceId }}</el-descriptions-item>
        <el-descriptions-item label="设备类型">{{ deviceInfo.deviceType }}</el-descriptions-item>
        <el-descriptions-item label="设备位置">{{ deviceInfo.location }}</el-descriptions-item>
        <el-descriptions-item label="计划时间">{{ formatDateTime(orderDetail.planTime) }}</el-descriptions-item>
        <el-descriptions-item label="巡检描述">
          <el-input
            v-model="orderDetail.inspectionDesc"
            type="textarea"
            :rows="3"
            placeholder="请输入巡检描述"
            :disabled="orderDetail.status === 'completed'"
          />
        </el-descriptions-item>
        <el-descriptions-item label="巡检照片">
          <div class="upload-section">
            <input
              type="file"
              ref="fileInput"
              style="display: none"
              accept="image/*"
              @change="handleFileChange"
            />
            <div 
              class="upload-area" 
              @click="triggerFileInput"
              @dragover.prevent
              @drop.prevent="handleDrop"
            >
              <template v-if="fileList.length === 0">
                <el-icon class="upload-icon"><Upload /></el-icon>
                <div class="upload-text">
                  <span>点击选择或拖拽图片到此处</span>
                  <p>支持 jpg、png 格式图片</p>
                </div>
              </template>
              
              <div v-else class="image-preview-container">
                <div v-for="(img, index) in fileList" :key="index" class="image-preview-item">
                  <el-image :src="img.url" fit="cover" />
                  <div class="image-actions">
                    <el-button 
                      type="danger" 
                      icon="Delete" 
                      circle 
                      @click.stop="removeImage(index)"
                      :disabled="orderDetail.status === 'completed'"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </el-descriptions-item>
      </el-descriptions>

      <div class="action-buttons">
        <el-button 
          type="primary" 
          @click="handleSave" 
          style="margin-right: 10px"
          :disabled="orderDetail.status === 'completed' || !canOperate"
        >{{ !canOperate ? '请等待3秒' : '保存' }}</el-button>
        <el-button 
          type="success" 
          @click="handleComplete" 
          :disabled="orderDetail.status === 'completed' || !canOperate"
        >{{ !canOperate ? '请等待3秒' : '完成工单' }}</el-button>
      </div>
    </el-card>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Upload } from '@element-plus/icons-vue'
import { getOrderById, updateOrder, updateOrderStatus } from '@/api/inspectionOrder'
import { getDevice } from '@/api/device'
import { getPlanById } from '@/api/inspection'

const route = useRoute()
const router = useRouter()
const orderDetail = ref({})
const deviceInfo = ref({})
const fileList = ref([])
const fileInput = ref(null)
const canOperate = ref(false)
const watermarkColor = ref('#000000')

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

// 获取当前步骤
const getActiveStep = (status) => {
  const stepMap = {
    'pending': 0,
    'processing': 1,
    'waiting_approval': 2,
    'completed': 3,
    '待处理': 0,
    '处理中': 1,
    '待审批': 2,
    '已完成': 3
  }
  return stepMap[status] || 0
}

// 获取工单详情
const fetchOrderDetail = async () => {
  try {
    const orderId = route.params.id
    const response = await getOrderById(orderId)
    
    if (response.code === 200 && response.data) {
      orderDetail.value = response.data
      
      // 获取计划信息
      const planResponse = await getPlanById(response.data.planId)
      if (planResponse.code === 200 && planResponse.data) {
        // 获取设备信息
        const deviceResponse = await getDevice(planResponse.data.deviceId)
        if (deviceResponse.code === 200) {
          deviceInfo.value = deviceResponse.data
        }
      }

      // 处理照片数据 - 修改这部分代码
      if (orderDetail.value.photos) {
        try {
          // 清空现有图片列表
          fileList.value = []
          
          // 将逗号分隔的字符串转换为数组,并过滤掉空字符串
          const photoArray = orderDetail.value.photos.split(',').filter(item => item.trim() !== '')
          
          // 处理每张照片
          fileList.value = photoArray.map((base64String, index) => {
            // 检查并处理 Base64 字符串
            let imgSrc = base64String
            if (base64String && !base64String.startsWith('data:image')) {
              imgSrc = 'data:image/png;base64,' + base64String
            }

            return {
              url: imgSrc,
              name: `photo_${index}`  // 使用索引而不是时间戳
            }
          }).filter(item => {
            // 验证 Base64 数据的有效性
            return item.url && item.url.length > 22  // 最小有效base64长度检查
          })
        } catch (error) {
          console.error('处理照片数据失败:', error)
          ElMessage.warning('部分照片加载失败')
          fileList.value = []  // 出错时清空列表
        }
      } else {
        fileList.value = []  // 没有照片时确保列表为空
      }
    }
  } catch (error) {
    console.error('获取工单详情失败:', error)
    ElMessage.error('获取工单详情失败')
  }
}

// 修改完成工单处理函数
const handleComplete = async () => {
  try {
    // 先保存当前工单信息
    await handleSave()
    
    // 更新工单状态为待审批
    await updateOrderStatus(orderDetail.value.orderId, '待审批')
    ElMessage.success('工单已提交审批')
    orderDetail.value.status = '待审批'
  } catch (error) {
    console.error('完成工单失败:', error)
    ElMessage.error('完成工单失败')
  }
}

// 处理保存 - 修改这部分代码
const handleSave = async () => {
  try {
    // 将图片转换为Base64字符串数组,过滤掉可能的无效数据
    const imageUrls = fileList.value
      .filter(file => file.url && file.url.length > 22)  // 过滤无效图片
      .map(file => file.url)
    
    // 构建更新的工单数据
    const updateData = {
      ...orderDetail.value,
      photos: imageUrls.join(',') // 将图片数组转换为逗号分隔的字符串
    }

    const response = await updateOrder(updateData)
    if (response.code === 200) {
      ElMessage.success('保存成功')
    } else {
      throw new Error(response.msg || '保存失败')
    }
  } catch (error) {
    console.error('保存失败:', error)
    ElMessage.error('保存失败:' + error.message)
  }
}

// 触发文件选择
const triggerFileInput = () => {
  if (orderDetail.value.status === 'completed') return
  fileInput.value.click()
}

// 处理文件选择
const handleFileChange = (e) => {
  const file = e.target.files[0]
  if (file) {
    processImage(file)
  }
}

// 处理拖拽
const handleDrop = (e) => {
  if (orderDetail.value.status === 'completed') return
  const file = e.dataTransfer.files[0]
  if (file && file.type.startsWith('image/')) {
    processImage(file)
  } else {
    ElMessage.warning('请上传图片文件')
  }
}

// 处理图片
const processImage = (file) => {
  const reader = new FileReader()
  reader.onload = (e) => {
    const img = new Image()
    img.onload = () => {
      const canvas = document.createElement('canvas')
      canvas.width = img.width
      canvas.height = img.height
      const ctx = canvas.getContext('2d')

      ctx.drawImage(img, 0, 0)
      
      // 添加水印
      const text = new Date().toLocaleString('zh-CN')
      ctx.font = '24px Arial'
      ctx.fillStyle = watermarkColor.value
      ctx.globalAlpha = 0.5
      
      const textWidth = ctx.measureText(text).width
      const x = canvas.width - textWidth - 20
      const y = canvas.height - 20
      
      ctx.fillText(text, x, y)

      const watermarkedImage = canvas.toDataURL(file.type)
      fileList.value.push({
        url: watermarkedImage,
        name: file.name
      })
    }
    img.src = e.target.result
  }
  reader.readAsDataURL(file)
}

// 移除图片
const removeImage = (index) => {
  if (orderDetail.value.status === 'completed') return
  fileList.value.splice(index, 1)
}

onMounted(() => {
  fetchOrderDetail()
  setTimeout(() => {
    canOperate.value = true
  }, 3000)
})
</script>

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

.steps-card {
  margin-bottom: 20px;
}

.inspection-card {
  margin-bottom: 20px;
}

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

.action-buttons {
  margin-top: 20px;
  text-align: center;
}

.upload-section {
  padding: 10px;
  width: 100%;
  box-sizing: border-box;
  overflow: hidden; /* 添加此行 */
}

.upload-area {
  width: 100%;
  min-height: 180px;
  border: 2px dashed #d9d9d9;
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  transition: border-color 0.3s;
  padding: 20px;
  box-sizing: border-box; /* 添加此行 */
}

.image-preview-container {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 16px;
  padding: 10px;
  box-sizing: border-box; /* 添加此行 */
}

.image-preview-item {
  position: relative;
  aspect-ratio: 1;
  border-radius: 4px;
  overflow: hidden;
}

.image-preview-item .el-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.image-actions {
  position: absolute;
  top: 8px;
  right: 8px;
  display: none;
}

.image-preview-item:hover .image-actions {
  display: block;
}

:deep(.el-descriptions__label) {
  min-width: 120px;
  width: 120px;
  white-space: nowrap;
}
</style>
posted @ 2025-05-08 20:32  QixunQiu  阅读(15)  评论(0)    收藏  举报