<template>
|
<view>
|
<!-- 主物料弹窗 -->
|
<view v-if="dialogVisible" class="material-overlay">
|
<view class="material-container">
|
<view class="material-header">
|
<text class="material-title">物料</text>
|
<view class="close-btn" @click="dialogVisible = false">
|
<up-icon name="close" size="20" color="#666" />
|
</view>
|
</view>
|
<scroll-view class="material-body" scroll-y>
|
<view v-if="materialTableData.length === 0" class="empty-tip">
|
<text>暂无物料数据</text>
|
</view>
|
<view v-for="item in materialTableData" :key="item.id" class="material-card">
|
<view class="material-row">
|
<text class="mc-label">工序名称</text>
|
<text class="mc-value">{{ item.processName || '-' }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">原料名称</text>
|
<text class="mc-value">{{ item.materialName || '-' }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">原料型号</text>
|
<text class="mc-value">{{ item.materialModel || '-' }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">计量单位</text>
|
<text class="mc-value">{{ item.unit || '-' }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">线边仓数量</text>
|
<text class="mc-value">{{ item.pickQty || 0 }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">补料数量</text>
|
<text class="mc-value">{{ item.supplementQty || 0 }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">实际数量</text>
|
<view class="mc-value">
|
<up-input v-model="item.actualQty"
|
type="number"
|
placeholder="请输入实际数量"
|
clearable
|
style="width: 200rpx" />
|
</view>
|
</view>
|
<view class="material-actions">
|
<up-button size="small" type="primary" @click="openSupplementDialog(item)">补料</up-button>
|
<up-button size="small" type="info" @click="openSupplementRecordDialog(item)">补料记录</up-button>
|
</view>
|
</view>
|
</scroll-view>
|
<view class="material-footer">
|
<up-button type="primary" :loading="pickSubmitting" @click="handleSubmitPick">领用</up-button>
|
<up-button @click="dialogVisible = false">取消</up-button>
|
</view>
|
</view>
|
</view>
|
|
<!-- 补料弹窗 -->
|
<view v-if="supplementDialogVisible" class="material-overlay">
|
<view class="material-container supplement-container">
|
<view class="material-header">
|
<text class="material-title">补料</text>
|
<view class="close-btn" @click="supplementDialogVisible = false">
|
<up-icon name="close" size="20" color="#666" />
|
</view>
|
</view>
|
<view class="material-body">
|
<up-form :model="supplementForm" ref="supplementFormRef" label-width="140">
|
<up-form-item label="补料数量" prop="supplementQty" required>
|
<up-input v-model="supplementForm.supplementQty"
|
type="number"
|
placeholder="请输入补料数量"
|
clearable />
|
</up-form-item>
|
<up-form-item label="补料原因" prop="supplementReason" required>
|
<up-textarea v-model="supplementForm.supplementReason"
|
placeholder="请输入补料原因"
|
:maxlength="200"
|
autoHeight />
|
</up-form-item>
|
</up-form>
|
</view>
|
<view class="material-footer">
|
<up-button type="primary" :loading="supplementSubmitting" @click="handleSubmitSupplement">确定</up-button>
|
<up-button @click="supplementDialogVisible = false">取消</up-button>
|
</view>
|
</view>
|
</view>
|
|
<!-- 补料记录弹窗 -->
|
<view v-if="supplementRecordDialogVisible" class="material-overlay">
|
<view class="material-container supplement-record-container">
|
<view class="material-header">
|
<text class="material-title">补料记录</text>
|
<view class="close-btn" @click="supplementRecordDialogVisible = false">
|
<up-icon name="close" size="20" color="#666" />
|
</view>
|
</view>
|
<scroll-view class="material-body" scroll-y>
|
<view v-if="supplementRecordTableData.length === 0" class="empty-tip">
|
<text>暂无补料记录</text>
|
</view>
|
<view v-for="item in supplementRecordTableData" :key="item.id" class="record-card">
|
<view class="material-row">
|
<text class="mc-label">补料数量</text>
|
<text class="mc-value">{{ item.supplementQty }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">补料原因</text>
|
<text class="mc-value">{{ item.supplementReason }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">补料人</text>
|
<text class="mc-value">{{ item.supplementUserName }}</text>
|
</view>
|
<view class="material-row">
|
<text class="mc-label">补料日期</text>
|
<text class="mc-value">{{ item.supplementTime }}</text>
|
</view>
|
</view>
|
</scroll-view>
|
<view class="material-footer">
|
<up-button @click="supplementRecordDialogVisible = false">关闭</up-button>
|
</view>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script setup>
|
import { computed, nextTick, reactive, ref, watch } from "vue";
|
import {
|
listWorkOrderMaterialLedger,
|
addWorkOrderMaterialSupplement,
|
listWorkOrderMaterialSupplementRecord,
|
pickWorkOrderMaterial,
|
} from "@/api/productionManagement/workOrder.js";
|
|
const props = defineProps({
|
modelValue: {
|
type: Boolean,
|
default: false,
|
},
|
rowData: {
|
type: Object,
|
default: () => null,
|
},
|
});
|
|
const emit = defineEmits(["update:modelValue", "refresh"]);
|
|
const dialogVisible = computed({
|
get: () => props.modelValue,
|
set: val => emit("update:modelValue", val),
|
});
|
|
const materialTableLoading = ref(false);
|
const materialTableData = ref([]);
|
const currentMaterialRow = ref(null);
|
const currentMaterialOrderRow = ref(null);
|
const pickSubmitting = ref(false);
|
|
const supplementDialogVisible = ref(false);
|
const supplementSubmitting = ref(false);
|
const supplementFormRef = ref(null);
|
const supplementForm = reactive({
|
supplementQty: null,
|
supplementReason: "",
|
});
|
|
const supplementRecordDialogVisible = ref(false);
|
const supplementRecordLoading = ref(false);
|
const supplementRecordTableData = ref([]);
|
|
const loadMaterialTable = async row => {
|
if (!row?.id) return;
|
currentMaterialOrderRow.value = row;
|
materialTableLoading.value = true;
|
materialTableData.value = [];
|
try {
|
const res = await listWorkOrderMaterialLedger({
|
workOrderId: row.id,
|
processId: row.processId,
|
productProcessRouteItemId: row.productProcessRouteItemId,
|
});
|
materialTableData.value = res.data || [];
|
} catch (e) {
|
console.error("获取物料台账失败", e);
|
uni.showToast({ title: "获取物料台账失败", icon: "error" });
|
} finally {
|
materialTableLoading.value = false;
|
}
|
};
|
|
watch(
|
() => props.modelValue,
|
visible => {
|
if (visible && props.rowData) {
|
loadMaterialTable(props.rowData);
|
}
|
}
|
);
|
|
const resetSupplementForm = () => {
|
supplementForm.supplementQty = null;
|
supplementForm.supplementReason = "";
|
};
|
|
const openSupplementDialog = row => {
|
currentMaterialRow.value = row;
|
resetSupplementForm();
|
supplementDialogVisible.value = true;
|
};
|
|
const handleSubmitSupplement = () => {
|
if (!supplementForm.supplementQty) {
|
uni.showToast({ title: "请输入补料数量", icon: "none" });
|
return;
|
}
|
if (!supplementForm.supplementReason) {
|
uni.showToast({ title: "请输入补料原因", icon: "none" });
|
return;
|
}
|
if (!currentMaterialRow.value?.id) {
|
uni.showToast({ title: "缺少物料明细ID", icon: "none" });
|
return;
|
}
|
supplementSubmitting.value = true;
|
addWorkOrderMaterialSupplement({
|
materialLedgerId: currentMaterialRow.value.id,
|
supplementQty: Number(supplementForm.supplementQty),
|
supplementReason: supplementForm.supplementReason,
|
workOrderId: currentMaterialOrderRow.value?.id,
|
})
|
.then(async () => {
|
supplementDialogVisible.value = false;
|
await loadMaterialTable(currentMaterialOrderRow.value);
|
uni.showToast({ title: "补料成功" });
|
emit("refresh");
|
})
|
.catch(e => {
|
console.error("补料失败", e);
|
uni.showToast({ title: "补料失败", icon: "error" });
|
})
|
.finally(() => {
|
supplementSubmitting.value = false;
|
});
|
};
|
|
const openSupplementRecordDialog = async row => {
|
supplementRecordDialogVisible.value = true;
|
supplementRecordLoading.value = true;
|
supplementRecordTableData.value = [];
|
try {
|
const res = await listWorkOrderMaterialSupplementRecord({
|
materialLedgerId: row.id,
|
});
|
supplementRecordTableData.value = res.data || [];
|
} catch (e) {
|
console.error("获取补料记录失败", e);
|
uni.showToast({ title: "获取补料记录失败", icon: "error" });
|
} finally {
|
supplementRecordLoading.value = false;
|
}
|
};
|
|
const validatePickRows = () => {
|
if (materialTableData.value.length === 0) {
|
return { valid: false, message: "暂无可领用物料" };
|
}
|
const invalidRow = materialTableData.value.find(
|
item =>
|
item.actualQty === null ||
|
item.actualQty === undefined ||
|
item.actualQty === ""
|
);
|
if (invalidRow) {
|
return { valid: false, message: "请填写实际数量后再领用" };
|
}
|
const exceedRow = materialTableData.value.find(item => {
|
const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
|
return Number(item.actualQty || 0) > maxQty;
|
});
|
if (exceedRow) {
|
return { valid: false, message: "实际数量不能大于领用数量+补料数量" };
|
}
|
return { valid: true, message: "" };
|
};
|
|
const handleSubmitPick = async () => {
|
if (!currentMaterialOrderRow.value?.id) return;
|
const validateResult = validatePickRows();
|
if (!validateResult.valid) {
|
uni.showToast({ title: validateResult.message, icon: "none" });
|
return;
|
}
|
pickSubmitting.value = true;
|
try {
|
await pickWorkOrderMaterial({
|
workOrderId: currentMaterialOrderRow.value.id,
|
items: materialTableData.value.map(item => ({
|
materialLedgerId: item.id,
|
actualQty: Number(item.actualQty || 0),
|
})),
|
});
|
uni.showToast({ title: "领用成功" });
|
await loadMaterialTable(currentMaterialOrderRow.value);
|
emit("refresh");
|
} catch (e) {
|
console.error("领用失败", e);
|
uni.showToast({ title: "领用失败", icon: "error" });
|
} finally {
|
pickSubmitting.value = false;
|
}
|
};
|
</script>
|
|
<style scoped lang="scss">
|
.material-overlay {
|
position: fixed;
|
top: 0;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
background: rgba(0, 0, 0, 0.5);
|
z-index: 999;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.material-container {
|
width: 92%;
|
max-height: 85vh;
|
background: #fff;
|
border-radius: 16rpx;
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
}
|
|
.supplement-container {
|
max-height: 60vh;
|
}
|
|
.supplement-record-container {
|
max-height: 75vh;
|
}
|
|
.material-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 24rpx 32rpx;
|
border-bottom: 1px solid #f0f0f0;
|
flex-shrink: 0;
|
}
|
|
.material-title {
|
font-size: 32rpx;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.close-btn {
|
padding: 8rpx;
|
}
|
|
.material-body {
|
flex: 1;
|
padding: 16rpx 24rpx;
|
overflow-y: auto;
|
}
|
|
.empty-tip {
|
text-align: center;
|
padding: 60rpx 0;
|
color: #999;
|
font-size: 28rpx;
|
}
|
|
.material-card {
|
background: #f9fafb;
|
border-radius: 12rpx;
|
padding: 20rpx;
|
margin-bottom: 16rpx;
|
}
|
|
.material-row {
|
display: flex;
|
align-items: center;
|
padding: 10rpx 0;
|
}
|
|
.mc-label {
|
width: 160rpx;
|
font-size: 26rpx;
|
color: #909399;
|
flex-shrink: 0;
|
}
|
|
.mc-value {
|
flex: 1;
|
font-size: 26rpx;
|
color: #303133;
|
}
|
|
.material-actions {
|
display: flex;
|
gap: 16rpx;
|
margin-top: 16rpx;
|
padding-top: 16rpx;
|
border-top: 1px solid #ebeef5;
|
}
|
|
.record-card {
|
background: #f9fafb;
|
border-radius: 12rpx;
|
padding: 20rpx;
|
margin-bottom: 16rpx;
|
}
|
|
.material-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 16rpx;
|
padding: 20rpx 32rpx;
|
border-top: 1px solid #f0f0f0;
|
flex-shrink: 0;
|
}
|
</style>
|