gaoluyang
2026-06-29 27cd042df9aca0383a49f3514bc21958dd890912
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<script lang="ts" setup>
import type { MesMdProductBomApi } from '#/api/mes/md/item/productBom';
 
import { computed, ref, useAttrs, watch } from 'vue';
 
import { IconifyIcon } from '../../../../../packages/icons/src';
 
import { Input, Tooltip } from 'ant-design-vue';
 
import { getProductBomListByItemId } from '#/api/mes/md/item/productBom';
 
import MdProductBomSelectDialog from './product-bom-select-dialog.vue';
 
defineOptions({ name: 'MdProductBomSelect', inheritAttrs: false });
 
const props = withDefaults(
  defineProps<{
    allowClear?: boolean;
    disabled?: boolean;
    itemId?: number;
    modelValue?: number;
    placeholder?: string;
  }>(),
  {
    allowClear: true,
    disabled: false,
    itemId: undefined,
    modelValue: undefined,
    placeholder: '请选择 BOM 物料',
  },
);
const emit = defineEmits<{
  change: [bom: MesMdProductBomApi.ProductBom | undefined];
  'update:modelValue': [value: number | undefined];
}>();
const attrs = useAttrs(); // 透传属性
const dialogRef = ref<InstanceType<typeof MdProductBomSelectDialog>>(); // BOM 物料选择弹窗
const hovering = ref(false); // 是否悬停
const selectedBom = 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;
  }
  if (selectedBom.value?.bomItemId === bomItemId) {
    return;
  }
  const list = await getProductBomListByItemId(props.itemId as number);
  selectedBom.value = list.find((item) => item.bomItemId === bomItemId);
}
 
watch(
  () => props.modelValue,
  (value) => {
    resolveBomById(value);
  },
  { immediate: true },
);
 
watch(
  () => props.itemId,
  () => {
    selectedBom.value = undefined;
    emit('update:modelValue', undefined);
    emit('change', undefined);
  },
);
 
/** 清空已选 BOM 物料 */
function clearSelected() {
  selectedBom.value = undefined;
  emit('update:modelValue', undefined);
  emit('change', undefined);
}
 
/** 打开 BOM 物料选择弹窗 */
function handleClick(event: MouseEvent) {
  if (props.disabled || props.itemId === undefined || props.itemId === null) {
    return;
  }
  const target = event.target as HTMLElement;
  if (showClear.value && target.closest('.ant-input-suffix')) {
    event.stopPropagation();
    clearSelected();
    return;
  }
  dialogRef.value?.open(props.itemId as number, props.modelValue);
}
 
/** 回填选中的 BOM 物料 */
function handleSelected(row: MesMdProductBomApi.ProductBom) {
  selectedBom.value = row;
  emit('update:modelValue', row.bomItemId);
  emit('change', row);
}
</script>
 
<template>
  <div
    v-bind="attrs"
    class="w-full"
    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
    @click="handleClick"
    @mouseenter="hovering = true"
    @mouseleave="hovering = false"
  >
    <Tooltip :mouse-enter-delay="0.5" :open="selectedBom ? undefined : false">
      <template #title>
        <div v-if="selectedBom" class="leading-6">
          <div>编码:{{ selectedBom.bomItemCode || '-' }}</div>
          <div>名称:{{ selectedBom.bomItemName || '-' }}</div>
          <div>规格:{{ selectedBom.bomItemSpecification || '-' }}</div>
          <div>单位:{{ selectedBom.unitMeasureName || '-' }}</div>
          <div>用量比例:{{ selectedBom.quantity ?? '-' }}</div>
        </div>
      </template>
      <Input
        :disabled="disabled"
        :placeholder="placeholder"
        :value="displayLabel"
        readonly
      >
        <template #suffix>
          <IconifyIcon
            class="size-4"
            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
          />
        </template>
      </Input>
    </Tooltip>
  </div>
  <MdProductBomSelectDialog ref="dialogRef" @selected="handleSelected" />
</template>