团队冲刺(补交)之报废审核

后端
实体类

点击查看代码
package com.example.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Entity
@Getter
@Setter
@Table(name = "scrap_records")
public class ScrapRecord implements Serializable {

    // 核心标识
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // ✅ 添加自增策略
    @Column(name = "order_id", nullable = false, length = 255)
    private String orderId;

    @Column(name = "sn", nullable = false, length = 255, unique = true)
    private String sn;

    // 申请信息
    @Column(name = "scrap_reason", nullable = false, length = 255)
    private String scrapReason;

    @Column(name = "damage_photo", length = 255)
    private String damagePhoto;

    @Column(name = "apply_time", nullable = false, length = 255)
    private String applyTime;




    // 执行信息
    @Column(name = "scrap_time", length = 255)
    private String scrapTime;

    @Column(name = "executor", length = 255)
    private String executor;

    @Column(name = "disposal_method", length = 255)
    private String disposalMethod;

    // 状态联动(新增待审核状态)
    @Enumerated(EnumType.STRING)
    @Column(name = "part_status", columnDefinition = "ENUM('待审核','已报废','驳回')")
    private PartStatus partStatus;
    @Column(name = "applicant_id", nullable = false, length = 255)
    private String applicantId;

    public enum PartStatus {
        待审核, 已报废,驳回
    }

    // 无参构造
    public ScrapRecord() {
    }


    public ScrapRecord(String orderId, String sn, String scrapReason, String damagePhoto, String applyTime, String scrapTime, String executor, String disposalMethod, PartStatus partStatus, String applicantId) {
        this.orderId = orderId;
        this.sn = sn;
        this.scrapReason = scrapReason;
        this.damagePhoto = damagePhoto;
        this.applyTime = applyTime;

        this.scrapTime = scrapTime;
        this.executor = executor;
        this.disposalMethod = disposalMethod;
        this.partStatus = partStatus;
        this.applicantId = applicantId;
    }

    public String getOrderId() {
        return orderId;
    }

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

    public String getSn() {
        return sn;
    }

    public void setSn(String sn) {
        this.sn = sn;
    }

    public String getScrapReason() {
        return scrapReason;
    }

    public void setScrapReason(String scrapReason) {
        this.scrapReason = scrapReason;
    }

    public String getDamagePhoto() {
        return damagePhoto;
    }

    public void setDamagePhoto(String damagePhoto) {
        this.damagePhoto = damagePhoto;
    }

    public String getApplyTime() {
        return applyTime;
    }

    public void setApplyTime(String applyTime) {
        this.applyTime = applyTime;
    }


    public String getScrapTime() {
        return scrapTime;
    }

    public void setScrapTime(String scrapTime) {
        this.scrapTime = scrapTime;
    }

    public String getExecutor() {
        return executor;
    }

    public void setExecutor(String executor) {
        this.executor = executor;
    }

    public String getDisposalMethod() {
        return disposalMethod;
    }

    public void setDisposalMethod(String disposalMethod) {
        this.disposalMethod = disposalMethod;
    }

    public PartStatus getPartStatus() {
        return partStatus;
    }

    public void setPartStatus(PartStatus partStatus) {
        this.partStatus = partStatus;
    }

    public String getApplicantId() {
        return applicantId;
    }

    public void setApplicantId(String applicantId) {
        this.applicantId = applicantId;
    }
}
controller层
点击查看代码
package com.example.controller;

import com.example.dao.ScrapRecordRepository;
import com.example.entity.ScrapRecord;
import com.example.service.ScrapRecordService;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/scrapRecord/scrap-records")
public class ScrapRecordController {


    private final ScrapRecordService service;

    public ScrapRecordController(ScrapRecordService service) {
        this.service = service;
    }

    /**
     * 创建记录(带文件上传)
     */
    @PostMapping
    public ResponseEntity<ScrapRecord> createRecord(
            @RequestParam("sn") String sn,
            @RequestParam("scrapReason") String scrapReason,
            @RequestParam("applicantId") String applicantId,
            @RequestParam(value = "damagePhoto", required = false) MultipartFile file
    ) throws IOException {
        ScrapRecord record = new ScrapRecord();
        record.setSn(sn);
        record.setScrapReason(scrapReason);
        // 其他字段由服务层填充

        ScrapRecord savedRecord = service.saveRecord(record, file, applicantId);
        return ResponseEntity.ok(savedRecord);
    }

