Skip to content
vue

 <a-table ref="tableElRef" v-bind="bindAttrs" @change="handleTableChange">
  <template #[item]="data" v-for="item in Object.keys(slots)" :key="item">
    <slot :name="item" v-bind="data || {}"></slot>
  </template>
</a-table>

<grid-table v-bind="auditHistoriesTable">
  <template #content="{ record }">
    <div v-for="(item, index) in record.content.slice(0, 4)" :key="index" class="my-2 text-sm text-gray-500">
      <span>{{ item.label }}:</span>
      <span>{{ item.value }}</span>
    </div>
  </template>
</grid-table>

{
  title: '摘要',
  dataIndex: 'content',
  align: 'left',
  slots: { customRender: 'content' },
},
ts
<template>
  <a-select v-bind="bindAttrs" @dropdownVisibleChange="handleFetch" @popupScroll="handleScroll">
    <template #suffixIcon v-if="loading">
      <loading-outlined spin />
    </template>
    <template #notFoundContent>
      <div class="text-center" v-if="isSearched"> 暂无数据 </div>
      <div class="text-center" v-else> 请输入关键字搜索 </div>
    </template>
  </a-select>
</template>
<script lang="ts" setup>
  import { debounce, get, isArray, isFunction } from 'lodash-es';
  import { computed, PropType, unref, useAttrs } from 'vue';
  import { getReference } from '/@/api/global';

  const props = defineProps({
    value: [Number, String, Array] as PropType<any>,
    api: [Function, String] as PropType<Fn | string>,
    immediate: {
      type: Boolean as PropType<boolean>,
      default: false,
    },
    filter: {
      type: Object as PropType<Recordable>,
    },
    valueField: {
      type: [String, Function] as PropType<string | Fn>,
      default: 'value',
    },
    labelField: {
      type: [String, Function] as PropType<string | Fn>,
      default: 'label',
    },
    PriKeyField: {
      type: String as PropType<string>,
      default: 'id',
    },
    resultField: {
      type: String as PropType<string>,
      default: 'items',
    },
    zeroToUndefined: {
      type: Boolean as PropType<boolean>,
      default: true,
    },
    zeroValueToUndefined: {
      type: Boolean as PropType<boolean>,
      default: false,
    },
    numberToString: {
      type: Boolean as PropType<boolean>,
      default: false,
    },
    showSearch: Boolean as PropType<boolean>,
  });
  const emits = defineEmits(['blur', 'change', 'update:value', 'options-change', 'label-change', 'option-select']);
  const attrs = useAttrs();
  const loading = ref(false);
  let page = 1,pageSize = 15,lockPage = false,keyword = '';
  const options = ref<Recordable[]>([]);
  const v = ref();
  if(props.value === 0 && props.zeroValueToUndefined){
    emits('update:value', undefined);
  }
  watch(
    () => props.value,
    (value) => {
      if (value === 0 && props.zeroToUndefined) {
        v.value = undefined;
      } else {
        const valueUnref = toRaw(unref(value));
        if (valueUnref && !options.value.some((item) => (isArray(valueUnref) ? valueUnref.includes(item.value) : item.value === valueUnref))) {

          fetch({ [props.PriKeyField]: valueUnref });
        }
        v.value = value;
      }
    },
    {
      immediate: true,
    },
  );
  onMounted(() => {
    if (props.immediate || props.value) {
      const param = !isFunction(props.valueField) ? { [props.valueField]: props.value } : undefined;
      fetch(param);
    }
  });
  watch(()=>props.filter,(value,oldValue)=>{
    if(JSON.stringify(value) != JSON.stringify(oldValue)){
      fetch()
    }
  });
  watch(
    () => [props.api],
    async () => await fetch(),
  );
  watch(
    () => v.value,
    (v) => {
      emits('update:value', v);
      if(Array.isArray(v)){
        emits(
            'option-select',
            options.value.filter((o) => v.indexOf(o.value) > -1),
        );
      }else{
        emits(
            'option-select',
            options.value.find((o) => o.value === v),
        );
      }
    },
  );

  let isFetched = false;
  async function handleFetch() {
    resetPage()
    await fetch()
  }

  const isSearched = ref(false);
  const handleSearch = debounce(async (kw: string) => {
    if (props.showSearch) {
      resetPage()
      keyword = kw
      if (await fetch({ kw })) isSearched.value = true;
    }
  }, 500);

  async function fetch(param?: Recordable,callback?: Fn) {
    const { api, filter, resultField, labelField, valueField, numberToString } = props;
    if (!api) return;

    try {
      loading.value = true;
      const requestParam = { ...filter, ...param,page,pageSize};
      const res = await (isFunction(api) ? api(requestParam) : getReference(api, requestParam));
      let optionData = isArray(res) ? res : get(res, resultField);
      const resultOptions = optionData?.map((item) => {
        const value = isFunction(valueField) ? valueField(item) : item[valueField];
        const label = isFunction(labelField) ? labelField(item) : item[labelField];
        const rawItem = item;
        if (item.isDefault) {
          v.value = value;
        }
        return {
          label: label,
          value: numberToString ? `${value}` : value,
          rawItem,
        };
      })
      if(callback){
        callback(resultOptions)
      }else{
        options.value = resultOptions;
      }
      emits('options-change', options.value);
      return true;
    } catch (error) {
      console.warn(error);
      return false;
    } finally {
      loading.value = false;
    }
  }

  const bindAttrs = computed(() => {
    return {
      dropdownStyle: { maxHeight: '280px' },
      optionFilterProp: 'label',
      value: unref(v),
      options: unref(options),
      onChange: (...value) => emits('change', ...value),
      'onUpdate:value': (...value) => emits('update:value', ...value),
      ...attrs,
      ...(props.showSearch ? { showSearch: true, onSearch: handleSearch } : {}),
    };
  });
  function resetPage(){
    page = 1
    lockPage = false
    keyword = ''
  }
  async function handleScroll(e){
    const { target } = e
    const scrollTop = target.scrollTop
    const scrollHeight = target.scrollHeight - scrollTop
    const clientHeight = target.clientHeight
    if(!lockPage && scrollHeight < clientHeight + 2){
      page++
      await fetch({ kw:keyword },function (result){
        options.value = options.value.concat(result)
        if(result.length < pageSize){
          lockPage = true
        }
        target.scrollTop = scrollTop
      })

    }
  }
</script>

value api requestParams

情景: 远程请求 分页下载 选择器 动态选择

Lucking