<template>
|
<view class="repair-maintain">
|
<!-- 使用通用页面头部组件 -->
|
<PageHeader title="新增维修"
|
@back="goBack" />
|
<!-- 表单内容 -->
|
<u-form ref="formRef"
|
:model="form"
|
:rules="formRules"
|
label-width="140rpx">
|
<!-- 基本信息 -->
|
<u-cell-group title="维修信息"
|
inset>
|
<u-form-item prop="maintenanceName"
|
label="报修人"
|
required>
|
<u-input v-model="form.maintenanceName"
|
placeholder="请输入报修人"
|
clearable />
|
</u-form-item>
|
<u-form-item prop="maintenanceResult"
|
label="维修结果"
|
required>
|
<u-input v-model="form.maintenanceResult"
|
type="textarea"
|
rows="3"
|
placeholder="请输入维修结果"
|
clearable
|
maxlength="200"
|
show-word-limit />
|
</u-form-item>
|
<u-form-item label="维修状态"
|
prop="repairTime"
|
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="maintenanceTime"
|
required
|
border-bottom>
|
<u-input v-model="form.maintenanceTime"
|
placeholder="请选择维修日期"
|
readonly
|
@click="showDatePicker = true"
|
clearable />
|
<template #right>
|
<u-icon name="arrow-right"
|
@click="showDatePicker = true"></u-icon>
|
</template>
|
</u-form-item>
|
<u-form-item label="设备备件"
|
prop="sparePartsIds"
|
border-bottom>
|
<view class="spare-parts-container"
|
@click="showSparePart = true">
|
<view v-if="selectedSpareParts.length > 0"
|
class="spare-parts-list">
|
<view v-for="(item, index) in selectedSpareParts"
|
:key="String(item.id)"
|
class="spare-part-tag">
|
<text>{{ item.name }}</text>
|
<u-icon name="close"
|
size="12"
|
color="#fff"
|
@click.stop="removeSparePart(index)" />
|
</view>
|
</view>
|
<text v-else
|
class="placeholder">请选择设备备件</text>
|
</view>
|
<template #right>
|
<u-icon name="arrow-right"
|
@click.stop="showSparePart = true"></u-icon>
|
</template>
|
</u-form-item>
|
<u-form-item v-if="selectedSpareParts.length"
|
label="领用数量"
|
border-bottom>
|
<view class="spare-qty-list">
|
<view v-for="item in selectedSpareParts"
|
:key="String(item.id)"
|
class="spare-qty-row">
|
<view class="spare-qty-name">
|
<text class="spare-name">{{ item.name }}</text>
|
<text v-if="item.quantity !== null && item.quantity !== undefined"
|
class="spare-stock">(库存:{{ item.quantity }})</text>
|
</view>
|
<up-number-box v-model="sparePartQtyMap[item.id]"
|
:min="1"
|
:max="item.quantity !== null && item.quantity !== undefined ? Number(item.quantity) : undefined" />
|
</view>
|
</view>
|
</u-form-item>
|
</u-cell-group>
|
<!-- 提交按钮 -->
|
<view class="footer-btns">
|
<u-button class="cancel-btn"
|
@click="goBack">取消</u-button>
|
<u-button class="save-btn"
|
@click="submitForm"
|
:loading="loading">保存</u-button>
|
</view>
|
</u-form>
|
<!-- 日期选择器 -->
|
<up-datetime-picker :show="showDatePicker"
|
v-model="pickerDateValue"
|
mode="datetime"
|
title="选择日期"
|
format="YYYY-MM-DD HH:mm:ss"
|
@confirm="onDateConfirm"
|
@cancel="showDatePicker = false" />
|
<!-- 设备备件选择器 -->
|
<up-popup :show="showSparePart"
|
mode="bottom"
|
:closeable="true"
|
@close="showSparePart = false">
|
<view class="spare-part-popup">
|
<view class="popup-header">
|
<text class="popup-title">选择设备备件</text>
|
</view>
|
<view class="spare-part-options">
|
<view v-for="(item, index) in sparePartOptions"
|
:key="index"
|
class="spare-part-option"
|
:class="{ selected: isSparePartSelected(item.id) }"
|
@click="toggleSparePartSelection(item)">
|
<text class="spare-part-option-text">{{ item.name }}</text>
|
<u-icon v-if="isSparePartSelected(item.id)"
|
name="checkmark"
|
color="#2c7be5" />
|
</view>
|
</view>
|
<up-button type="primary"
|
size="small"
|
:customStyle="{ borderRadius: '6px', padding: '4px 12px' }"
|
@click="confirmSparePartSelection">确定</up-button>
|
</view>
|
</up-popup>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, onMounted, reactive, watch } from "vue";
|
import { onShow } from "@dcloudio/uni-app";
|
import PageHeader from "@/components/PageHeader.vue";
|
import { addMaintain } from "@/api/equipmentManagement/repair";
|
import { getSparePartsList } from "@/api/equipmentManagement/repair";
|
import useUserStore from "@/store/modules/user";
|
import dayjs from "dayjs";
|
|
defineOptions({
|
name: "设备维修表单",
|
});
|
|
const userStore = useUserStore();
|
|
// 表单引用
|
const formRef = ref(null);
|
const loading = ref(false);
|
const showDatePicker = ref(false);
|
const pickerDateValue = ref(Date.now()); // 使用时间戳
|
const showSparePart = ref(false);
|
const sparePartOptions = ref([]);
|
const selectedSpareParts = ref([]);
|
const tempSelectedSpareParts = ref([]);
|
const sparePartQtyMap = reactive({});
|
|
// 表单验证规则
|
const formRules = {
|
maintenanceName: [
|
{ required: true, trigger: "blur", message: "请输入报修人" },
|
],
|
maintenanceResult: [
|
{ required: true, trigger: "blur", message: "请输入维修结果" },
|
],
|
maintenanceTime: [
|
{ required: true, trigger: "change", message: "请选择维修日期" },
|
],
|
};
|
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;
|
},
|
});
|
};
|
// 使用 ref 声明表单数据
|
const form = ref({
|
maintenanceName: userStore.nickName || "", // 默认使用当前用户昵称
|
maintenanceResult: undefined, // 维修结果
|
maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // 维修日期(只显示日期)
|
status: "1",
|
sparePartsIds: [],
|
});
|
|
// 自定义showToast函数
|
const showToast = message => {
|
uni.showToast({
|
title: message,
|
icon: "none",
|
});
|
};
|
|
// 重置表单数据和校验状态
|
const resetForm = () => {
|
form.value = {
|
maintenanceName: userStore.nickName || "",
|
maintenanceResult: undefined,
|
maintenanceTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
status: "1",
|
sparePartsIds: [],
|
};
|
selectedSpareParts.value = [];
|
tempSelectedSpareParts.value = [];
|
Object.keys(sparePartQtyMap).forEach(k => delete sparePartQtyMap[k]);
|
};
|
|
const resetFormAndValidate = () => {
|
resetForm();
|
};
|
|
// 提交表单
|
const submitForm = async () => {
|
try {
|
// 使用uview-plus的表单验证方式
|
const valid = await formRef.value.validate();
|
if (valid) {
|
submitFormData();
|
}
|
} catch (e) {
|
showToast("表单验证失败");
|
}
|
};
|
|
// 提交表单数据
|
const submitFormData = async () => {
|
try {
|
loading.value = true;
|
const id = getPageId();
|
|
if (!id) {
|
showToast("参数错误");
|
loading.value = false;
|
return;
|
}
|
form.value.status = Number(form.value.status);
|
// 领用数量校验
|
if (
|
Array.isArray(form.value.sparePartsIds) &&
|
form.value.sparePartsIds.length > 0
|
) {
|
for (const partId of form.value.sparePartsIds) {
|
const qty = Number(sparePartQtyMap?.[partId]);
|
if (!Number.isFinite(qty) || qty <= 0) {
|
showToast("请填写备件领用数量");
|
loading.value = false;
|
return;
|
}
|
const part = sparePartOptions.value.find(
|
p => String(p.id) === String(partId)
|
);
|
const stock = part?.quantity;
|
if (
|
stock !== null &&
|
stock !== undefined &&
|
Number.isFinite(Number(stock))
|
) {
|
if (qty > Number(stock)) {
|
showToast(
|
`备件「${part?.name || ""}」领用数量不能超过库存(${stock})`
|
);
|
loading.value = false;
|
return;
|
}
|
}
|
}
|
}
|
|
const spareIds = Array.isArray(form.value.sparePartsIds)
|
? form.value.sparePartsIds
|
: [];
|
const submitData = {
|
...form.value,
|
sparePartsIds: spareIds.length ? spareIds.join(",") : "",
|
sparePartsQty: spareIds.length
|
? spareIds.map(pid => sparePartQtyMap?.[pid] ?? 1).join(",")
|
: "",
|
sparePartsUseList: spareIds.length
|
? spareIds.map(pid => ({
|
id: pid,
|
quantity: sparePartQtyMap?.[pid] ?? 1,
|
}))
|
: [],
|
};
|
|
const { code } = await addMaintain({ id: id, ...submitData });
|
|
if (code == 200) {
|
showToast("新增维修成功");
|
resetFormAndValidate();
|
setTimeout(() => {
|
goBack();
|
}, 500);
|
} else {
|
loading.value = false;
|
}
|
} catch (e) {
|
console.log(e);
|
|
loading.value = false;
|
showToast("操作失败");
|
}
|
};
|
|
// 返回上一页
|
const goBack = () => {
|
uni.removeStorageSync("repairId");
|
uni.navigateBack();
|
};
|
|
// 获取页面ID
|
const getPageId = () => {
|
const id = uni.getStorageSync("repairId");
|
return id;
|
};
|
|
// 确认日期选择
|
const onDateConfirm = e => {
|
form.value.maintenanceTime = dayjs(e.value).format("YYYY-MM-DD HH:mm:ss");
|
pickerDateValue.value = e.value;
|
showDatePicker.value = false;
|
};
|
|
const fetchSparePartOptions = async () => {
|
try {
|
const res = await getSparePartsList({ current: 1, size: 1000 });
|
if (res?.code === 200) {
|
sparePartOptions.value = res?.data?.records || [];
|
} else {
|
sparePartOptions.value = [];
|
}
|
} catch (e) {
|
sparePartOptions.value = [];
|
}
|
};
|
|
const isSparePartSelected = id => {
|
return tempSelectedSpareParts.value.some(p => String(p.id) === String(id));
|
};
|
|
const toggleSparePartSelection = item => {
|
const idx = tempSelectedSpareParts.value.findIndex(
|
p => String(p.id) === String(item.id)
|
);
|
if (idx >= 0) {
|
tempSelectedSpareParts.value.splice(idx, 1);
|
delete sparePartQtyMap[item.id];
|
} else {
|
tempSelectedSpareParts.value.push({
|
id: item.id,
|
name: item.name,
|
quantity: item.quantity,
|
});
|
if (!Number.isFinite(Number(sparePartQtyMap[item.id]))) {
|
sparePartQtyMap[item.id] = 1;
|
}
|
}
|
};
|
|
const confirmSparePartSelection = () => {
|
selectedSpareParts.value = [...tempSelectedSpareParts.value];
|
form.value.sparePartsIds = selectedSpareParts.value.map(i => i.id);
|
// 保底给未填的数量赋值
|
selectedSpareParts.value.forEach(p => {
|
if (
|
!Number.isFinite(Number(sparePartQtyMap[p.id])) ||
|
Number(sparePartQtyMap[p.id]) <= 0
|
) {
|
sparePartQtyMap[p.id] = 1;
|
}
|
});
|
showSparePart.value = false;
|
};
|
|
const removeSparePart = index => {
|
const removed = selectedSpareParts.value.splice(index, 1)[0];
|
tempSelectedSpareParts.value = [...selectedSpareParts.value];
|
form.value.sparePartsIds = selectedSpareParts.value.map(i => i.id);
|
if (removed?.id !== null && removed?.id !== undefined) {
|
delete sparePartQtyMap[removed.id];
|
}
|
};
|
|
// 初始化表单数据
|
const initForm = async () => {
|
form.value.status = "1";
|
// 设置报修人为当前用户昵称
|
form.value.maintenanceName = userStore.nickName || "";
|
// 设置当前日期(只包含年月日)
|
form.value.maintenanceTime = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
|
// 拉取备件列表(对齐 PC:/spareParts/listPage)
|
await fetchSparePartOptions();
|
};
|
|
onShow(() => {
|
// 页面显示时初始化表单
|
initForm();
|
});
|
|
onMounted(() => {
|
// 页面加载时初始化表单
|
initForm();
|
});
|
|
watch(showSparePart, val => {
|
if (val) {
|
tempSelectedSpareParts.value = [...selectedSpareParts.value];
|
tempSelectedSpareParts.value.forEach(p => {
|
if (
|
!Number.isFinite(Number(sparePartQtyMap[p.id])) ||
|
Number(sparePartQtyMap[p.id]) <= 0
|
) {
|
sparePartQtyMap[p.id] = 1;
|
}
|
});
|
|
// 兜底:如果还没加载备件列表,打开弹窗时再拉一次
|
if (
|
!Array.isArray(sparePartOptions.value) ||
|
sparePartOptions.value.length === 0
|
) {
|
fetchSparePartOptions().catch(() => {});
|
}
|
}
|
});
|
</script>
|
|
<style scoped lang="scss">
|
@import "@/static/scss/form-common.scss";
|
.repair-maintain {
|
min-height: 100vh;
|
background: #f8f9fa;
|
padding-bottom: 5rem;
|
}
|
|
.spare-parts-container {
|
width: 100%;
|
min-height: 72rpx;
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
gap: 12rpx;
|
}
|
|
.spare-parts-list {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 12rpx;
|
}
|
|
.spare-part-tag {
|
display: inline-flex;
|
align-items: center;
|
gap: 8rpx;
|
padding: 10rpx 14rpx;
|
background: #2c7be5;
|
border-radius: 999rpx;
|
color: #fff;
|
font-size: 24rpx;
|
}
|
|
.placeholder {
|
color: #c0c4cc;
|
font-size: 28rpx;
|
}
|
|
.spare-qty-list {
|
width: 100%;
|
display: flex;
|
flex-direction: column;
|
gap: 18rpx;
|
padding: 6rpx 0;
|
}
|
|
.spare-qty-row {
|
width: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
gap: 18rpx;
|
}
|
|
.spare-qty-name {
|
flex: 1;
|
min-width: 0;
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
gap: 8rpx;
|
}
|
|
.spare-name {
|
max-width: 420rpx;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
color: #303133;
|
font-size: 28rpx;
|
}
|
|
.spare-stock {
|
color: #909399;
|
font-size: 24rpx;
|
}
|
|
.spare-part-popup {
|
padding: 24rpx;
|
}
|
|
.popup-header {
|
padding-bottom: 12rpx;
|
}
|
|
.popup-title {
|
font-size: 32rpx;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.spare-part-options {
|
max-height: 60vh;
|
overflow: auto;
|
margin: 16rpx 0 24rpx 0;
|
border-radius: 12rpx;
|
background: #fff;
|
}
|
|
.spare-part-option {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 22rpx 18rpx;
|
border-bottom: 1rpx solid #f2f3f5;
|
}
|
|
.spare-part-option.selected {
|
background: #f3f8ff;
|
}
|
|
.spare-part-option-text {
|
color: #303133;
|
font-size: 28rpx;
|
}
|
|
.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;
|
}
|
</style>
|