2026-08-29 d5048a33e4760d414115a5e25645d30b41d24198
fix(wls): 修复多个组件中的类型安全和数据验证问题

- 在an-select-dialog.vue中添加非空断言操作符确保类型安全
- 重构batch-feedback.vue实现工序配置的动态数量验证逻辑
- 修复approval/config/data.ts中布尔值转换问题避免类型错误
- 修正productsales/data.ts中的销售订单代码字段映射
- 优化returnsales/form.vue中销售订单查询的数据处理逻辑
- 在approval/config/index.vue中使用unref解决响应式数据引用问题
- 简化returnsales/line-form.vue中批记录获取的条件判断逻辑
- 添加productreceipt/line-list.vue中入库明细数量一致性验证
- 修复po-select-dialog.vue中采购订单选择的类型安全问题
- 重构process-config-form.vue中的业务类型变更处理逻辑
- 修正stock-adjust-modal.vue中表单字段依赖关系配置并更新模态框标题设置
已修改11个文件
185 ■■■■■ 文件已修改
src/views/mes/pro/feedback/modules/batch-feedback.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/approval/config/data.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/approval/config/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/approval/config/modules/process-config-form.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/modules/po-select-dialog.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/an-select-dialog.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/stock-adjust-modal.vue 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/modules/line-list.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/form.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/line-form.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/modules/batch-feedback.vue
@@ -1,13 +1,14 @@
<script lang="ts" setup>
import type { MesProTaskApi } from '#/api/mes/pro/task';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Button, message, Tag } from 'ant-design-vue';
import { batchCreateFeedback } from '#/api/mes/pro/feedback';
import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process';
import ProTaskSelectDialog from '#/views/mes/pro/task/components/select-dialog.vue';
defineOptions({ name: 'BatchFeedbackForm' });
@@ -32,22 +33,49 @@
const taskDialogRef = ref<InstanceType<typeof ProTaskSelectDialog>>();
const selectedTasks = ref<MesProTaskApi.Task[]>([]);
const needReportQuantityMap = ref<Record<number, boolean>>({});
const formData = ref({
  feedbackQuantity: undefined as number | undefined,
  remark: '',
});
const submitting = ref(false);
/** 是否需要填写报工数量(任一选中任务工序要求填写数量即必填) */
const needQuantity = computed(() =>
  selectedTasks.value.some(
    (t) => needReportQuantityMap.value[t.id!] ?? true,
  ),
);
function handleOpenTaskDialog() {
  taskDialogRef.value?.open([], { multiple: true });
}
function handleTaskSelected(rows: MesProTaskApi.Task[]) {
/** 按选中任务工序加载是否需要报工数量 */
async function handleTaskSelected(rows: MesProTaskApi.Task[]) {
  selectedTasks.value = rows;
  needReportQuantityMap.value = {};
  for (const task of rows) {
    if (!task.routeId || !task.processId) {
      continue;
    }
    try {
      const routeProcess = await getRouteProcessByRouteAndProcess(
        task.routeId,
        task.processId,
      );
      needReportQuantityMap.value[task.id!] =
        routeProcess?.needReportQuantity ?? true;
    } catch {
      // 加载失败按默认处理(数量必填)
      needReportQuantityMap.value[task.id!] = true;
    }
  }
}
function handleRemoveTask(taskId: number) {
  selectedTasks.value = selectedTasks.value.filter((t) => t.id !== taskId);
  delete needReportQuantityMap.value[taskId];
}
async function handleSubmit() {
@@ -55,7 +83,7 @@
    message.warning('请选择任务');
    return;
  }
  if (!formData.value.feedbackQuantity) {
  if (needQuantity.value && !formData.value.feedbackQuantity) {
    message.warning('请输入报工数量');
    return;
  }
@@ -63,7 +91,9 @@
  try {
    const result = await batchCreateFeedback({
      taskIds: selectedTasks.value.map((t) => t.id!),
      feedbackQuantity: formData.value.feedbackQuantity,
      feedbackQuantity: needQuantity.value
        ? (formData.value.feedbackQuantity ?? 0)
        : 0,
      remark: formData.value.remark || undefined,
    });
    if (result.failCount) {
@@ -101,7 +131,12 @@
          </Tag>
        </div>
      </a-form-item>
      <a-form-item label="报工数量" required>
      <a-form-item
        v-if="needQuantity"
        label="报工数量"
        required
        tooltip="工序配置为不需填写报工数量时,合格品/不良品数量由质检确定"
      >
        <a-input-number
          v-model:value="formData.feedbackQuantity"
          :min="0.01"
src/views/wls/approval/config/data.ts
@@ -62,8 +62,8 @@
            checked: config?.approvalEnabled ?? false,
            checkedChildren: '开',
            unCheckedChildren: '关',
            onChange: (checked: boolean) => {
              handleChange(row, 'INBOUND', checked);
            onChange: (checked) => {
              handleChange(row, 'INBOUND', Boolean(checked));
            },
          });
        },
@@ -82,8 +82,8 @@
            checked: config?.approvalEnabled ?? false,
            checkedChildren: '开',
            unCheckedChildren: '关',
            onChange: (checked: boolean) => {
              handleChange(row, 'OUTBOUND', checked);
            onChange: (checked) => {
              handleChange(row, 'OUTBOUND', Boolean(checked));
            },
          });
        },
