<template>
|
<view class="repair-add">
|
<!-- 使用通用页面头部组件 -->
|
<PageHeader :title="operationType === 'edit' ? '编辑报修' : '新增报修'"
|
@back="goBack" />
|
<!-- 表单内容 -->
|
<u-form @submit="sendForm"
|
ref="formRef"
|
:rules="formRules"
|
:model="form"
|
label-width="110">
|
<!-- 基本信息 -->
|
<u-cell-group title="基本信息">
|
<u-form-item label="设备名称"
|
prop="deviceLedgerId"
|
required
|
border-bottom
|
class="device-name-form-item">
|
<view class="device-picker-wrap">
|
<picker mode="selector"
|
class="device-picker-full"
|
:range="deviceOptions"
|
range-key="deviceName"
|
:value="deviceIndex"
|
@change="onDevicePickerChange">
|
<view class="picker-input-row">
|
<text class="picker-input-text"
|
:class="{ placeholder: !deviceNameText }">{{ deviceNameText || "请选择设备名称" }}</text>
|
<view class="picker-input-arrow"><u-icon name="arrow-right" /></view>
|
</view>
|
</picker>
|
</view>
|
</u-form-item>
|
<u-form-item label="规格型号"
|
prop="deviceModel"
|
border-bottom>
|
<u-input v-model="form.deviceModel"
|
placeholder="请输入规格型号"
|
clearable />
|
</u-form-item>
|
<u-form-item label="报修日期"
|
prop="repairTime"
|
required
|
border-bottom>
|
<u-input v-model="form.repairTime"
|
placeholder="请选择报修日期"
|
readonly
|
@click="showDatePicker"
|
clearable />
|
<template #right>
|
<u-icon name="arrow-right"
|
@click="showDatePicker"></u-icon>
|
</template>
|
</u-form-item>
|
<u-form-item label="报修状态"
|
prop="status"
|
required
|
border-bottom>
|
<u-input v-model="repairStatusText"
|
placeholder="请选择报修状态"
|
readonly
|
@click="openRepairStatusPicker"
|
clearable />
|
<template #right>
|
<u-icon name="arrow-right"
|
@click="openRepairStatusPicker"></u-icon>
|
</template>
|
</u-form-item>
|
<u-form-item label="报修人"
|
prop="repairName"
|
required
|
border-bottom>
|
<u-input v-model="form.repairName"
|
placeholder="请输入报修人"
|
clearable />
|
</u-form-item>
|
<u-form-item label="故障现象"
|
prop="remark"
|
required
|
border-bottom>
|
<u-textarea v-model="form.remark"
|
rows="3"
|
placeholder="请输入故障现象"
|
clearable
|
count
|
maxlength="200" />
|
</u-form-item>
|
<view class="simple-upload-area">
|
<view class="upload-buttons">
|
<u-button type="primary"
|
@click="chooseRepairMedia"
|
:loading="uploading"
|
:disabled="repairFileList.length >= uploadConfig.limit"
|
:customStyle="{ marginRight: '10px', flex: 1 }">
|
<u-icon name="camera"
|
size="18"
|
color="#fff"
|
style="margin-right: 5px;"></u-icon>
|
{{ uploading ? "上传中..." : "拍照" }}
|
</u-button>
|
</view>
|
<view v-if="uploading"
|
class="upload-progress">
|
<u-line-progress :percentage="uploadProgress"
|
:showText="true"
|
activeColor="#409eff"></u-line-progress>
|
</view>
|
<view v-if="repairFileList.length > 0"
|
class="file-list">
|
<view v-for="(file, index) in repairFileList"
|
:key="index"
|
class="file-item">
|
<view class="file-preview-container">
|
<view class="delete-btn"
|
@click="removeRepairFile(index)">
|
<u-icon name="close"
|
size="12"
|
color="#fff"></u-icon>
|
</view>
|
<image :src="file.url || file.tempFilePath || file.path || file.downloadUrl"
|
class="file-preview"
|
mode="aspectFill" />
|
</view>
|
<view class="file-info">
|
<text class="file-name">{{ file.bucketFilename || file.name || "图片" }}</text>
|
<text class="file-size">{{ formatFileSize(file.size) }}</text>
|
</view>
|
</view>
|
</view>
|
<view v-if="repairFileList.length === 0"
|
class="empty-state">
|
<text>请选择要上传的现场照片</text>
|
</view>
|
</view>
|
</u-cell-group>
|
<!-- 提交按钮 -->
|
<view class="footer-btns">
|
<u-button class="cancel-btn"
|
@click="goBack">取消</u-button>
|
<u-button class="save-btn"
|
type="primary"
|
@click="sendForm"
|
:loading="loading">保存</u-button>
|
</view>
|
</u-form>
|
<!-- 日期选择器 -->
|
<up-datetime-picker :show="showDate"
|
v-model="pickerDateValue"
|
@confirm="onDateConfirm"
|
@cancel="showDate = false"
|
mode="date" />
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, computed, onMounted } from "vue";
|
import { onShow } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
|
import {
|
addRepair,
|
editRepair,
|
getRepairById,
|
} from "@/api/equipmentManagement/repair";
|
import config from "@/config";
|
import { getToken } from "@/utils/auth";
|
import dayjs from "dayjs";
|
import { formatDateToYMD } from "@/utils/ruoyi";
|
const showToast = message => {
|
uni.showToast({
|
title: message,
|
icon: "none",
|
});
|
};
|
|
defineOptions({
|
name: "设备报修表单",
|
});
|
|
// 表单引用
|
const formRef = ref(null);
|
const operationType = ref("add");
|
const loading = ref(false);
|
const showDate = ref(false);
|
const pickerDateValue = ref(Date.now());
|
|
// 上传配置(与巡检一致)
|
const uploadConfig = {
|
action: "/file/upload",
|
limit: 10,
|
};
|
const uploadFileUrl = computed(() => config.baseUrl + uploadConfig.action);
|
const repairFileList = ref([]);
|
const uploading = ref(false);
|
const uploadProgress = ref(0);
|
|
// 设备选项
|
const deviceOptions = ref([]);
|
const deviceNameText = ref("");
|
const deviceIndex = computed(() => {
|
const id = form.value.deviceLedgerId;
|
if (id == null || !deviceOptions.value.length) return 0;
|
const idx = deviceOptions.value.findIndex(item => item.id === id || item.id == id);
|
return idx >= 0 ? idx : 0;
|
});
|
|
// 表单验证规则
|
const formRules = {
|
deviceLedgerId: [
|
{ required: true, trigger: "change", message: "请选择设备名称" },
|
],
|
repairTime: [
|
{ required: true, trigger: "change", message: "请选择报修日期" },
|
],
|
status: [
|
{ required: true, trigger: "change", message: "请选择报修状态" },
|
],
|
repairName: [{ required: true, trigger: "blur", message: "请输入报修人" }],
|
remark: [{ required: true, trigger: "blur", message: "请输入故障现象" }],
|
};
|
|
// 使用 ref 声明表单数据
|
const form = ref({
|
deviceLedgerId: undefined, // 设备ID
|
deviceModel: undefined, // 规格型号
|
repairTime: dayjs().format("YYYY-MM-DD"), // 报修日期
|
status: undefined, // 报修状态
|
repairName: undefined, // 报修人
|
remark: undefined, // 故障现象
|
});
|
|
// 报修状态选项
|
const repairStatusOptions = ref([
|
{ name: "待维修", value: "0" },
|
{ name: "完结", value: "1" },
|
{ name: "失败", value: "2" },
|
]);
|
const repairStatusText = ref("");
|
|
// 打开报修状态选择器
|
const openRepairStatusPicker = () => {
|
uni.showActionSheet({
|
itemList: repairStatusOptions.value.map(item => item.name),
|
success: res => {
|
form.value.status = repairStatusOptions.value[res.tapIndex].value;
|
repairStatusText.value = repairStatusOptions.value[res.tapIndex].name;
|
},
|
});
|
};
|
|
// 加载设备列表
|
const loadDeviceName = async () => {
|
try {
|
const { data } = await getDeviceLedger();
|
deviceOptions.value = data || [];
|
} catch (e) {
|
showToast("获取设备列表失败");
|
}
|
};
|
|
// 设置设备规格型号
|
const setDeviceModel = id => {
|
const option = deviceOptions.value.find(item => item.id === id);
|
if (option) {
|
form.value.deviceModel = option.deviceModel;
|
deviceNameText.value = option.deviceName;
|
}
|
};
|
|
// 加载表单数据(编辑模式)
|
const loadForm = async id => {
|
if (id) {
|
operationType.value = "edit";
|
try {
|
const { code, data } = await getRepairById(id);
|
if (code == 200) {
|
form.value.deviceLedgerId = data.deviceLedgerId;
|
form.value.deviceModel = data.deviceModel;
|
form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD");
|
form.value.status = data.status;
|
form.value.repairName = data.repairName;
|
form.value.remark = data.remark;
|
repairStatusText.value =
|
repairStatusOptions.value.find(item => item.value == data.status)
|
?.name || "";
|
// 设置设备名称显示
|
const device = deviceOptions.value.find(
|
item => item.id === data.deviceLedgerId
|
);
|
if (device) {
|
deviceNameText.value = device.deviceName;
|
}
|
// 回显附件列表(后端返回 fileList 时)
|
const list = data.fileList || data.commonFileList || [];
|
repairFileList.value = (Array.isArray(list) ? list : []).map(f => ({
|
url: f.url || f.downloadUrl,
|
name: f.bucketFilename || f.originalFilename || f.name,
|
bucketFilename: f.bucketFilename || f.originalFilename || f.name,
|
size: f.size || f.byteSize,
|
uploadResponse: f,
|
}));
|
}
|
} catch (e) {
|
showToast("获取详情失败");
|
}
|
} else {
|
// 新增模式
|
operationType.value = "add";
|
}
|
};
|
|
// 下拉框选择设备
|
const onDevicePickerChange = e => {
|
const index = Number(e.detail?.value ?? 0);
|
const item = deviceOptions.value[index];
|
if (item) {
|
form.value.deviceLedgerId = item.id;
|
setDeviceModel(item.id);
|
}
|
};
|
|
// 格式化文件大小
|
const formatFileSize = size => {
|
if (!size) return "";
|
if (size < 1024) return size + "B";
|
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB";
|
return (size / (1024 * 1024)).toFixed(1) + "MB";
|
};
|
|
// 拍照选择(与巡检一致)
|
const chooseRepairMedia = () => {
|
if (repairFileList.value.length >= uploadConfig.limit) {
|
showToast(`最多只能选择${uploadConfig.limit}个文件`);
|
return;
|
}
|
const remaining = uploadConfig.limit - repairFileList.value.length;
|
if (typeof uni.chooseMedia === "function") {
|
uni.chooseMedia({
|
count: Math.min(remaining, 9),
|
mediaType: ["image"],
|
sizeType: ["compressed", "original"],
|
sourceType: ["camera", "album"],
|
success: res => {
|
const files = res?.tempFiles || [];
|
files.forEach((tf, idx) => {
|
const filePath = tf.tempFilePath || tf.path || "";
|
const file = {
|
tempFilePath: filePath,
|
path: filePath,
|
type: "image",
|
name: `repair_${Date.now()}_${idx}.jpg`,
|
size: tf.size || 0,
|
uid: Date.now() + Math.random() + idx,
|
};
|
uploadRepairFile(file);
|
});
|
},
|
fail: () => showToast("选择图片失败"),
|
});
|
} else {
|
uni.chooseImage({
|
count: remaining,
|
sizeType: ["compressed", "original"],
|
sourceType: ["camera", "album"],
|
success: res => {
|
(res.tempFilePaths || []).forEach((path, idx) => {
|
uploadRepairFile({
|
tempFilePath: path,
|
path: path,
|
type: "image",
|
name: `repair_${Date.now()}_${idx}.jpg`,
|
size: 0,
|
uid: Date.now() + Math.random(),
|
});
|
});
|
},
|
fail: () => showToast("选择图片失败"),
|
});
|
}
|
};
|
|
const uploadRepairFile = file => {
|
const filePath = file.tempFilePath || file.path;
|
if (!filePath) return;
|
uploading.value = true;
|
uploadProgress.value = 0;
|
const token = getToken();
|
if (!token) {
|
showToast("请先登录");
|
uploading.value = false;
|
return;
|
}
|
const uploadTask = uni.uploadFile({
|
url: uploadFileUrl.value,
|
filePath,
|
name: "file",
|
header: { Authorization: "Bearer " + token },
|
success: res => {
|
try {
|
if (res.statusCode === 200) {
|
const response = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
|
const d = response?.data !== undefined ? response.data : response;
|
if (response && (response.code === 200 || response.code === 0) && d) {
|
const fileData = {
|
...file,
|
id: d.id,
|
tempId: d.tempId ?? d.tempFileId ?? d.id,
|
url: d.url || d.downloadUrl || filePath,
|
bucketFilename: d.bucketFilename || d.originalFilename || file.name,
|
downloadUrl: d.downloadUrl || d.url,
|
size: d.size ?? d.byteSize ?? file.size,
|
uploadResponse: response,
|
};
|
repairFileList.value.push(fileData);
|
} else {
|
showToast(response?.msg || "上传失败");
|
}
|
} else {
|
showToast("上传失败");
|
}
|
} catch (e) {
|
showToast("上传失败");
|
}
|
uploading.value = false;
|
uploadProgress.value = 0;
|
},
|
fail: () => {
|
showToast("上传失败");
|
uploading.value = false;
|
uploadProgress.value = 0;
|
},
|
});
|
if (uploadTask && uploadTask.onProgressUpdate) {
|
uploadTask.onProgressUpdate(e => {
|
uploadProgress.value = e.progress;
|
});
|
}
|
};
|
|
const removeRepairFile = index => {
|
repairFileList.value.splice(index, 1);
|
};
|
|
// 显示日期选择器
|
const showDatePicker = () => {
|
showDate.value = true;
|
};
|
|
// 确认日期选择
|
const onDateConfirm = e => {
|
form.value.repairTime = formatDateToYMD(e.value);
|
pickerDateValue.value = dayjs(e.value).format("YYYY-MM-DD");
|
showDate.value = false;
|
};
|
|
onShow(() => {
|
// 页面显示时获取参数
|
getPageParams();
|
});
|
|
onMounted(() => {
|
// 页面加载时获取设备列表和参数
|
loadDeviceName();
|
getPageParams();
|
});
|
|
// 组件卸载时清理定时器
|
|
// 提交表单
|
const sendForm = async () => {
|
try {
|
// 手动验证表单
|
let isValid = true;
|
let errorMessage = "";
|
if (!form.value.deviceLedgerId) {
|
isValid = false;
|
errorMessage = "请选择设备名称";
|
} else if (!form.value.repairTime || form.value.repairTime.trim() === "") {
|
isValid = false;
|
errorMessage = "请选择报修日期";
|
} else if (form.value.status === undefined || form.value.status === null || form.value.status === "") {
|
isValid = false;
|
errorMessage = "请选择报修状态";
|
} else if (!form.value.repairName || form.value.repairName.trim() === "") {
|
isValid = false;
|
errorMessage = "请输入报修人";
|
} else if (!form.value.remark || form.value.remark.trim() === "") {
|
isValid = false;
|
errorMessage = "请输入故障现象";
|
}
|
|
if (!isValid) {
|
showToast(errorMessage);
|
return;
|
}
|
|
loading.value = true;
|
const id = getPageId();
|
|
// 附件数组:上传接口返回的附件信息(与巡检一致传 fileList)
|
const fileList = repairFileList.value.map(f => {
|
const d = f.uploadResponse?.data !== undefined ? f.uploadResponse.data : f.uploadResponse;
|
return d ? { ...d } : null;
|
}).filter(Boolean);
|
const submitData = { ...form.value };
|
if (fileList.length) submitData.fileList = fileList;
|
|
const { code } = id
|
? await editRepair({ id: id, ...submitData })
|
: await addRepair(submitData);
|
|
if (code == 200) {
|
showToast(`${id ? "编辑" : "新增"}报修成功`);
|
setTimeout(() => {
|
uni.navigateBack();
|
}, 1500);
|
} else {
|
loading.value = false;
|
}
|
} catch (e) {
|
loading.value = false;
|
showToast("表单验证失败");
|
}
|
};
|
|
// 返回上一页
|
const goBack = () => {
|
uni.removeStorageSync("repairId");
|
uni.navigateBack();
|
};
|
|
// 获取页面参数
|
const getPageParams = () => {
|
// 使用uni.getStorageSync获取id
|
const id = uni.getStorageSync("repairId");
|
|
// 根据是否有id参数来判断是新增还是编辑
|
if (id) {
|
// 编辑模式,获取详情
|
loadForm(id);
|
// 可选:获取后清除存储的id,避免影响后续操作
|
uni.removeStorageSync("repairId");
|
} else {
|
// 新增模式
|
loadForm();
|
}
|
};
|
|
// 获取页面ID
|
const getPageId = () => {
|
// 使用uni.getStorageSync获取id
|
const id = uni.getStorageSync("repairId");
|
return id;
|
};
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/static/scss/form-common.scss";
|
.repair-add {
|
min-height: 100vh;
|
background: #f8f9fa;
|
padding-bottom: 5rem;
|
}
|
|
.footer-btns {
|
position: fixed;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: #fff;
|
display: flex;
|
justify-content: space-around;
|
align-items: center;
|
padding: 0.75rem 0;
|
box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05);
|
z-index: 1000;
|
}
|
|
.cancel-btn {
|
font-weight: 400;
|
font-size: 1rem;
|
color: #ffffff;
|
width: 6.375rem;
|
background: #c7c9cc;
|
box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
|
border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
|
}
|
|
.save-btn {
|
font-weight: 400;
|
font-size: 1rem;
|
color: #ffffff;
|
width: 14rem;
|
background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
|
box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2);
|
border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
|
}
|
|
// 响应式调整
|
@media (max-width: 768px) {
|
.submit-section {
|
padding: 12px;
|
}
|
}
|
|
.tip-text {
|
padding: 4px 16px 0 16px;
|
font-size: 12px;
|
color: #888;
|
}
|
|
:deep(.device-name-form-item .u-form-item__content) {
|
justify-content: flex-start !important;
|
}
|
|
.device-picker-wrap {
|
width: 100%;
|
flex: 1;
|
min-width: 0;
|
}
|
|
.device-picker-full {
|
display: block;
|
width: 100%;
|
}
|
|
.picker-input-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
width: 100%;
|
min-height: 36px;
|
padding: 0;
|
}
|
|
.picker-input-text {
|
flex: 1;
|
min-width: 0;
|
font-size: 15px;
|
color: #333;
|
line-height: 36px;
|
margin-right: 8px;
|
}
|
|
.picker-input-arrow {
|
flex-shrink: 0;
|
display: flex;
|
align-items: center;
|
}
|
|
.picker-input-text.placeholder {
|
color: #c0c4cc;
|
}
|
|
.simple-upload-area {
|
padding: 15px 20px;
|
}
|
|
.upload-buttons {
|
display: flex;
|
gap: 10px;
|
margin-bottom: 10px;
|
}
|
|
.upload-progress {
|
margin: 15px 0;
|
padding: 0 10px;
|
}
|
|
.file-list {
|
margin-top: 15px;
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12px;
|
}
|
|
.file-item {
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
background: #fff;
|
border-radius: 12px;
|
padding: 8px;
|
border: 1px solid #e9ecef;
|
width: calc(50% - 6px);
|
min-width: 120px;
|
}
|
|
.file-preview-container {
|
position: relative;
|
margin-bottom: 8px;
|
}
|
|
.file-preview {
|
width: 80px;
|
height: 80px;
|
border-radius: 8px;
|
object-fit: cover;
|
border: 2px solid #f0f0f0;
|
}
|
|
.delete-btn {
|
position: absolute;
|
top: -6px;
|
right: -6px;
|
width: 20px;
|
height: 20px;
|
background: #ff4757;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
z-index: 1;
|
}
|
|
.file-info {
|
text-align: center;
|
width: 100%;
|
}
|
|
.file-name {
|
font-size: 12px;
|
color: #333;
|
display: block;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
max-width: 100px;
|
}
|
|
.file-size {
|
font-size: 10px;
|
color: #999;
|
margin-top: 2px;
|
display: block;
|
}
|
|
.empty-state {
|
text-align: center;
|
padding: 24px 16px;
|
color: #999;
|
font-size: 14px;
|
background: #f8f9fa;
|
border-radius: 8px;
|
border: 2px dashed #ddd;
|
}
|
</style>
|