| | |
| | | </el-row> |
| | | <div class="form-section-title">出库信息</div> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="出库类别" |
| | | prop="outCategory" |
| | | :rules="[{ required: true, message: '请选择出库类别', trigger: 'change' }]"> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="出库批次" |
| | | prop="batchNo" |
| | | :rules="isSalesOut |
| | | ? [{ required: true, message: '销售类出库必须选择出库批次', trigger: 'change' }] |
| | | : []"> |
| | | <el-select v-model="formState.batchNo" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | style="width: 100%"> |
| | | <el-option v-for="item in batchNoOptions" |
| | | :key="item" |
| | | :label="item" |
| | | :value="item" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="储库名称" |
| | | prop="oilDepotId" |
| | | :rules="[{ required: true, message: '请选择储库名称', trigger: 'change' }]"> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="油品" |
| | | prop="oilProduct" |
| | | :rules="isOilProduct ? [ |
| | | { |
| | | required: true, |
| | | message: '请选择油品', |
| | | trigger: 'change', |
| | | } |
| | | ] : []"> |
| | | <el-input v-if="isOilProduct" |
| | | :model-value="formState.oilProduct" |
| | | :rules="[{ required: true, message: '请选择油品', trigger: 'change' }]"> |
| | | <el-input :model-value="formState.oilProduct" |
| | | readonly |
| | | placeholder="--" /> |
| | | <el-select v-else |
| | | v-model="formState.oilProduct" |
| | | placeholder="请选择" |
| | | disabled |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <div class="form-section-title">物流信息</div> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车辆" |
| | | prop="vehicleId" |
| | | :rules="isSalesOut |
| | | ? [{ required: true, message: '销售类出库必须选择车辆', trigger: 'change' }] |
| | | : []"> |
| | | <el-select v-model="formState.vehicleId" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | popper-class="vehicle-select-popper" |
| | | style="width: 100%" |
| | | @change="vehicleChange"> |
| | | <el-option v-for="item in vehicleList" |
| | | :key="item.id" |
| | | :label="item.plateNumber" |
| | | :value="item.id"> |
| | | <div class="vehicle-option"> |
| | | <div class="vehicle-option-title">{{ item.plateNumber }}</div> |
| | | <div class="vehicle-option-line">默认司机:{{ item.driverName || '--' }}</div> |
| | | <div class="vehicle-option-line">司机电话:{{ item.driverPhone || '--' }}</div> |
| | | <div class="vehicle-option-line">核定载重(吨):{{ item.loadCapacity ?? '--' }}</div> |
| | | </div> |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车挂牌号" |
| | | prop="trailerPlateNo" |
| | | :rules="[{ required: true, message: '请输入车挂牌号', trigger: 'blur' }]"> |
| | | <el-input v-model="formState.trailerPlateNo" |
| | | :readonly="!!formState.vehicleId" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="司机姓名" |
| | | prop="driverName" |
| | | :rules="[{ required: true, message: '请输入司机姓名', trigger: 'blur' }]"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车牌号" |
| | | prop="truckPlateNo" |
| | | :rules="[{ required: true, message: '请输入车牌号', trigger: 'blur' }]"> |
| | | <el-input v-model="formState.truckPlateNo" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车挂牌号" |
| | | prop="trailerPlateNo" |
| | | :rules="[{ required: true, message: '请输入车挂牌号', trigger: 'blur' }]"> |
| | | <el-input v-model="formState.trailerPlateNo" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | <el-row :gutter="30" |
| | | v-if="isSalesOut"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="可用量"> |
| | | <div class="available-qty-tip"> |
| | | <span>上次剩余(吨):{{ availableQtyText.lastResidualOil }}</span> |
| | | <span>当前库存(吨):{{ availableQtyText.stockQuantity }}</span> |
| | | <span class="available-qty-strong">可用量(吨):{{ availableQtyText.availableQty }}</span> |
| | | <span class="available-qty-rule">可用量 = 上次剩余 + 当前库存</span> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance, onMounted } from "vue"; |
| | | import { ref, computed, watch, getCurrentInstance, onMounted } from "vue"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import ImageUpload from "@/components/AttachmentUpload/image/index.vue"; |
| | | import { addOilOutRecordOnly } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import { |
| | | addOilOutRecordOnly, |
| | | getStockInventoryBatchNoQty, |
| | | } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import { getOilOutAvailableQty } from "@/api/inventoryManagement/stockOut.js"; |
| | | import { allOilDepot } from "@/api/basicData/oilDepot.js"; |
| | | import { allTankInfo } from "@/api/basicData/tankInfo.js"; |
| | | import { allVehicle } from "@/api/basicData/vehicle.js"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | import { productTreeList } from "@/api/basicData/product.js"; |
| | | |
| | | const props = defineProps({ |
| | | visible: { |
| | |
| | | model: "", |
| | | unit: "", |
| | | outCategory: undefined, |
| | | batchNo: undefined, |
| | | oilDepotId: undefined, |
| | | oilDepotName: "", |
| | | tankId: undefined, |
| | |
| | | driverPhone: "", |
| | | escortName: "", |
| | | escortPhone: "", |
| | | vehicleId: undefined, |
| | | truckPlateNo: "", |
| | | trailerPlateNo: "", |
| | | }); |
| | |
| | | const oilDepotList = ref([]); |
| | | const tankList = ref([]); |
| | | const customerList = ref([]); |
| | | const oilProductOptions = ref([]); |
| | | const vehicleList = ref([]); |
| | | const showProductSelectDialog = ref(false); |
| | | // 出库批次候选取该规格的入库批次 |
| | | const batchNoOptions = ref([]); |
| | | // 销售类出库的可用量提示(上次剩余 + 当前库存) |
| | | const availableQty = ref(null); |
| | | |
| | | // 在产品树中查找"油品"分类节点 |
| | | const findOilNode = nodes => { |
| | | for (const node of nodes || []) { |
| | | if (node.productName === "油品") { |
| | | return node; |
| | | } |
| | | if (node.children && node.children.length) { |
| | | const found = findOilNode(node.children); |
| | | if (found) { |
| | | return found; |
| | | } |
| | | } |
| | | const fetchBatchNoOptions = () => { |
| | | batchNoOptions.value = []; |
| | | const productModelId = formState.value.productModelId; |
| | | if (!productModelId) { |
| | | return; |
| | | } |
| | | return null; |
| | | getStockInventoryBatchNoQty({ |
| | | current: 1, |
| | | size: 200, |
| | | productModelId, |
| | | }).then(res => { |
| | | const records = res?.data?.records || []; |
| | | batchNoOptions.value = [ |
| | | ...new Set(records.map(item => item.batchNo).filter(Boolean)), |
| | | ]; |
| | | }); |
| | | }; |
| | | |
| | | // 所选商品名称命中的油品选项(油品节点子项,来自产品维护模块) |
| | | const matchedOilOption = computed(() => |
| | | oilProductOptions.value.find( |
| | | item => item.productName === formState.value.productName |
| | | ) |
| | | ); |
| | | // 销售类出库必须给出库批次(后端新增时即校验,且审批通过会按批次扣库存) |
| | | const isSalesOut = computed(() => formState.value.outCategory === "销售"); |
| | | |
| | | // 只有选中油品字段选项里有的具体油品才启用油品字段;"油品"大类本身不启用 |
| | | const isOilProduct = computed(() => !!matchedOilOption.value); |
| | | // 可用量提示:车辆 + 规格(+批次)确定后向后端取「上次剩余 / 当前库存 / 可用量」 |
| | | const fetchAvailableQty = () => { |
| | | availableQty.value = null; |
| | | const { vehicleId, productModelId, batchNo } = formState.value; |
| | | if (!isSalesOut.value || !vehicleId || !productModelId) { |
| | | return; |
| | | } |
| | | getOilOutAvailableQty({ vehicleId, productModelId, batchNo }) |
| | | .then(res => { |
| | | availableQty.value = res?.data || null; |
| | | }) |
| | | .catch(() => { |
| | | availableQty.value = null; |
| | | }); |
| | | }; |
| | | |
| | | const availableQtyText = computed(() => ({ |
| | | lastResidualOil: availableQty.value?.lastResidualOil ?? "--", |
| | | stockQuantity: availableQty.value?.stockQuantity ?? "--", |
| | | availableQty: availableQty.value?.availableQty ?? "--", |
| | | })); |
| | | |
| | | watch( |
| | | () => [ |
| | | formState.value.outCategory, |
| | | formState.value.vehicleId, |
| | | formState.value.productModelId, |
| | | formState.value.batchNo, |
| | | ], |
| | | () => { |
| | | fetchAvailableQty(); |
| | | } |
| | | ); |
| | | |
| | | const isShow = computed({ |
| | | get() { |
| | |
| | | formState.value.model = product.model; |
| | | formState.value.productModelId = product.id; |
| | | formState.value.unit = product.unit; |
| | | // 油品选项与商品名称同步:命中油品选项则自动带出该油品,未命中则清空并禁用 |
| | | formState.value.oilProduct = matchedOilOption.value |
| | | ? product.productName |
| | | : undefined; |
| | | // 本租户「油品」就是产品名称(库存明细/出库台账的油品列绑的都是 productName), |
| | | // 直接取所选产品名,不再去产品树里比对,避免比对不上时油品栏空白 |
| | | formState.value.oilProduct = product.productName || undefined; |
| | | formState.value.batchNo = undefined; |
| | | fetchBatchNoOptions(); |
| | | showProductSelectDialog.value = false; |
| | | proxy.$refs["formRef"]?.validateField("productModelId"); |
| | | } |
| | |
| | | } |
| | | }; |
| | | |
| | | // 车辆选择改变:车牌号、车挂牌号与司机信息由车辆主数据带出,出库时司机可手动覆盖 |
| | | const vehicleChange = id => { |
| | | const vehicle = vehicleList.value.find(item => item.id === id); |
| | | if (vehicle) { |
| | | formState.value.truckPlateNo = vehicle.plateNumber || ""; |
| | | // 车挂牌号与车牌号是同一个牌,选了车辆就带出并置为只读,避免手填出两个不一致的值 |
| | | formState.value.trailerPlateNo = vehicle.plateNumber || ""; |
| | | formState.value.driverName = vehicle.driverName || ""; |
| | | formState.value.driverPhone = vehicle.driverPhone || ""; |
| | | } else { |
| | | formState.value.truckPlateNo = ""; |
| | | formState.value.trailerPlateNo = ""; |
| | | } |
| | | }; |
| | | |
| | | const handleSubmit = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | |
| | | allOilDepot().then(res => { |
| | | oilDepotList.value = res?.data || []; |
| | | }); |
| | | productTreeList().then(res => { |
| | | const oilNode = findOilNode(res); |
| | | oilProductOptions.value = oilNode?.children || []; |
| | | }); |
| | | allTankInfo().then(res => { |
| | | tankList.value = res?.data || []; |
| | | }); |
| | | listCustomer({ current: 1, size: 1000, type: 0 }).then(res => { |
| | | customerList.value = res?.data?.records || []; |
| | | }); |
| | | allVehicle().then(res => { |
| | | vehicleList.value = res?.data || []; |
| | | }); |
| | | }); |
| | | </script> |
| | |
| | | } |
| | | |
| | | .tank-option-line { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | line-height: 1.8; |
| | | } |
| | | } |
| | | |
| | | // 可用量提示:上次剩余 + 当前库存 = 可用量 |
| | | .available-qty-tip { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 6px 20px; |
| | | width: 100%; |
| | | padding: 6px 14px; |
| | | font-size: 13px; |
| | | color: #303133; |
| | | background: rgba(64, 158, 255, 0.08); |
| | | border-left: 3px solid #409eff; |
| | | border-radius: 3px; |
| | | |
| | | .available-qty-strong { |
| | | font-weight: 600; |
| | | color: #1d6fd8; |
| | | } |
| | | |
| | | .available-qty-rule { |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | // 车辆下拉选项:小卡片样式 |
| | | .vehicle-option { |
| | | padding: 8px 10px; |
| | | |
| | | .vehicle-option-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .vehicle-option-line { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | line-height: 1.8; |
| | |
| | | } |
| | | } |
| | | |
| | | // 车辆下拉面板(body 下,需全局样式) |
| | | .vehicle-select-popper { |
| | | .el-select-dropdown__item { |
| | | height: auto; |
| | | line-height: normal; |
| | | padding: 4px 0; |
| | | } |
| | | |
| | | .vehicle-option { |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 6px; |
| | | background-color: #fafbfc; |
| | | |
| | | &:hover { |
| | | border-color: #409eff; |
| | | background-color: #f0f7ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 客户名称下拉面板(body 下,需全局样式) |
| | | .customer-select-popper { |
| | | .el-select-dropdown__item { |