Cpte-Vue3/src/views/base/area/components/AreaSelect.vue

362 lines
10 KiB
Vue
Raw Normal View History

2025-11-02 22:39:59 +08:00
<!-- 库区下拉选择-->
<template>
<a-select
v-model:value="selectedValue"
showSearch
:placeholder="placeholder"
:loading="loading"
:allowClear="true"
:filterOption="filterOption"
:notFoundContent="notFoundContent"
:mode="multiple ? 'multiple' : 'default'"
@change="handleChange"
@search="handleSearch"
@focus="handleFocus"
@popupScroll="handlePopupScroll"
:getPopupContainer="getParentContainer"
v-bind="attrs"
>
<template #notFoundContent>
<a-spin v-if="loading" size="small" />
<span v-else></span>
</template>
<a-select-option v-for="option in areaOptions" :key="option.id" :value="getOptionValue(option)">
{{ getOptionLabel(option) }}
</a-select-option>
</a-select>
</template>
<script lang="ts">
import { defineComponent, ref, watch, computed, onMounted } from 'vue';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { propTypes } from '/@/utils/propTypes';
import { defHttp } from '/@/utils/http/axios';
import { setPopContainer } from '/@/utils';
import { debounce } from 'lodash-es';
// 库区数据接口
interface Area {
id: string;
areaCode: string;
areaName: string;
}
//响应数据接口
interface ResponseData {
records: Area[];
total: number;
size: number;
current: number;
page: number;
}
export default defineComponent({
name: 'AreaSelect',
inheritAttrs: false,
props: {
// 选中值支持v-model
value: propTypes.oneOfType([propTypes.string, propTypes.array, propTypes.object]),
// 占位符
placeholder: propTypes.string.def('请选择库区'),
// 是否多选
multiple: propTypes.bool.def(false),
// 是否异步加载数据
async: propTypes.bool.def(true),
// 分页大小
pageSize: propTypes.number.def(20),
// 弹出层容器
popContainer: propTypes.string,
// 自定义弹出层容器函数
getPopupContainer: {
type: Function,
default: (node: HTMLElement) => node?.parentNode,
},
// 是否立即触发change事件
immediateChange: propTypes.bool.def(false),
// 返回值类型: 'id'(默认) | 'object' | 其他字段名
returnValue: propTypes.string.def('id'),
//默认启用
izActive: propTypes.number.def(1),
},
emits: ['change', 'update:value', 'optionsLoaded'],
setup(props, { emit }) {
const areaOptions = ref<Area[]>([]);
const loading = ref<boolean>(false);
const allAreas = ref<Area[]>([]);
const attrs = useAttrs({ excludeDefaultKeys: false });
// 分页相关
const pageNo = ref(1);
const isHasData = ref(true);
const scrollLoading = ref(false);
const searchKeyword = ref('');
// 选中值
const selectedValue = ref<string | string[] | undefined>(undefined);
// 未找到内容
const notFoundContent = computed(() => {
return loading.value ? undefined : null;
});
/**
* 获取选项显示文本 - 始终显示完整格式
*/
function getOptionLabel(option: Area) {
return `${option.areaCode} - ${option.areaName}`;
}
/**
* 获取选项值 - 根据returnValue确定实际存储的值
*/
function getOptionValue(option: Area) {
if (props.returnValue === 'object') {
return option.id; // 对于object类型仍然使用id作为选项值但在change事件中返回完整对象
} else if (props.returnValue === 'id') {
return option.id;
} else {
return option[props.returnValue as keyof Area] as string;
}
}
/**
* 获取弹出层容器
*/
function getParentContainer(node: HTMLElement) {
if (props.popContainer) {
return setPopContainer(node, props.popContainer);
} else {
if (typeof props.getPopupContainer === 'function') {
return props.getPopupContainer(node);
} else {
return node?.parentNode;
}
}
}
/**
* 过滤选项 - 禁用前端过滤使用后端搜索
*/
function filterOption(_input: string, _option: any) {
return true; // 禁用前端过滤,完全依赖后端搜索
}
/**
* 获取库区数据
*/
const fetchAreas = async (page = 1, keyword = '', isSearch = false) => {
try {
loading.value = true;
const res = await defHttp.get<ResponseData>({
url: '/base/area/list',
params: {
pageSize: props.pageSize,
pageNo: page,
keyword: keyword,
izActive: props.izActive,
},
});
console.log('获取库区数据成功:', res);
const records = res.records || [];
if (page === 1 || isSearch) {
// 第一页或搜索时,重置数据
allAreas.value = records;
areaOptions.value = records;
} else {
// 滚动加载时,追加数据
allAreas.value = [...allAreas.value, ...records];
areaOptions.value = [...areaOptions.value, ...records];
}
// 修正分页判断逻辑
isHasData.value = records.length >= props.pageSize;
console.log('是否还有更多数据:', records.length);
emit('optionsLoaded', allAreas.value);
} catch (error) {
console.error('获取库区数据失败:', error);
if (page === 1) {
allAreas.value = [];
areaOptions.value = [];
}
} finally {
loading.value = false;
scrollLoading.value = false;
}
};
/**
* 根据选项值找到对应的选项对象
*/
function findOptionByValue(value: string): Area | undefined {
if (props.returnValue === 'object' || props.returnValue === 'id') {
return allAreas.value.find((item) => item.id === value);
} else {
return allAreas.value.find((item) => item[props.returnValue as keyof Area] === value);
}
}
/**
* 获取需要返回的值
*/
function getReturnValue(value: string | string[]) {
if (!value) {
return props.multiple ? [] : undefined;
}
// 如果返回整个对象
if (props.returnValue === 'object') {
if (Array.isArray(value)) {
return value.map((v) => findOptionByValue(v)).filter(Boolean);
} else {
return findOptionByValue(value);
}
}
// 如果返回ID默认情况
else if (props.returnValue === 'id') {
return value;
}
// 如果返回对象中的某个字段
else {
if (Array.isArray(value)) {
return value.map((v) => {
const option = findOptionByValue(v);
return option ? option[props.returnValue as keyof Area] : v;
});
} else {
const option = findOptionByValue(value);
return option ? option[props.returnValue as keyof Area] : value;
}
}
}
/**
* 搜索处理防抖
*/
const handleSearch = debounce(function (value: string) {
searchKeyword.value = value;
pageNo.value = 1;
isHasData.value = true;
// 直接调用API进行搜索
fetchAreas(1, value, true);
}, 300);
/**
* 处理焦点事件
*/
function handleFocus() {
// 如果还没有数据,加载数据
if (allAreas.value.length === 0 && props.async) {
pageNo.value = 1;
isHasData.value = true;
fetchAreas(1, '');
}
attrs.onFocus?.();
}
/**
* 处理值变化
*/
function handleChange(value: string | string[]) {
selectedValue.value = value;
// 根据配置返回相应的值
const returnValue = getReturnValue(value);
console.log('值变化:', returnValue);
emit('update:value', returnValue);
emit('change', returnValue);
}
/**
* 滚动加载处理
*/
function handlePopupScroll(e: Event) {
const target = e.target as HTMLElement;
const { scrollTop, scrollHeight, clientHeight } = target;
if (!scrollLoading.value && isHasData.value && scrollTop + clientHeight >= scrollHeight - 10) {
console.log('滚动加载更多');
scrollLoading.value = true;
pageNo.value++;
fetchAreas(pageNo.value, searchKeyword.value)
.finally(() => {
scrollLoading.value = false;
})
.catch(() => {
pageNo.value--;
});
}
}
/**
* 根据选中值初始化显示文本
*/
const initSelectValue = async () => {
if (!props.value) {
selectedValue.value = props.multiple ? [] : undefined;
return;
}
// 如果是异步模式且还没有加载数据,则先加载数据
if (props.async && allAreas.value.length === 0) {
await fetchAreas();
}
// 根据不同的returnValue设置选中的值
if (props.returnValue === 'object') {
// 如果返回的是对象value可能是对象或对象数组
if (Array.isArray(props.value)) {
selectedValue.value = props.value.map((item: any) => item.id);
} else {
selectedValue.value = (props.value as any).id;
}
} else if (props.returnValue === 'id') {
selectedValue.value = props.value as string | string[];
} else {
// 对于其他字段类型,直接使用传入的值
selectedValue.value = props.value as string | string[];
}
};
// 监听value变化
watch(
() => props.value,
() => {
initSelectValue();
},
{ immediate: true }
);
// 组件挂载时初始化
onMounted(() => {
if (!props.async) {
fetchAreas();
}
});
return {
attrs,
areaOptions,
loading,
selectedValue,
notFoundContent,
getParentContainer,
filterOption,
handleChange,
handleSearch,
handleFocus,
getOptionLabel,
getOptionValue,
handlePopupScroll,
};
},
});
</script>
<style lang="less" scoped></style>