src/views/inventoryManagement/stockManagement/OilStockOutDialog.vue
@@ -44,7 +44,7 @@
        </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' }]">
@@ -61,7 +61,25 @@
              </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' }]">
@@ -130,22 +148,10 @@
          <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">
@@ -317,6 +323,45 @@
        <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' }]">
@@ -365,23 +410,16 @@
            </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>
@@ -402,14 +440,18 @@
</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: {
@@ -427,6 +469,7 @@
    model: "",
    unit: "",
    outCategory: undefined,
    batchNo: undefined,
    oilDepotId: undefined,
    oilDepotName: "",
    tankId: undefined,
@@ -448,6 +491,7 @@
    driverPhone: "",
    escortName: "",
    escortPhone: "",
    vehicleId: undefined,
    truckPlateNo: "",
    trailerPlateNo: "",
  });
@@ -457,34 +501,67 @@
  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() {
@@ -549,10 +626,11 @@
      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");
    }
@@ -586,6 +664,21 @@
    }
  };
  // 车辆选择改变:车牌号、车挂牌号与司机信息由车辆主数据带出,出库时司机可手动覆盖
  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) {
@@ -615,15 +708,14 @@
    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>
@@ -671,6 +763,48 @@
    }
    .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;
@@ -737,6 +871,26 @@
    }
  }
  // 车辆下拉面板(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 {