2026-09-03 6738e8f5a78431e41fed28e6b795e70c2396070a
src/views/mes/md/item/components/product-bom-select.vue
@@ -18,7 +18,8 @@
    allowClear?: boolean;
    disabled?: boolean;
    itemId?: number;
    modelValue?: number;
    modelValue?: number | number[];
    multiple?: boolean;
    placeholder?: string;
  }>(),
  {
@@ -26,50 +27,68 @@
    disabled: false,
    itemId: undefined,
    modelValue: undefined,
    multiple: false,
    placeholder: '请选择 BOM 物料',
  },
);
const emit = defineEmits<{
  change: [bom: MesMdProductBomApi.ProductBom | undefined];
  'update:modelValue': [value: number | undefined];
  change: [
    bom: MesMdProductBomApi.ProductBom[] | MesMdProductBomApi.ProductBom | undefined,
  ];
  'update:modelValue': [value: number[] | number | undefined];
}>();
const attrs = useAttrs(); // 透传属性
const dialogRef = ref<InstanceType<typeof MdProductBomSelectDialog>>(); // BOM 物料选择弹窗
const hovering = ref(false); // 是否悬停
const selectedBom = ref<MesMdProductBomApi.ProductBom>(); // 当前选中的 BOM 物料
const selectedBoms = ref<MesMdProductBomApi.ProductBom[]>([]); // 已选 BOM 物料(单选时最多一项)
const displayLabel = computed(() => selectedBom.value?.bomItemName ?? ''); // 选择器展示名称
const showClear = computed(
  () =>
    props.allowClear &&
    !props.disabled &&
    hovering.value &&
    props.modelValue !== undefined &&
    props.modelValue !== null,
);
/** 根据 BOM 子物料编号回显选择器 */
async function resolveBomById(bomItemId: number | undefined) {
  if (
    bomItemId === undefined ||
    bomItemId === null ||
    props.itemId === undefined ||
    props.itemId === null
  ) {
    selectedBom.value = undefined;
    return;
const selectedBom = computed(() => selectedBoms.value[0]); // 单选时的当前项
const displayLabel = computed(() => {
  // 多选展示「名称、名称」,单选展示单个名称
  if (props.multiple) {
    return selectedBoms.value
      .map((bom) => bom.bomItemName)
      .filter(Boolean)
      .join('、');
  }
  if (selectedBom.value?.bomItemId === bomItemId) {
  return selectedBom.value?.bomItemName ?? '';
}); // 选择器展示名称
const showClear = computed(() => {
  if (!props.allowClear || props.disabled || !hovering.value) {
    return false;
  }
  if (props.multiple) {
    return Array.isArray(props.modelValue) && props.modelValue.length > 0;
  }
  return props.modelValue !== undefined && props.modelValue !== null;
});
/** 从 modelValue 解析出 BOM 物料编号列表 */
function getModelIds() {
  if (props.multiple) {
    return Array.isArray(props.modelValue)
      ? props.modelValue.filter((id): id is number => id != null)
      : [];
  }
  return typeof props.modelValue === 'number' ? [props.modelValue] : [];
}
/** 根据 BOM 物料编号回显选择器 */
async function resolveBomsById(ids: number[]) {
  if (ids.length === 0 || props.itemId === undefined || props.itemId === null) {
    selectedBoms.value = [];
    return;
  }
  const list = await getProductBomListByItemId(props.itemId as number);
  selectedBom.value = list.find((item) => item.bomItemId === bomItemId);
  selectedBoms.value = list.filter(
    (item) => item.bomItemId != null && ids.includes(item.bomItemId),
  );
}
watch(
  () => props.modelValue,
  (value) => {
    resolveBomById(value);
  () => {
    resolveBomsById(getModelIds());
  },
  { immediate: true },
);
@@ -77,17 +96,17 @@
watch(
  () => props.itemId,
  () => {
    selectedBom.value = undefined;
    emit('update:modelValue', undefined);
    emit('change', undefined);
    selectedBoms.value = [];
    emit('update:modelValue', props.multiple ? [] : undefined);
    emit('change', props.multiple ? [] : undefined);
  },
);
/** 清空已选 BOM 物料 */
function clearSelected() {
  selectedBom.value = undefined;
  emit('update:modelValue', undefined);
  emit('change', undefined);
  selectedBoms.value = [];
  emit('update:modelValue', props.multiple ? [] : undefined);
  emit('change', props.multiple ? [] : undefined);
}
/** 打开 BOM 物料选择弹窗 */
@@ -101,14 +120,22 @@
    clearSelected();
    return;
  }
  dialogRef.value?.open(props.itemId as number, props.modelValue);
  const ids = getModelIds();
  dialogRef.value?.open(props.itemId as number, props.multiple ? ids : ids[0]);
}
/** 回填选中的 BOM 物料 */
function handleSelected(row: MesMdProductBomApi.ProductBom) {
  selectedBom.value = row;
  emit('update:modelValue', row.bomItemId);
  emit('change', row);
function handleSelected(rows: MesMdProductBomApi.ProductBom[]) {
  selectedBoms.value = rows;
  emit(
    'update:modelValue',
    props.multiple
      ? rows
          .map((row) => row.bomItemId)
          .filter((id): id is number => id != null)
      : rows[0]?.bomItemId,
  );
  emit('change', props.multiple ? rows : rows[0]);
}
</script>
@@ -121,9 +148,14 @@
    @mouseenter="hovering = true"
    @mouseleave="hovering = false"
  >
    <Tooltip :mouse-enter-delay="0.5" :open="selectedBom ? undefined : false">
    <Tooltip :mouse-enter-delay="0.5" :open="selectedBoms.length ? undefined : false">
      <template #title>
        <div v-if="selectedBom" class="leading-6">
        <div v-if="multiple" class="leading-6">
          <div v-for="bom in selectedBoms" :key="bom.bomItemId">
            {{ bom.bomItemName || '-' }}{{ bom.bomItemCode ? `(${bom.bomItemCode})` : '' }}
          </div>
        </div>
        <div v-else-if="selectedBom" class="leading-6">
          <div>编码:{{ selectedBom.bomItemCode || '-' }}</div>
          <div>名称:{{ selectedBom.bomItemName || '-' }}</div>
          <div>规格:{{ selectedBom.bomItemSpecification || '-' }}</div>
@@ -146,5 +178,9 @@
      </Input>
    </Tooltip>
  </div>
  <MdProductBomSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>
  <MdProductBomSelectDialog
    ref="dialogRef"
    :multiple="multiple"
    @selected="handleSelected"
  />
</template>