2026-09-03 6738e8f5a78431e41fed28e6b795e70c2396070a
src/views/mes/md/item/components/product-bom-select-dialog.vue
@@ -13,26 +13,115 @@
defineOptions({ name: 'MdProductBomSelectDialog' });
const props = withDefaults(
  defineProps<{
    multiple?: boolean;
  }>(),
  {
    multiple: false,
  },
);
const emit = defineEmits<{
  selected: [row: MesMdProductBomApi.ProductBom];
  selected: [rows: MesMdProductBomApi.ProductBom[]];
}>();
const open = ref(false); // 弹窗是否打开
const list = ref<MesMdProductBomApi.ProductBom[]>([]); // BOM 子物料列表
const selectedRow = ref<MesMdProductBomApi.ProductBom>(); // 当前选中的 BOM 行
const selectedRows = ref<MesMdProductBomApi.ProductBom[]>([]); // 已选 BOM 行(单选时最多一行)
const preSelectedIds = ref<number[]>([]); // 预选 BOM 物料编号
/** 获取多选记录,包含 VXE reserve 记录 */
function getMultipleSelectedRows() {
  const selectedMap = new Map<number, MesMdProductBomApi.ProductBom>();
  const records = [
    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
  ] as MesMdProductBomApi.ProductBom[];
  records.forEach((row) => {
    if (row.bomItemId != null) {
      selectedMap.set(row.bomItemId, row);
    }
  });
  return [...selectedMap.values()];
}
/** 处理勾选变化 */
function handleCheckboxSelectChange() {
  selectedRows.value = getMultipleSelectedRows();
}
/** 上周单击行痕迹,用于吞掉双击触发的第二次 click */
let lastClick = { bomItemId: undefined as number | undefined, time: 0 };
/** 单击行:多选切换勾选;单选由 radioConfig.trigger='row' 处理 */
async function handleCellClick({
  row,
  column,
}: {
  row: MesMdProductBomApi.ProductBom;
  column?: { type?: string };
}) {
  // 点击勾选/单选列自身由控件处理,避免二次切换
  if (column?.type === 'checkbox' || column?.type === 'radio') {
    return;
  }
  if (!props.multiple) {
    return;
  }
  const now = Date.now();
  if (lastClick.bomItemId === row.bomItemId && now - lastClick.time < 300) {
    lastClick.time = 0;
    return;
  }
  lastClick = { bomItemId: row.bomItemId, time: now };
  const selected = gridApi.grid.isCheckedByCheckboxRow(row);
  await gridApi.grid.setCheckboxRow(row, !selected);
  selectedRows.value = getMultipleSelectedRows();
}
/** 处理行双击 */
async function handleCellDblclick({
  row,
}: {
  row: MesMdProductBomApi.ProductBom;
}) {
  if (props.multiple) {
    return; // 多选由单击 cellClick 切换,双击不再重复处理
  }
  selectedRows.value = [row];
  gridApi.grid.setRadioRow(row);
  handleConfirm();
}
/** 处理单选变化 */
function handleRadioChange(row: MesMdProductBomApi.ProductBom) {
  selectedRows.value = [row];
}
/** 当前选择列类型 */
function getSelectColumn() {
  return {
    type: props.multiple ? 'checkbox' : 'radio',
    width: 55,
    align: 'center',
  } as const;
}
const [Grid, gridApi] = useVbenVxeGrid({
  gridOptions: {
    border: true,
    columns: [
      { type: 'radio', width: 55, align: 'center' },
      ...(useProductBomGridColumns(true) || []),
    ],
    columns: [getSelectColumn(), ...(useProductBomGridColumns(true) || [])],
    data: list.value,
    height: 500,
    keepSource: true,
    pagerConfig: {
      enabled: false,
    },
    checkboxConfig: {
      highlight: true,
      range: true,
      reserve: true,
    },
    radioConfig: {
      highlight: true,
@@ -48,35 +137,62 @@
    },
  } as VxeTableGridOptions<MesMdProductBomApi.ProductBom>,
  gridEvents: {
    cellDblclick: ({ row }: { row: MesMdProductBomApi.ProductBom }) => {
      selectedRow.value = row;
      gridApi.grid.setRadioRow(row);
      handleConfirm();
    },
    cellClick: handleCellClick,
    cellDblclick: handleCellDblclick,
    checkboxChange: handleCheckboxSelectChange,
    checkboxAll: handleCheckboxSelectChange,
    radioChange: ({ row }: { row: MesMdProductBomApi.ProductBom }) => {
      selectedRow.value = row;
      handleRadioChange(row);
    },
  },
});
/** 打开 BOM 物料选择弹窗 */
async function openModal(itemId: number, selectedBomItemId?: number) {
/** 回显预选 BOM 物料(数据一次性加载,无分页,直接勾选即可) */
async function applyPreSelection() {
  if (preSelectedIds.value.length === 0) {
    return;
  }
  for (const row of list.value) {
    if (
      row.bomItemId === undefined ||
      row.bomItemId === null ||
      !preSelectedIds.value.includes(row.bomItemId)
    ) {
      continue;
    }
    if (props.multiple) {
      await gridApi.grid.setCheckboxRow(row, true);
    } else {
      await gridApi.grid.setRadioRow(row);
      selectedRows.value = [row];
      return;
    }
  }
  if (props.multiple) {
    selectedRows.value = getMultipleSelectedRows();
  }
}
/** 打开 BOM 物料选择弹窗(selectedBomItemIds 单选传单个编号,多选传编号数组) */
async function openModal(itemId: number, selectedBomItemIds?: number | number[]) {
  open.value = true;
  selectedRow.value = undefined;
  preSelectedIds.value = (
    Array.isArray(selectedBomItemIds)
      ? selectedBomItemIds
      : selectedBomItemIds === undefined || selectedBomItemIds === null
        ? []
        : [selectedBomItemIds]
  ).filter((id): id is number => id != null);
  selectedRows.value = [];
  gridApi.setLoading(true);
  try {
    list.value = await getProductBomListByItemId(itemId);
    gridApi.setGridOptions({ data: list.value });
    gridApi.setGridOptions({
      data: list.value,
      columns: [getSelectColumn(), ...(useProductBomGridColumns(true) || [])],
    });
    await nextTick();
    if (selectedBomItemId !== null) {
      const match = list.value.find(
        (row) => row.bomItemId === selectedBomItemId,
      );
      if (match) {
        selectedRow.value = match;
        gridApi.grid.setRadioRow(match);
      }
    }
    await applyPreSelection();
  } finally {
    gridApi.setLoading(false);
  }
@@ -85,17 +201,23 @@
/** 关闭 BOM 物料选择弹窗 */
async function closeModal() {
  open.value = false;
  selectedRow.value = undefined;
  selectedRows.value = [];
  preSelectedIds.value = [];
  await gridApi.grid.clearCheckboxRow();
  await gridApi.grid.clearCheckboxReserve();
  await gridApi.grid.clearRadioRow();
}
/** 确认选择 BOM 物料 */
function handleConfirm() {
  if (!selectedRow.value) {
    message.warning('请选择一条数据');
  const rows = props.multiple
    ? getMultipleSelectedRows()
    : selectedRows.value.slice(0, 1);
  if (rows.length === 0) {
    message.warning(props.multiple ? '请至少选择一条数据' : '请选择一条数据');
    return;
  }
  emit('selected', selectedRow.value);
  emit('selected', rows);
  open.value = false;
}
@@ -113,4 +235,4 @@
  >
    <Grid table-title="BOM 子物料列表" />
  </Modal>
</template>
</template>