    /**
     * 单独上传/更新照片
     */
    @PostMapping("/{orderId}/upload-photo")
    public ResponseEntity<String> uploadPhoto(
            @PathVariable String orderId,
            @RequestParam("file") MultipartFile file) throws IOException {
        service.uploadDamagePhoto(orderId, file);
        return ResponseEntity.ok("Photo uploaded successfully");
    }
    // 新增查询接口
    @GetMapping("/my-records")
    public ResponseEntity<List<ScrapRecord>> getMyRecords(
            @RequestParam String applicantId
    ) {
        List<ScrapRecord> records = service.findByApplicantId(applicantId);
        return ResponseEntity.ok(records);
    }

    @GetMapping("/photo-url")
    public ResponseEntity<String> getPhotoRelativePath(
            @RequestParam String damagePhoto) {

        // 安全校验文件名格式
        if (damagePhoto.contains("..")) {
            return ResponseEntity.badRequest().body("非法文件名");
        }

        // 生成可访问的相对路径(对应静态资源映射)
        String relativePath = damagePhoto;
        return ResponseEntity.ok(relativePath);
    }
    @PutMapping("/{orderId}/review")
    public ResponseEntity<ScrapRecord> updateReview(
            @PathVariable String orderId,
            @RequestBody Map<String, Object> updates) {
        ScrapRecord updatedRecord = service.updateReview(orderId, updates);
        return ResponseEntity.ok(updatedRecord);
    }
    @GetMapping
    public ResponseEntity<List<ScrapRecord>> getRecordsByStatus(
            @RequestParam(required = false) ScrapRecord.PartStatus partStatus) {
        if (partStatus == null) {
            return ResponseEntity.ok(service.findAll());
        }
        return ResponseEntity.ok(service.findByPartStatus(partStatus));
    }
    /**
     * 全局异常处理(示例)
     */
    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleFileUploadError(IOException e) {
        return ResponseEntity.internalServerError().body("文件上传失败: " + e.getMessage());
    }
}
dao层
点击查看代码
package com.example.dao;

import com.example.entity.ScrapRecord;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface ScrapRecordRepository extends JpaRepository<ScrapRecord, String> {
    List<ScrapRecord> findByApplicantId(String applicantId);
    List<ScrapRecord> findByPartStatus(ScrapRecord.PartStatus partStatus);

    Optional<Object> findBySn(String sn);
}
service层
点击查看代码
package com.example.service;

import com.example.dao.ScrapRecordRepository;
import com.example.entity.ScrapRecord;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@Service
public class ScrapRecordService {
    private final RestTemplate restTemplate;
    private final ScrapRecordRepository repository;
    // 文件上传路径


    public ScrapRecordService(ScrapRecordRepository repository, RestTemplate restTemplate) {
        this.repository = repository;
        this.restTemplate = restTemplate;
    }

    /**
     * 保存或更新报废记录(含文件上传)
     */
    public ScrapRecord saveRecord(ScrapRecord record, MultipartFile damagePhoto, String applicantId) throws IOException {
        record.setApplyTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        record.setPartStatus(ScrapRecord.PartStatus.待审核);
        record.setApplicantId(applicantId);

        if (damagePhoto != null && !damagePhoto.isEmpty()) {
            String fileUrl = uploadFileToFileController(damagePhoto);
            record.setDamagePhoto(fileUrl);
        }

        return repository.save(record);
    }

    /**
     * 单独上传照片并更新记录
     */
    public void uploadDamagePhoto(String orderId, MultipartFile file) throws IOException {
        ScrapRecord record = repository.findById(orderId)
                .orElseThrow(() -> new RuntimeException("Record not found"));
        if (file != null && !file.isEmpty()) {
            String fileUrl = uploadFileToFileController(file);
            record.setDamagePhoto(fileUrl);
            repository.save(record);
        }
    }

    /**
     * 获取所有记录
     */
    public List<ScrapRecord> findAll() {
        return repository.findAll();
    }

