Appearance
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
情景: 远程请求 分页下载 选择器 动态选择
