团队冲刺(补交)之报废审核
后端
实体类
点击查看代码
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;
}
}
点击查看代码
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());
}
}
点击查看代码
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);
}
点击查看代码
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>