    /**
     * 生成唯一文件名(UUID + 时间戳)
     */
    private String generateUniqueFilename(MultipartFile file) {
        String originalName = file.getOriginalFilename();
        String fileExtension = originalName.substring(originalName.lastIndexOf("."));
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        return UUID.randomUUID() + "_" + timestamp + fileExtension;
    }
    public List<ScrapRecord> findByApplicantId(String applicantId) {
        return repository.findByApplicantId(applicantId);
    }
    // 新增文件访问方法
    public String getFullImagePath(String filename) {
        return "/uploads/" + filename;  // 与静态资源路径对应
    }
    // 在 ScrapRecordService.java 中添加
// 在类中添加路径校验方法

    private String uploadFileToFileController(MultipartFile file) throws IOException {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
        body.add("file", new ByteArrayResource(file.getBytes()) {
            @Override
            public String getFilename() {
                return file.getOriginalFilename();
            }
        });

        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
        ResponseEntity<Map> response = restTemplate.postForEntity(
                "http://localhost:8080/files/upload",
                requestEntity,
                Map.class
        );

        if (response.getStatusCode() == HttpStatus.OK) {
            Map<String, Object> responseBody = response.getBody();
            if (responseBody != null && (Integer) responseBody.get("code") == 200) {
                return (String) responseBody.get("url");
            } else {
                throw new RuntimeException("文件上传失败: " + responseBody.get("msg"));
            }
        } else {
            throw new RuntimeException("文件上传失败,状态码: " + response.getStatusCode());
        }
    }
    public ScrapRecord updateReview(String orderId, Map<String, Object> updates) {
        ScrapRecord record = repository.findById(orderId)
                .orElseThrow(() -> new RuntimeException("记录不存在"));

        // 强制设置审核时间为当前时间
        record.setScrapTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        // 更新状态
        if (updates.containsKey("partStatus")) {
            record.setPartStatus(ScrapRecord.PartStatus.valueOf(updates.get("partStatus").toString()));
        }

        // 更新处置方式
        if (updates.containsKey("disposalMethod")) {
            record.setDisposalMethod(updates.get("disposalMethod").toString());
        }

        // 更新执行人
        if (updates.containsKey("executor")) {
            record.setExecutor(updates.get("executor").toString());
        }

        // 更新报废时间
        if (updates.containsKey("scrapTime") && updates.get("scrapTime") != null) {
            record.setScrapTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        }

        return repository.save(record);
    }
    public List<ScrapRecord> findByPartStatus(ScrapRecord.PartStatus partStatus) {
        return repository.findByPartStatus(partStatus);
    }
}
前端 报废申请的前端
点击查看代码
<template>
  <div class="scrap-apply-container">
    <el-card class="apply-form">
      <el-form
          ref="form"
          :model="form"
          :rules="rules"
          label-width="120px"
      >
      <h2>备件报废申请</h2>
      <el-form-item label="备件SN号" prop="sn" required>
        <el-autocomplete
            v-model="form.sn"
            :fetch-suggestions="querySearch"
            placeholder="请输入备件序列号"
            clearable
            @select="handleSelect"
            value-key="sn"
        >
          <template #default="{ item }">
            <div>{{ item.sn }}</div>
          </template>
        </el-autocomplete>
      </el-form-item>

        <!-- 报废原因输入 -->
        <el-form-item label="报废原因" prop="scrapReason" required>
          <el-input
              type="textarea"
              v-model="form.scrapReason"
              :rows="3"
              placeholder="请详细描述报废原因"
          ></el-input>
        </el-form-item>

        <!-- 照片上传 -->
        <el-form-item label="现场照片" prop="damagePhoto">
          <el-upload
              action="#"
              :auto-upload="false"
              :on-change="handlePhotoChange"
              :show-file-list="false"
              :class="{ 'is-invalid': !photoFile }"
          >
            <el-button size="small" type="primary">点击上传</el-button>
            <span v-if="photoFile" class="photo-name">
              {{ photoFile.name }}
              <el-icon @click.stop="removePhoto"><Close /></el-icon>
            </span>
          </el-upload>

        </el-form-item>

        <el-form-item>
          <el-button
              type="primary"
              @click="submitForm"
              :loading="isSubmitting"
          >
            提交申请
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
    <!-- 申请记录表格 -->
    <el-card class="record-list mt-4" style="width: 100%">
      <h3>我的报废申请记录</h3>
      <el-table :data="records" style="width: 100%" ref="scrapTable">
        <el-table-column prop="orderId" label="报废单号" width="180" />
        <el-table-column prop="sn" label="SN号" width="120" />
        <el-table-column prop="scrapReason" label="报废原因" />
        <el-table-column label="状态" width="100">
          <template #default="{ row }">
            <el-tag :type="statusTag(row.partStatus)">
              {{ row.partStatus }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="applyTime" label="申请时间" width="180">
          <template #default="{ row }">
            {{ formatTime(row.applyTime) }}
          </template>
        </el-table-column>
        <el-table-column label="照片" width="120">
          <template #default="{ row }">
            <el-image
                :src="getImageUrl(row.damagePhoto)"
                @error="handleImageError"
                :preview-src-list="[getImageUrl(row.damagePhoto)]"
            >
              <template #error>
                <div class="image-error">
                  <el-icon><Picture /></el-icon>
                  <span>图片加载失败</span>
                </div>
              </template>
            </el-image>
          </template>
        </el-table-column>
      </el-table>
    </el-card>

  </div>

</template>

<script>
import axios from 'axios';
import { ElMessage, ElLoading } from 'element-plus';
import {Close, Picture} from '@element-plus/icons-vue';

export default {
  name: 'ScrapApply',
  components: {Close},
  data() {
    // 自定义照片验证规则
    const validatePhoto = (rule, value, callback) => {
      if (!this.photoFile) {
        callback(new Error('必须上传现场照片'));
      } else {
        callback();
      }
    };


    return {
      form: {
        sn: '',
        scrapReason: '',

      },
      rules: {
        sn: [
          { required: true, message: '请输入备件SN号', trigger: 'blur' }
        ],
        scrapReason: [
          { required: true, message: '请输入报废原因', trigger: 'blur' }
        ],
        damagePhoto: [
          { validator: validatePhoto, trigger: 'change' }
        ]
      },
      photoFile: null,
      isSubmitting: false,
      currentUser: null,
      records: [],
      loading: false,
      snOptions: [], // 存储所有SN号的缓存
      loadingSn: false


    };
  },
  async mounted() {
    // 获取当前登录用户
    await this.initializeUser();

    if (this.currentUser) {
      await this.fetchRecords();
    }
  },
  methods: {

    async querySearch(queryString, cb) {
      try {
        const response = await axios.get('/spare_part/sn-search', {
          params: { keyword: queryString }
        });
        const results = response.data.map(sn => ({ value: sn, sn: sn }));
        cb(results);
      } catch (error) {
        console.error('搜索SN失败:', error);
        cb([]);
      }
    },
    // 处理选中事件
    handleSelect(item) {
      this.form.sn = item.sn;
    },

    getImageUrl(url) {
      if (!url) return '';
      // 添加时间戳解决缓存问题
      const separator = url.includes('?') ? '&' : '?';
      return `${url}${separator}t=${new Date().getTime()}`;
    },

    // 新增方法:获取申请记录
    async fetchRecords() {
      if (!this.currentUser) return;

      this.loading = true;
      try {
        const response = await axios.get('/scrapRecord/scrap-records/my-records', {
          params: {
            applicantId: this.currentUser.user_id
          }

        });
        this.records = response.data;
        // 简化布局调整
        this.$nextTick(() => {
          this.adjustTableLayout();
          console.log('Records updated:', this.records);
        });
      } catch (error) {
        ElMessage.error('获取记录失败: ' + (error.response?.data?.message || error.message));
      } finally {
        this.loading = false;
      }
    },handleImageError(e) {
      console.error('Image load failed:', e);
      this.$message.warning('照片加载失败,请检查文件是否存在');
    },

    // 修改 adjustTableLayout 方法
    adjustTableLayout() {
      const table = this.$refs.scrapTable;
      if (!table) {
        console.warn('Table ref not found');
        return;
      }

      // 使用Element Plus内置方法
      this.$nextTick(() => {
        table.doLayout();  // 触发表格重新布局
        console.log('Table layout updated');
      });
    },
    // 状态标签样式
    statusTag(status) {
      return status === '已报废' ? 'success' : 'warning';
    },

    // 时间格式化
    formatTime(timeStr) {
      return new Date(timeStr).toLocaleString('zh-CN');
    },
    // 修改图片路径处理方法
    // 修改图片路径处理方法(添加调试)


    // 处理文件选择
    handlePhotoChange(file) {
      this.photoFile = file.raw;
    },// 新增独立用户初始化方法
    async initializeUser() {
      const user = JSON.parse(sessionStorage.getItem('user'));
      if (!user) {
        ElMessage.error('请先登录');
        await this.$router.push('/login'); // 保持路由跳转一致性
        return;
      }
      this.currentUser = user;
    },

    // 提交表单
    async submitForm() {
      if (!this.currentUser) return;

      try {
        // 先进行表单验证
        await this.$refs.form.validate();

        if (!this.photoFile) {
          this.$message.error('请上传现场照片');
          return;
        }
        this.isSubmitting = true;
        const formData = new FormData();
        formData.append('sn', this.form.sn);
        formData.append('scrapReason', this.form.scrapReason);
        formData.append('applicantId', this.currentUser.user_id);

        if (this.photoFile) {
          formData.append('damagePhoto', this.photoFile);
        }

        const response = await axios.post('/scrapRecord/scrap-records', formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        });

        this.$message.success('申请提交成功!');
        this.resetForm();
      } catch (error) {
        // 修复后的错误处理
        const errorMsg = error.response?.data?.message || error.message;
        this.$message.error(`提交失败: ${errorMsg}`);
      } finally {
        this.isSubmitting = false;
      }
    },

    removePhoto() {
      this.photoFile = null;
      this.$refs.form.validateField('damagePhoto');
    },

    // 重置表单
    resetForm() {
      this.form = {
        sn: '',
        scrapReason: ''
      };
      this.photoFile = null;
    }
  }
};

