| | |
| | | <script lang="ts" setup> |
| | | import type { CrmCustomerApi } from '#/api/crm/customer'; |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | | import { computed, ref, useAttrs, watch } from 'vue'; |
| | | |
| | | import { Select } from 'ant-design-vue'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | |
| | | import { getCustomerSimpleList } from '#/api/crm/customer'; |
| | | import { Input, Tooltip } from 'ant-design-vue'; |
| | | |
| | | interface Props { |
| | | value?: number; |
| | | disabled?: boolean; |
| | | } |
| | | import { getCustomer } from '#/api/crm/customer'; |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | value: undefined, |
| | | disabled: false, |
| | | }); |
| | | import CrmCustomerSelectDialog from './crm-customer-select-dialog.vue'; |
| | | |
| | | const emit = defineEmits(['update:value', 'change']); |
| | | defineOptions({ name: 'CrmCustomerSelect', inheritAttrs: false }); |
| | | |
| | | const customerList = ref<CrmCustomerApi.Customer[]>([]); |
| | | const loading = ref(false); |
| | | |
| | | const options = computed(() => { |
| | | return customerList.value.map((item) => ({ |
| | | label: item.name, |
| | | value: item.id, |
| | | })); |
| | | }); |
| | | |
| | | const selectedValue = ref(props.value); |
| | | |
| | | watch( |
| | | () => props.value, |
| | | (val) => { |
| | | selectedValue.value = val; |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | allowClear?: boolean; |
| | | disabled?: boolean; |
| | | modelValue?: number; |
| | | placeholder?: string; |
| | | }>(), |
| | | { |
| | | allowClear: true, |
| | | disabled: false, |
| | | modelValue: undefined, |
| | | placeholder: '请选择客户', |
| | | }, |
| | | ); |
| | | const emit = defineEmits<{ |
| | | change: [item: CrmCustomerApi.Customer | undefined]; |
| | | 'update:modelValue': [value: number | undefined]; |
| | | }>(); |
| | | const attrs = useAttrs(); |
| | | const dialogRef = ref<InstanceType<typeof CrmCustomerSelectDialog>>(); |
| | | const hovering = ref(false); |
| | | const selectedItem = ref<CrmCustomerApi.Customer>(); |
| | | |
| | | watch(selectedValue, (val) => { |
| | | emit('update:value', val); |
| | | const customer = customerList.value.find((item) => item.id === val); |
| | | emit('change', customer); |
| | | }); |
| | | const displayLabel = computed(() => selectedItem.value?.name ?? ''); |
| | | const showClear = computed( |
| | | () => |
| | | props.allowClear && |
| | | !props.disabled && |
| | | hovering.value && |
| | | props.modelValue !== null, |
| | | ); |
| | | |
| | | async function loadCustomers() { |
| | | if (customerList.value.length > 0) return; |
| | | loading.value = true; |
| | | try { |
| | | customerList.value = await getCustomerSimpleList(); |
| | | } finally { |
| | | loading.value = false; |
| | | async function resolveCustomerById(id: number | undefined) { |
| | | if (!id) { |
| | | selectedItem.value = undefined; |
| | | return; |
| | | } |
| | | if (selectedItem.value?.id === id) { |
| | | return; |
| | | } |
| | | selectedItem.value = await getCustomer(id); |
| | | } |
| | | |
| | | function filterOption(input: string, option: any) { |
| | | return option.label.toLowerCase().includes(input.toLowerCase()); |
| | | watch( |
| | | () => props.modelValue, |
| | | (value) => { |
| | | resolveCustomerById(value); |
| | | }, |
| | | { immediate: true }, |
| | | ); |
| | | |
| | | function clearSelected() { |
| | | selectedItem.value = undefined; |
| | | emit('update:modelValue', undefined); |
| | | emit('change', undefined); |
| | | } |
| | | |
| | | loadCustomers(); |
| | | function handleClick(event: MouseEvent) { |
| | | if (props.disabled) { |
| | | return; |
| | | } |
| | | const target = event.target as HTMLElement; |
| | | if (showClear.value && target.closest('.ant-input-suffix')) { |
| | | event.stopPropagation(); |
| | | clearSelected(); |
| | | return; |
| | | } |
| | | const selectedIds = ( |
| | | props.modelValue === null ? [] : [props.modelValue] |
| | | ) as number[]; |
| | | dialogRef.value?.open(selectedIds, { multiple: false }); |
| | | } |
| | | |
| | | function handleSelected(rows: CrmCustomerApi.Customer[]) { |
| | | const item = rows[0]; |
| | | if (!item) { |
| | | return; |
| | | } |
| | | selectedItem.value = item; |
| | | emit('update:modelValue', item.id); |
| | | emit('change', item); |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <Select |
| | | v-model:value="selectedValue" |
| | | :options="options" |
| | | :loading="loading" |
| | | :disabled="disabled" |
| | | placeholder="请选择客户" |
| | | show-search |
| | | :filter-option="filterOption" |
| | | allow-clear |
| | | /> |
| | | </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="selectedItem ? undefined : false"> |
| | | <template #title> |
| | | <div v-if="selectedItem" class="leading-6"> |
| | | <div>名称:{{ selectedItem.name || '-' }}</div> |
| | | <div>手机:{{ selectedItem.mobile || '-' }}</div> |
| | | <div>地区:{{ selectedItem.areaName || '-' }}</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> |
| | | <CrmCustomerSelectDialog ref="dialogRef" @selected="handleSelected" /> |
| | | </template> |