三拍:保养工单

MaintenanceOrderController

package com.example.demo.controller;

import com.example.demo.entity.MaintenanceOrder;
import com.example.demo.service.MaintenanceOrderService;
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/maintenance-order")
public class MaintenanceOrderController {

    @Autowired
    private MaintenanceOrderService maintenanceOrderService;

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

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

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

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

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

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

MaintenanceOrder

package com.example.demo.entity;

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

@Data
public class MaintenanceOrder {
    private String orderId;
    private Integer planId;
    private String engineerId;
    private LocalDateTime planTime;
    private String maintenanceDesc;
    private String status;
    private String beforePhotos;
    private String afterPhotos;

    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 getMaintenanceDesc() {
        return maintenanceDesc;
    }

    public void setMaintenanceDesc(String maintenanceDesc) {
        this.maintenanceDesc = maintenanceDesc;
    }

    public String getStatus() {
        return status;
    }

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

    public String getBeforePhotos() {
        return beforePhotos;
    }

    public void setBeforePhotos(String beforePhotos) {
        this.beforePhotos = beforePhotos;
    }

    public String getAfterPhotos() {
        return afterPhotos;
    }

    public void setAfterPhotos(String afterPhotos) {
        this.afterPhotos = afterPhotos;
    }
}

MaintenanceOrderServiceImpl

package com.example.demo.service.impl;

import com.example.demo.entity.MaintenanceOrder;
import com.example.demo.mapper.MaintenanceOrderMapper;
import com.example.demo.service.MaintenanceOrderService;
import com.example.demo.utils.MaintenanceOrderIdGenerator;
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 MaintenanceOrderServiceImpl implements MaintenanceOrderService {

    @Autowired
    private MaintenanceOrderMapper maintenanceOrderMapper;

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

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

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

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

    @Override
    @Transactional
    public boolean deleteOrder(String orderId) {
        try {
            return maintenanceOrderMapper.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 maintenanceOrderMapper.updateStatus(orderId, status) > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    @Transactional
    public boolean generateOrdersForPlans(List<Integer> planIds) {
        try {
            for (Integer planId : planIds) {
                MaintenanceOrder order = new MaintenanceOrder();
                order.setPlanId(planId);
                order.setPlanTime(LocalDateTime.now());
                order.setStatus("待处理");
                order.setEngineerId(null);
                order.setMaintenanceDesc(null);
                order.setBeforePhotos(null);
                order.setAfterPhotos(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;
    }
}

MaintenanceOrderService

package com.example.demo.service;

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

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

MaintenanceDetail.vue

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

    <el-card class="maintenance-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.maintenanceDesc"
            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="beforeFileInput"
              style="display: none"
              accept="image/*"
              @change="(e) => handleFileChange(e, 'before')"
            />
            <div 
              class="upload-area" 
              @click="() => triggerFileInput('before')"
              @dragover.prevent
              @drop.prevent="(e) => handleDrop(e, 'before')"
            >
              <template v-if="beforeFileList.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 beforeFileList" :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, 'before')"
                      :disabled="orderDetail.status === '已完成' || orderDetail.status === '待审批'"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </el-descriptions-item>
        <el-descriptions-item label="保养后照片">
          <div class="upload-section">
            <input
              type="file"
              ref="afterFileInput"
              style="display: none"
              accept="image/*"
              @change="(e) => handleFileChange(e, 'after')"
            />
            <div 
              class="upload-area" 
              @click="() => triggerFileInput('after')"
              @dragover.prevent
              @drop.prevent="(e) => handleDrop(e, 'after')"
            >
              <template v-if="afterFileList.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 afterFileList" :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, 'after')"
                      :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/maintenanceOrder'
import { getDevice } from '@/api/device'
import { getPlanById } from '@/api/maintenance'

const route = useRoute()
const router = useRouter()
const orderDetail = ref({})
const deviceInfo = ref({})
const beforeFileList = ref([])
const afterFileList = ref([])
const beforeFileInput = ref(null)
const afterFileInput = 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.beforePhotos) {
        try {
          // 清空现有图片列表
          beforeFileList.value = []
          
          // 将逗号分隔的字符串转换为数组,并过滤掉空字符串
          const photoArray = orderDetail.value.beforePhotos.split(',').filter(item => item.trim() !== '')
          
          // 处理每张照片
          beforeFileList.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: `before_photo_${index}`
            }
          }).filter(item => {
            // 验证 Base64 数据的有效性
            return item.url && item.url.length > 22  // 最小有效base64长度检查
          })
        } catch (error) {
          console.error('处理保养前照片数据失败:', error)
          ElMessage.warning('部分保养前照片加载失败')
          beforeFileList.value = []  // 出错时清空列表
        }
      } else {
        beforeFileList.value = []  // 没有照片时确保列表为空
      }

      // 处理保养后照片数据 - 修改这部分代码
      if (orderDetail.value.afterPhotos) {
        try {
          // 清空现有图片列表
          afterFileList.value = []
          
          // 将逗号分隔的字符串转换为数组,并过滤掉空字符串
          const photoArray = orderDetail.value.afterPhotos.split(',').filter(item => item.trim() !== '')
          
          // 处理每张照片
          afterFileList.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: `after_photo_${index}`
            }
          }).filter(item => {
            // 验证 Base64 数据的有效性
            return item.url && item.url.length > 22  // 最小有效base64长度检查
          })
        } catch (error) {
          console.error('处理保养后照片数据失败:', error)
          ElMessage.warning('部分保养后照片加载失败')
          afterFileList.value = []  // 出错时清空列表
        }
      } else {
        afterFileList.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 {
    const beforeImageUrls = beforeFileList.value
      .filter(file => file.url && file.url.length > 22)
      .map(file => file.url)
    
    const afterImageUrls = afterFileList.value
      .filter(file => file.url && file.url.length > 22)
      .map(file => file.url)
    
    const updateData = {
      ...orderDetail.value,
      beforePhotos: beforeImageUrls.join(','),
      afterPhotos: afterImageUrls.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 = (type) => {
  if (orderDetail.value.status === '已完成' || orderDetail.value.status === '待审批') return
  if (type === 'before') {
    beforeFileInput.value.click()
  } else {
    afterFileInput.value.click()
  }
}

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

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

// 处理图片
const processImage = (file, type) => {
  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)
      if (type === 'before') {
        beforeFileList.value.push({
          url: watermarkedImage,
          name: file.name
        })
      } else {
        afterFileList.value.push({
          url: watermarkedImage,
          name: file.name
        })
      }
    }
    img.src = e.target.result
  }
  reader.readAsDataURL(file)
}

// 移除图片
const removeImage = (index, type) => {
  if (orderDetail.value.status === '已完成' || orderDetail.value.status === '待审批') return
  if (type === 'before') {
    beforeFileList.value.splice(index, 1)
  } else {
    afterFileList.value.splice(index, 1)
  }
}

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

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

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

.maintenance-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;
}


</style>
posted @ 2025-05-09 23:34  QixunQiu  阅读(14)  评论(0)    收藏  举报