Files
SaaS-lib/lib/component/form/select/select-api-v2.vue
xuziqiang d0155dbe3c push
2024-05-15 17:29:42 +08:00

441 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- @format -->
<template>
<a-select
v-bind="getBindValues"
:options="getOptions"
@dropdown-visible-change="dropdownHandle"
@popupScroll="onPopupScroll"
v-model:value="modelValue"
@search="onSearch"
@clear="onClear"
:filterOption="onFilterOption">
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</a-select>
</template>
<script lang="ts">
import { computed, defineComponent, nextTick, PropType, ref, unref, watch } from 'vue';
import {
cloneDeep,
get,
isArray,
isEqual,
isFunction,
isString,
isUndefined,
isEmpty,
} from 'lodash-es';
import { HttpRequestConfig, useApi } from '/nerv-lib/use/use-api';
import { useParams } from '/nerv-lib/use/use-params';
type ChangeValue = string | number | undefined | string[] | number[];
declare type Recordable<T = any> = Record<string, T>;
export default defineComponent({
name: 'NsSelectApiV2',
props: {
api: {
type: [String, Object, Function] as PropType<string | Function | HttpRequestConfig>,
required: true,
},
value: [String, Object, Array, Number],
field: String,
params: {
type: Object,
default: () => ({}),
},
resultField: {
type: String,
default: 'data.data',
},
firstOption: {
type: Object,
},
numberToString: {
type: Boolean,
default: false,
},
immediate: {
type: Boolean,
default: false,
},
labelField: {
type: [String, Function],
default: 'label',
},
valueField: {
type: String,
default: 'value',
},
customRequest: {
type: [Function],
},
autoSelectFirst: {
// 与autoClearValue同时使用只会执行autoSelectFirst
type: Boolean,
default: false,
},
autoClearValue: {
type: Boolean,
default: false,
},
//数据筛选函数
filterData: {
type: Function,
},
// 开启前端筛选默认label包含
autoSearch: {
type: Boolean,
default: false,
},
//后端筛选字段
filterFiled: {
type: String,
},
//是否开启滚动加载
scrollLoad: {
type: Boolean,
default: false,
},
//分页字段
pageField: {
type: String,
default: 'page',
},
//第一页
defaultPage: {
type: Number,
default: 0,
},
dropdownReload: {
type: Boolean,
default: false,
},
dynamicParams: {
type: String || Array || Function || Object,
},
formModel: {
type: Object as PropType<Recordable>,
default: () => ({}),
},
},
emits: ['change', 'validateChange'],
setup(props, { attrs, emit }) {
const options = ref([]);
const isLoad = ref(false);
const changeValue = ref<any>(undefined);
const filterFiledRef = ref<string | undefined>(undefined);
let isFirstLoad = !!props.api; // 是否第一次加载
const { getParams } = useParams();
// eslint-disable-next-line vue/no-setup-props-destructure
let page = props.defaultPage; //
let isLoading = false;
const getBindValues = computed(() => {
const selectProps: Recordable = {};
if (props.filterFiled || props.autoSearch) {
selectProps['showSearch'] = true;
}
return {
...attrs,
...selectProps,
};
});
/**
* 设置filterFiled时注册search事件
*/
const onSearch = computed(() => {
if (props.filterFiled) {
return (input: string) => {
if (filterFiledRef.value !== input) {
filterFiledRef.value = input;
page = props.defaultPage;
fetch();
}
};
} else {
return undefined;
}
});
/**
* 设置filterFiled时注册filterOption事件
*/
const onFilterOption = computed(() => {
if (props.autoSearch) {
return (input: string, option: any) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
};
} else {
return false;
}
});
/**
* 根据请求得到的data获取options
*/
const getOptions = computed(() => {
const { firstOption, labelField, valueField, numberToString } = props;
const sourceOptions = [];
firstOption && sourceOptions.push(firstOption);
return unref(options).reduce((acc, next: Recordable) => {
if (isString(next)) {
//值是字符串类型
const option = {
label: next,
value: next,
};
acc.push(option);
return acc;
} else {
const value = get(next, valueField);
const option = {
...next,
label: isFunction(labelField) ? labelField(next) : get(next, labelField as string),
value: numberToString ? `${value}` : value,
};
acc.push(option);
return acc;
}
}, sourceOptions);
});
/**
* 获取数据
*/
const fetch = () => {
console.log('requiredParams', getBindValues.value.checkRequiredParams);
if (getBindValues.value.checkRequiredParams === false) {
options.value = [];
return;
}
isLoading = true;
const requestConfig: HttpRequestConfig = { method: 'get' };
const {
api,
params: _params,
resultField,
filterData,
filterFiled,
pageField,
scrollLoad,
defaultPage,
dynamicParams,
customRequest,
} = props;
let params: Recordable = cloneDeep(_params);
if (dynamicParams) {
params = getParams(attrs.record, dynamicParams, { ...params });
}
if (filterFiled && filterFiledRef.value) {
params[filterFiled] = filterFiledRef.value;
// options.value = [];
}
if (scrollLoad) {
params[pageField] = page;
if (page === defaultPage) {
options.value = [];
}
}
const success = (res: Recordable) => {
emit('validateChange', { help: undefined });
if (resultField) {
let data = get(res, resultField) || [];
// data = data.splice(Math.floor(Math.random() * 3), Math.floor(Math.random() * 5 + 5));
if (isFunction(filterData)) {
data = data.filter(filterData);
}
if (scrollLoad) {
options.value = [...options.value, ...data];
} else {
options.value = data;
}
}
isLoad.value = true;
isLoading = false;
};
const errror = (error: any) => {
if (error?.response?.status === 403) {
emit('validateChange', { help: '暂无权限', validateStatus: 'error' });
nextTick(() => {
//清空编辑初始值
modelValue.value = undefined;
});
}
options.value = [];
isLoading = false;
};
if (customRequest) {
customRequest({ api, params, requestConfig })
.then((res: Recordable) => {
success(res);
})
.catch((error: any) => {
errror(error);
});
} else {
const { httpRequest } = useApi();
httpRequest({ api, params, requestConfig })
.then((res: Recordable) => {
success(res);
})
.catch((error: any) => {
errror(error);
});
}
};
let lastParams: any = undefined;
/**
** 延迟获取数据
*/
async function dropdownHandle(open: boolean) {
if (!open) return;
if ((!props.immediate && !isLoad.value) || props.dropdownReload) {
await fetch();
}
}
/**
** 绑定value
*/
const modelValue = computed({
set(value: ChangeValue) {
if (isEqual(value, changeValue.value)) return;
changeValue.value = value;
triggerChange(value);
},
get() {
return changeValue.value;
},
});
/**
* 传入值需要
* 前提option已获取到数据
*/
watch(
() => props.value,
() => {
if (isLoad.value) {
modelValue.value = props.value;
}
},
{
immediate: true,
},
);
watch(
() => getOptions.value,
() => {
const { value, autoSelectFirst, autoClearValue } = props;
// 首次加载如果有值则选中值
if (isFirstLoad && !isUndefined(value)) {
modelValue.value = value;
} else if (!filterFiledRef.value) {
if (autoSelectFirst) {
modelValue.value = getOptions.value[0]?.value;
} else if (autoClearValue) {
modelValue.value = undefined;
}
}
isFirstLoad = false;
},
);
/**
* 重写change ant 初始化数据、删除数据时不触发change
* @param value
*/
function triggerChange(value: ChangeValue) {
if (isUndefined(value)) {
emit('change', value, undefined);
} else if (isArray(value)) {
const options: Record<string, any>[] = [];
value.forEach((v) => {
getOptions.value.forEach((option) => {
if (option.value === v) {
options.push(option);
}
});
});
emit('change', value, options);
} else {
let op = {};
getOptions.value.forEach((option) => {
if (option.value === value) {
op = option;
}
});
emit('change', value, op);
}
}
/**
** 联动 immediate 为是否主动获取数据
*/
watch(
() => attrs.record,
async (value: object) => {
if (isEmpty(value)) return;
let { params, dynamicParams, defaultParams = {} } = props;
params = getParams(value, dynamicParams, { ...params, ...defaultParams });
if (!isEqual(lastParams, params)) {
if (props.immediate || true) {
//todo 暂时全设为主动获取
lastParams = cloneDeep(params);
await fetch();
}
isLoad.value = false; // 设置成false 点击下拉才会触发
}
},
{ deep: true, immediate: props.immediate },
);
/**
** 联动 immediate 为是否主动获取数据
*/
watch(
[() => props.params, () => props.api],
async () => {
const { params } = props;
if (!isEqual(lastParams, params)) {
if (props.immediate || true) {
//todo 暂时全设为主动获取
lastParams = cloneDeep(params);
page = props.defaultPage;
await fetch();
}
isLoad.value = false; // 设置成false 点击下拉才会触发
}
},
{ deep: true, immediate: props.immediate },
);
const onPopupScroll = computed(() => {
if (props.scrollLoad) {
return (e) => {
const { scrollHeight, scrollTop, clientHeight } = e.target;
if (scrollHeight - scrollTop - 80 < clientHeight && !isLoading) {
page++;
fetch();
}
};
} else {
return undefined;
}
});
const onClear = () => {
filterFiledRef.value = undefined;
};
return {
onPopupScroll,
dropdownHandle,
getOptions,
getBindValues,
modelValue,
onSearch,
onFilterOption,
onClear,
};
},
});
</script>
<style lang="less" scoped></style>