@@ -102,8 +102,8 @@
            checked: config?.approvalEnabled ?? false,
            checkedChildren: '开',
            unCheckedChildren: '关',
            onChange: (checked: boolean) => {
              handleChange(row, 'TRANSFER', checked);
            onChange: (checked) => {
              handleChange(row, 'TRANSFER', Boolean(checked));
            },
          });
        },
@@ -122,8 +122,8 @@
            checked: config?.approvalEnabled ?? false,
            checkedChildren: '开',
            unCheckedChildren: '关',
            onChange: (checked: boolean) => {
              handleChange(row, 'CHECK', checked);
            onChange: (checked) => {
              handleChange(row, 'CHECK', Boolean(checked));
            },
          });
        },
@@ -173,7 +173,9 @@
}
/** 流程配置表单 */
export function useProcessFormSchema(): VbenFormSchema[] {
export function useProcessFormSchema(
  onBizTypeChange?: (value?: string) => void,
): VbenFormSchema[] {
  return [
    {
      fieldName: 'warehouseId',
@@ -190,6 +192,7 @@
      componentProps: {
        options: BIZ_TYPE_OPTIONS,
        placeholder: '请选择业务类型',
        onChange: onBizTypeChange,
      },
      rules: 'required',
    },
src/views/wls/approval/config/index.vue
@@ -2,7 +2,7 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { WmsApprovalApi } from '#/api/mes/wm/approval';
import { ref } from 'vue';
import { ref, unref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
@@ -69,7 +69,7 @@
    columns: useGridColumns(handleApprovalChange),
    height: 'auto',
    keepSource: true,
    data: warehouseConfigList,
    data: unref(warehouseConfigList),
    proxyConfig: {
      ajax: {
        query: async ({ page }, formValues) => {
src/views/wls/approval/config/modules/process-config-form.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { WmsApprovalApi } from '#/api/mes/wm/approval';
import { computed, ref, watch } from 'vue';
import { ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
@@ -25,7 +25,9 @@
    },
  },
  layout: 'horizontal',
  schema: useProcessFormSchema(),
  schema: useProcessFormSchema((value) => {
    currentBizType.value = value;
  }),
  showDefaultActions: false,
});
@@ -53,6 +55,7 @@
  },
  onOpenChange(isOpen) {
    if (isOpen) {
      currentBizType.value = undefined;
      const data = modalApi.getData() as {
        warehouseId: number;
        warehouseName: string;
@@ -69,11 +72,8 @@
  },
});
/** 当前选中的业务类型 */
const currentBizType = computed(() => {
  const values = formApi.getValues();
  return values.bizType;
});
/** 当前选中的业务类型(formApi.getValues 为异步,改由字段 onChange 同步) */
const currentBizType = ref<string>();
/** 监听业务类型变化,加载对应的流程列表 */
watch(currentBizType, async (bizType) => {
src/views/wls/arrivalnotice/modules/po-select-dialog.vue
@@ -105,7 +105,7 @@
      message.warning('请选择采购订单');
      return;
    }
    emit('selected', selectedRows.id);
    emit('selected', selectedRows.id!);
    await modalApi.close();
  },
  async onOpened() {
src/views/wls/itemreceipt/modules/an-select-dialog.vue
@@ -111,7 +111,7 @@
      message.warning('请选择到货通知单');
      return;
    }
    emit('selected', selectedRows.id);
    emit('selected', selectedRows.id!);
    await modalApi.close();
  },
  async onOpenChange(isOpen: boolean) {
src/views/wls/materialstock/components/stock-adjust-modal.vue
@@ -55,15 +55,15 @@
  if (!enabled) {
    formApi.updateSchema([
      { fieldName: 'batchCode', dependencies: { show: () => false } },
      { fieldName: 'manualBatchEntry', dependencies: { show: () => false } },
      { fieldName: 'produceDate', dependencies: { show: () => false } },
      { fieldName: 'expireDate', dependencies: { show: () => false } },
      { fieldName: 'receiptDate', dependencies: { show: () => false } },
      { fieldName: 'vendorId', dependencies: { show: () => true } },
      { fieldName: 'purchaseOrderCode', dependencies: { show: () => false } },
      { fieldName: 'lotNumber', dependencies: { show: () => false } },
      { fieldName: 'qualityStatus', dependencies: { show: () => false } },
      { fieldName: 'batchCode', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'manualBatchEntry', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'produceDate', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'expireDate', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'receiptDate', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'vendorId', dependencies: { show: () => true, triggerFields: [''] } },
      { fieldName: 'purchaseOrderCode', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'lotNumber', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'qualityStatus', dependencies: { show: () => false, triggerFields: [''] } },
    ]);
    return;
  }
@@ -72,15 +72,15 @@
  const isIn = operateType.value === 1;
  formApi.updateSchema([
    { fieldName: 'batchCode', dependencies: { show: () => manualBatchEntry.value } },
    { fieldName: 'manualBatchEntry', dependencies: { show: () => true } },
    { fieldName: 'produceDate', dependencies: { show: () => isIn && !!item?.produceDateFlag } },
    { fieldName: 'expireDate', dependencies: { show: () => isIn && !!item?.expireDateFlag } },
    { fieldName: 'receiptDate', dependencies: { show: () => isIn && !!item?.receiptDateFlag } },
    { fieldName: 'vendorId', dependencies: { show: () => !isIn || !!item?.vendorFlag } },
    { fieldName: 'purchaseOrderCode', dependencies: { show: () => isIn && !!item?.purchaseOrderCodeFlag } },
    { fieldName: 'lotNumber', dependencies: { show: () => isIn && !!item?.lotNumberFlag } },
    { fieldName: 'qualityStatus', dependencies: { show: () => isIn && !!item?.qualityStatusFlag } },
    { fieldName: 'batchCode', dependencies: { show: () => manualBatchEntry.value, triggerFields: [''] } },
    { fieldName: 'manualBatchEntry', dependencies: { show: () => true, triggerFields: [''] } },
    { fieldName: 'produceDate', dependencies: { show: () => isIn && !!item?.produceDateFlag, triggerFields: [''] } },
    { fieldName: 'expireDate', dependencies: { show: () => isIn && !!item?.expireDateFlag, triggerFields: [''] } },
    { fieldName: 'receiptDate', dependencies: { show: () => isIn && !!item?.receiptDateFlag, triggerFields: [''] } },
    { fieldName: 'vendorId', dependencies: { show: () => !isIn || !!item?.vendorFlag, triggerFields: [''] } },
    { fieldName: 'purchaseOrderCode', dependencies: { show: () => isIn && !!item?.purchaseOrderCodeFlag, triggerFields: [''] } },
    { fieldName: 'lotNumber', dependencies: { show: () => isIn && !!item?.lotNumberFlag, triggerFields: [''] } },
    { fieldName: 'qualityStatus', dependencies: { show: () => isIn && !!item?.qualityStatusFlag, triggerFields: [''] } },
  ]);
}
@@ -127,6 +127,7 @@
async function handleOperateTypeChange(type: 1 | 2) {
  operateType.value = type;
  modalTitle.value = type === 1 ? '库存入库' : '库存出库';
  modalApi.setState({ title: modalTitle.value });
  await formApi.setFieldValue('operateType', type);
  // 更新批次字段显示
  await updateBatchFields();
@@ -403,20 +404,21 @@
      batchEnabled.value = false;
      operateType.value = 1;
      modalTitle.value = '库存调整';
      modalApi.setState({ title: modalTitle.value });
      return;
    }
    // 重置表单
    await formApi.resetForm();
    // 默认隐藏批次相关字段
    formApi.updateSchema([
      { fieldName: 'batchCode', dependencies: { show: () => false } },
      { fieldName: 'manualBatchEntry', dependencies: { show: () => false } },
      { fieldName: 'produceDate', dependencies: { show: () => false } },
      { fieldName: 'expireDate', dependencies: { show: () => false } },
      { fieldName: 'receiptDate', dependencies: { show: () => false } },
      { fieldName: 'purchaseOrderCode', dependencies: { show: () => false } },
      { fieldName: 'lotNumber', dependencies: { show: () => false } },
      { fieldName: 'qualityStatus', dependencies: { show: () => false } },
      { fieldName: 'batchCode', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'manualBatchEntry', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'produceDate', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'expireDate', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'receiptDate', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'purchaseOrderCode', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'lotNumber', dependencies: { show: () => false, triggerFields: [''] } },
      { fieldName: 'qualityStatus', dependencies: { show: () => false, triggerFields: [''] } },
    ]);
    // 重置手动填写批次号状态
    manualBatchEntry.value = false;
@@ -436,7 +438,7 @@
      }
    }
  },
  title: modalTitle,
  title: '库存调整',
});
/** 提交表单 */
src/views/wls/productreceipt/modules/line-list.vue
@@ -153,6 +153,22 @@
          details = await getProductReceiptDetailListByLineId(line.id!);
          detailMap[line.id!] = details;
        }
        // 勾选入库的行必须已有上架明细,且明细数量之和等于行收货数量
        if (!details.length) {
          message.error(`物料「${line.itemName}」已勾选入库,请先添加上架明细`);
          return;
        }
        const lineQty = Number(line.quantity ?? 0);
        const sumQty = details.reduce(
          (acc, detail) => acc + Number(detail.quantity ?? 0),
          0,
        );
        if (lineQty !== sumQty) {
          message.error(
            `物料「${line.itemName}」上架明细数量之和(${sumQty})与收货数量(${lineQty})不一致`,
          );
          return;
        }
        row.details = details.map((detail) => ({
          warehouseId: detail.warehouseId!,
          locationId: detail.locationId!,
src/views/wls/productsales/data.ts
@@ -132,7 +132,7 @@
            contactAddress: notice?.recipientAddress,
            contactName: notice?.recipientName,
            contactTelephone: notice?.recipientTelephone,
            salesOrderCode: notice?.salesOrderCode,
            salesOrderCode: notice?.saleOrderCode,
          });
        },
        status: MesWmSalesNoticeStatusEnum.APPROVED,
