三拍:检测工单

TestingOrderController

package com.example.demo.controller;

import com.example.demo.entity.TestingOrder;
import com.example.demo.service.TestingOrderService;
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/testing-order")
public class TestingOrderController {

    @Autowired
    private TestingOrderService testingOrderService;

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

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

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

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

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

    @PutMapping("/status")
    public Result updateStatus(@RequestParam String orderId, @RequestParam String status) {
        return testingOrderService.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 testingOrderService.generateOrdersForPlans(planIds)
            ? Result.success()
            : Result.error("工单生成失败");
    }
}

TestingOrder

package com.example.demo.controller;

import com.example.demo.entity.TestingOrder;
import com.example.demo.service.TestingOrderService;
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/testing-order")
public class TestingOrderController {

    @Autowired
    private TestingOrderService testingOrderService;

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

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

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

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

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

    @PutMapping("/status")
    public Result updateStatus(@RequestParam String orderId, @RequestParam String status) {
        return testingOrderService.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 testingOrderService.generateOrdersForPlans(planIds)
            ? Result.success()
            : Result.error("工单生成失败");
    }
}

TestingOrderServiceImpl

package com.example.demo.service.impl;

import com.example.demo.entity.TestingOrder;
import com.example.demo.mapper.TestingOrderMapper;
import com.example.demo.service.TestingOrderService;
import com.example.demo.utils.TestingOrderIdGenerator;
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 TestingOrderServiceImpl implements TestingOrderService {

    @Autowired
    private TestingOrderMapper testingOrderMapper;

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

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

    @Override
    @Transactional
    public boolean addOrder(TestingOrder order) {
        try {
            order.setOrderId(TestingOrderIdGenerator.generateOrderId());
            if (order.getStatus() == null) {
                order.setStatus("待处理");
            }
            if (order.getPlanTime() == null) {
                order.setPlanTime(LocalDateTime.now());
            }
            return testingOrderMapper.insert(order) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

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

    @Override
    @Transactional
    public boolean deleteOrder(String orderId) {
        try {
            return testingOrderMapper.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 testingOrderMapper.updateStatus(orderId, status) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    @Transactional
    public boolean generateOrdersForPlans(List<Integer> planIds) {
        try {
            for (Integer planId : planIds) {
                TestingOrder order = new TestingOrder();
                order.setPlanId(planId);
                order.setPlanTime(LocalDateTime.now());
                order.setStatus("待处理");
                order.setEngineerId(null);
                order.setTestingDesc(null);
                order.setPhotos(null);
                
                // 生成工单
                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;
    }
}

TestingOrderService

package com.example.demo.service;

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

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

TestingDetail.vue

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

    <el-card class="testing-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.testingDesc"
            type="textarea"
            :rows="3"
            placeholder="请输入检测描述"
            :disabled="orderDetail.status === '已完成' || orderDetail.status === '待审批'"
          />
        </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 === '已完成' || orderDetail.status === '待审批'"
                    />
                  </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 === '已完成' || orderDetail.status === '待审批' || !canOperate"
        >{{ !canOperate ? '请等待3秒' : '保存' }}</el-button>
        <el-button 
          type="success" 
          @click="handleComplete" 
          :disabled="orderDetail.status === '已完成' || orderDetail.status === '待审批' || !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/testingOrder'
import { getDevice } from '@/api/device'
import { getPlanById } from '@/api/testing'

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('获取工单详情失败')
  }
}

// 修改 handleSave 函数
const handleSave = async () => {
  try {
    const imageUrls = fileList.value
      .filter(file => file.url && file.url.length > 22)
      .map(file => file.url)
    
    const updateData = {
      orderId: orderDetail.value.orderId,
      planId: orderDetail.value.planId,      // 保持原值
      engineerId: orderDetail.value.engineerId, // 保持原值
      planTime: orderDetail.value.planTime,   // 保持原值
      testingDesc: orderDetail.value.testingDesc,
      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)
  }
}

// 修改 handleComplete 函数
const handleComplete = async () => {
  try {
    const imageUrls = fileList.value
      .filter(file => file.url && file.url.length > 22)
      .map(file => file.url)
    
    const updateData = {
      orderId: orderDetail.value.orderId,
      planId: orderDetail.value.planId,      // 保持原值
      engineerId: orderDetail.value.engineerId, // 保持原值
      planTime: orderDetail.value.planTime,   // 保持原值
      testingDesc: orderDetail.value.testingDesc,
      photos: imageUrls.join(',')
    }

    // 保存工单信息
    const saveResponse = await updateOrder(updateData)
    if (saveResponse.code !== 200) {
      throw new Error(saveResponse.msg || '保存失败')
    }

    // 更新工单状态为待审批
    const statusResponse = await updateOrderStatus(orderDetail.value.orderId, '待审批')
    if (statusResponse.code === 200) {
      ElMessage.success('工单已提交审批')
      orderDetail.value.status = '待审批'
    } else {
      throw new Error(statusResponse.msg || '更新状态失败')
    }
  } catch (error) {
    console.error('完成工单失败:', error)
    ElMessage.error('完成工单失败:' + error.message)
  }
}

// 触发文件选择
const triggerFileInput = () => {
  if (orderDetail.value.status === '已完成' || orderDetail.value.status === '待审批') return
  fileInput.value.click()
}

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

// 处理拖拽
const handleDrop = (e) => {
  if (orderDetail.value.status === '已完成' || orderDetail.value.status === '待审批') 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 === '已完成' || orderDetail.value.status === '待审批') return
  fileList.value.splice(index, 1)
}

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

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

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

.testing-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: 5px;
  width: 100%;
  box-sizing: border-box;
  overflow: hidden; /* 防止内容溢出 */
}

.upload-area {
  width: 100%;
  min-height: 150px; /* 减小最小高度 */
  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: 10px; /* 减小内边距 */
  box-sizing: border-box; /* 确保padding计入总宽度 */
}

.upload-area:hover {
  border-color: #409EFF;
}

.upload-icon {
  font-size: 28px;
  color: #8c939d;
  margin-bottom: 8px;
}

.upload-text {
  text-align: center;
  color: #606266;
}

.upload-text p {
  margin: 5px 0 0;
  font-size: 12px;
}

/* 修改图片预览容器样式 */
.image-preview-container {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); /* 减小图片尺寸 */
  gap: 10px; /* 减小间距 */
  padding: 5px;
  box-sizing: border-box;
}

.image-preview-item {
  position: relative;
  aspect-ratio: 1;
  border-radius: 4px;
  overflow: hidden;
  max-height: 120px; /* 限制最大高度 */
}

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

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

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

/* 添加描述项样式,确保表头宽度固定 */
:deep(.el-descriptions__label) {
  min-width: 120px;
  width: 120px;
}

:deep(.el-descriptions__content) {
  overflow: auto; /* 允许内容滚动 */
}

/* 确保表格布局正确 */
:deep(.el-descriptions) {
  width: 100%;
  table-layout: fixed;
}

:deep(.el-descriptions__body) {
  width: 100%;
}

:deep(.el-descriptions__table) {
  width: 100%;
  table-layout: fixed;
}
</style>
posted @ 2025-05-10 22:24  QixunQiu  阅读(12)  评论(0)    收藏  举报