src/components/crm-customer-select.vue
@@ -1,75 +1,133 @@
<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>