src/views/wls/returnsales/modules/form.vue
@@ -191,16 +191,19 @@
      try {
        formData.value = await getReturnSales(data.id);
        // 通过销售订单号反查订单ID,确保 ErpSaleOrderSelect 能正确回显
        if (formData.value?.salesOrderCode && !formData.value.saleOrderId) {
        const formValues = formData.value as MesWmReturnSalesApi.ReturnSales & {
          saleOrderId?: number;
        };
        if (formValues.salesOrderCode && !formValues.saleOrderId) {
          const orderPage = await getSaleOrderPage({
            pageNo: 1,
            pageSize: 1,
            no: formData.value.salesOrderCode,
          } as Record<string, unknown> as never);
          const list = (orderPage as Record<string, unknown>)?.list as Array<Record<string, unknown>> | undefined;
          if (list?.length) {
            const orderId = list[0].id as number;
            (formData.value as Record<string, unknown>).saleOrderId = orderId;
            no: formValues.salesOrderCode,
          } as never);
          const first = orderPage?.list?.[0];
          if (first) {
            const orderId = Number(first.id);
            formValues.saleOrderId = orderId;
            const order = await getSaleOrder(orderId);
            saleOrderItems.value = order?.items || [];
          }
src/views/wls/returnsales/modules/line-form.vue
@@ -98,9 +98,9 @@
                pageNo: 1,
                pageSize: 1,
              });
              const batchList = batchPage?.list;
              if (batchList?.length) {
                await formApi.setFieldValue('batchId', batchList[0].id);
              const firstBatch = batchPage?.list?.[0];
              if (firstBatch) {
                await formApi.setFieldValue('batchId', firstBatch.id);
              }
            } catch {
              // 查询失败时忽略