</script>

<style scoped>
.scrap-apply-container {
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
}

.apply-form {
  margin-bottom: 30px;
}

.photo-name {
  margin-left: 10px;
  color: #666;
}
.is-invalid {
  :deep(.el-upload) {
    border: 1px solid #f56c6c;
    border-radius: 6px;
  }
}

.photo-name {
  display: flex;
  align-items: center;
  margin-left: 10px;

  .el-icon {
    margin-left: 5px;
    color: #999;
    cursor: pointer;

    &:hover {
      color: #f56c6c;
    }
  }
}

.el-form-item__error {
  margin-top: 5px;
}
</style>
报废审核的前端
点击查看代码
<template>
  <div class="app-container">
    <div class="main-content">
    <!-- 筛选条件 -->
    <div class="filter-container">
      <el-select
          v-model="selectedStatus"
          multiple
          collapse-tags
          collapse-tags-tooltip
          placeholder="选择状态"
          style="width: 400px; margin-right: 15px"
          @change="handleStatusChange"
      >
        <el-option
            v-for="status in enhancedStatusOptions"
            :key="status.value"
            :label="status.label"
            :value="status.value"
        />
      </el-select>
    </div>

    <!-- 审核列表 -->
    <el-table :data="filteredList" border highlight-current-row @row-click="handleRowClick" :row-class-name="tableRowClassName">
      <el-table-column prop="sn" label="SN号" width="180" />
      <el-table-column prop="scrapReason" label="报废原因" />
      <el-table-column prop="applyTime" label="申请时间" width="180" />
      <el-table-column prop="applicantId" label="申请人ID" width="120" />
      <el-table-column label="损坏照片" width="120">
        <template #default="{row}">
          <el-image
              style="width: 100px; height: 100px"
              :src="getImageUrl(row.damagePhoto)"
              :preview-src-list="[getImageUrl(row.damagePhoto)]"
              fit="cover"
          />
        </template>
      </el-table-column>
      <el-table-column prop="partStatus" label="当前状态" width="100">
        <template #default="{row}">
          <el-tag :type="statusTagType(row.partStatus)">
            {{ row.partStatus }}
          </el-tag>
        </template>
      </el-table-column>
    </el-table>

    <!-- 审核对话框 -->
    <el-dialog
        v-model="dialogVisible"
        title="报废审核"
        width="40%"
    >
      <el-form :model="currentRecord" label-width="100px">
        <el-form-item label="SN号">
          <el-input v-model="currentRecord.sn" disabled />
        </el-form-item>

        <el-form-item label="报废原因">
          <el-input v-model="currentRecord.scrapReason" disabled />
        </el-form-item>

        <el-form-item label="损坏照片">
          <el-image
              v-if="currentRecord.damagePhoto"
              style="width: 200px; height: 200px"
              :src="getImageUrl(currentRecord.damagePhoto)"
              :preview-src-list="[getImageUrl(currentRecord.damagePhoto)]"
              fit="cover"
          />
          <span v-else>无照片</span>
        </el-form-item>

        <el-form-item label="审核状态" prop="partStatus" required>
          <el-select v-model="currentRecord.partStatus">
            <el-option
                v-for="status in statusOptions"
                :key="status"
                :label="status"
                :value="status"
            />
          </el-select>
        </el-form-item>

        <el-form-item
            v-if="currentRecord.partStatus === '已报废'"
            label="处置方式"
            prop="disposalMethod"
            required
        >
          <el-input
              v-model="currentRecord.disposalMethod"
              placeholder="请输入处置方式"
          />
        </el-form-item>

        <el-form-item
            v-if="currentRecord.partStatus === '已报废'"
            label="执行人"
            prop="executor"
            required
        >
          <el-input
              v-model="currentRecord.executor"
              placeholder="请输入执行人"
          />
        </el-form-item>
      </el-form>

      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="handleSubmit">提交审核</el-button>
      </template>
    </el-dialog>
  </div>
  </div>
</template>

<script setup>
import {ref, reactive, computed, watch, onMounted} from 'vue'
import axios from 'axios'
import {ElMessage} from "element-plus";

const apiUrl = 'http://localhost:8080/scrapRecord/scrap-records'
// 获取当前登录用户信息
const getCurrentUser = () => {
  try {
    const userData = sessionStorage.getItem('user')
    return userData ? JSON.parse(userData) : null
  } catch (e) {
    console.error('用户信息解析失败:', e)
    return null
  }
}
// 状态选项
const statusOptions = ['待审核', '已报废', '驳回']
const statusConfig = {
  all: { label: '全部', value: '' },
  pending: { label: '待审核', value: '待审核' },
  scrapped: { label: '已报废', value: '已报废' },
  rejected: { label: '驳回', value: '驳回' }
}
const finalStatuses = ['已报废', '驳回']
// 响应式数据
const allData = ref([]) // 存储所有数据
const selectedStatus = ref([statusConfig.pending.value])
const dialogVisible = ref(false)
const currentRecord = ref({
  orderId: '',
  sn: '',
  scrapReason: '',
  damagePhoto: '',
  partStatus: '',
  disposalMethod: '',
  executor: ''
})

// 计算筛选后的列表(前端过滤)
const filteredList = computed(() => {
  // 包含"全部"或空选时返回所有数据
  if (selectedStatus.value.includes(statusConfig.all.value) ||
      selectedStatus.value.length === 0) {
    return allData.value
  }

  // 根据选中状态过滤数据
  return allData.value.filter(item =>
      selectedStatus.value.includes(item.partStatus)
  )
})

// 修改后的状态选项(包含全部)
const enhancedStatusOptions = ref([
  statusConfig.all,
  statusConfig.pending,
  statusConfig.scrapped,
  statusConfig.rejected
])


// 获取图片完整URL(添加时间戳防缓存)
const getImageUrl = (url) => {
  if (!url) return '';
  // 直接使用后端返回的完整URL,添加时间戳防止缓存
  const separator = url.includes('?') ? '&' : '?';
  return `${url}${separator}t=${new Date().getTime()}`;
}

// 图片加载错误处理
const handleImageError = (e) => {
  console.error('图片加载失败:', e)
}
// 状态标签样式
const statusTagType = (status) => {
  switch(status) {
    case '待审核': return 'warning'
    case '已报废': return 'success'
    case '驳回': return 'danger'
    default: return 'info'
  }
}

// 获取数据
const fetchAllData = async () => {
  try {
    const response = await axios.get(apiUrl)
    allData.value = response.data
  } catch (error) {
    console.error('获取数据失败:', error)
    ElMessage.error('获取数据失败')
  }
}
// 点击行显示审核对话框
const handleRowClick = (row) => {
  if (finalStatuses.includes(row.partStatus)) {
    ElMessage.warning('该记录已审核完成,不可修改')
    return
  }
  currentRecord.value = { ...row,
    executor: getCurrentUser()?.name || ''}
  dialogVisible.value = true
}
const tableRowClassName = ({ row }) => {
  return finalStatuses.includes(row.partStatus) ? 'disabled-row' : ''
}
// 提交审核
const handleSubmit = async () => {
  try {
    const updates = {
      partStatus: currentRecord.value.partStatus,
      disposalMethod: currentRecord.value.disposalMethod,
      executor: currentRecord.value.executor
    }

    // 1. 先提交报废记录审核
    await axios.put(`${apiUrl}/${currentRecord.value.orderId}/review`, updates)

    // 2. 如果审核通过为"已报废",更新备件状态
    if (currentRecord.value.partStatus === '已报废') {
      // 2.1 通过SN码查询备件信息
      const sparePartRes = await axios.get(`http://localhost:8080/spare_part/bySn/${currentRecord.value.sn}`)

      if (!sparePartRes.data) {
        throw new Error('未找到对应备件')
      }

      // 2.2 创建要更新的备件对象(保留原始数据)
      const updatedPart = {
        ...sparePartRes.data,
        sparePartStatus: '已报废' // 修改状态字段
      }

      // 2.3 调用现有更新接口
      await axios.put(`http://localhost:8080/spare_part/${sparePartRes.data.partId}`, updatedPart)
    }

    ElMessage.success('审核提交成功')
    dialogVisible.value = false
    await fetchAllData()
  } catch (error) {
    console.error('操作失败:', error)
    ElMessage.error(`操作失败: ${error.response?.data?.message || error.message}`)
  }
}
const handleStatusChange = (values) => {
  const lastValue = values[values.length - 1]

  // 处理"全部"选项逻辑
  if (lastValue === statusConfig.all.value) {
    selectedStatus.value = [statusConfig.all.value]
  } else {
    selectedStatus.value = values.filter(v => v !== statusConfig.all.value)
    // 空选时自动选择"全部"
    if (selectedStatus.value.length === 0) {
      selectedStatus.value = [statusConfig.all.value]
    }
  }
}
// 初始化加载数据
onMounted(() => {
  fetchAllData()
})
</script>

<style scoped>
.app-container {
  padding: 20px;
  display: flex;
  justify-content: center; /* 确保内容水平居中 */
}

.main-content {
  width: 1200px; /* 根据实际需要调整这个宽度 */
}

.filter-container {
  margin-bottom: 20px;
}

/* 修改选择框样式 */
.el-select {
  width: 100%; /* 让选择框填满容器 */
}

.el-table {
  width: 100%;
  margin: 0 auto; /* 表格水平居中 */
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

.main-content {
  width: 1200px; /* 根据实际需求调整宽度 */
}

.filter-container {
  margin-bottom: 20px;
}

/* 添加禁用行样式 */
:deep(.disabled-row) {
  cursor: not-allowed;
  background-color: #f5f7fa;

  /* 禁用悬停高亮 */
  &:hover > td {
    background-color: inherit !important;
  }

  /* 禁用当前行高亮 */
  &.current-row > td {
    background-color: inherit !important;
  }
}
/* 保持原有图片样式 */
.el-image {
  cursor: pointer;
  border-radius: 4px;
  transition: transform 0.3s ease; /* 添加悬停动画 */
}

.el-image:hover {
  transform: scale(1.05); /* 图片悬停放大效果 */
}

/* 优化对话框样式 */
.el-dialog {
  border-radius: 8px;
}

.el-form-item {
  margin-bottom: 22px;
}
</style>
前端页面如图 ![](https://img2024.cnblogs.com/blog/3478919/202506/3478919-20250615212533185-217559307.png) ![](https://img2024.cnblogs.com/blog/3478919/202506/3478919-20250615212549871-528678409.png)
posted @ 2025-06-15 21:26  YANGzLIN...11  阅读(11)  评论(0)    收藏  举报