中航光电热表web
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
7.5 KiB

<template>
<el-select
ref="selectRef"
v-model="localValue"
:placeholder="placeholder"
:disabled="disabled"
filterable
clearable
:filter-method="handleSearch"
@visible-change="handleVisibleChange"
@clear="handleClear"
@change="handleChange"
class="paged-select"
:teleported="false"
:popper-options="{
modifiers: [{ name: 'eventListeners', options: { scroll: false, resize: false } }],
}"
>
<el-option
v-for="item in optionList"
:key="item[valueKey]"
:label="item[labelKey]"
:value="item[valueKey]"
/>
<el-option v-if="loading && hasMore" label="加载中..." disabled />
<el-option v-if="!hasMore && optionList.length" label="没有更多了~" disabled />
</el-select>
</template>
<script>
import axios from 'axios';
export default {
name: 'DynamicPagedSelect',
props: {
value: [String, Number],
apiUrl: { type: String, required: true },
apiMethod: { type: String, default: 'get' },
params: { type: Object, default: () => ({}) },
listKey: { type: String, required: true },
totalKey: { type: String, required: true },
labelKey: { type: String, required: true },
valueKey: { type: String, required: true },
searchKey: { type: String, default: 'keyword' },
pageSize: { type: Number, default: 10 },
placeholder: String,
disabled: Boolean,
debounceTime: { type: Number, default: 500 },
echoApi: { type: String, default: '' },
echoMethod: { type: String, default: 'get' },
},
data() {
return {
localValue: this.value,
optionList: [],
pageNum: 1,
loading: false,
hasMore: true,
searchText: '',
scrollEl: null,
dropdownVisible: false,
bindTimer: null,
total: 0,
};
},
watch: {
value(val) {
this.localValue = val;
this.initEcho();
},
localValue(val) {
this.$emit('input', val);
},
optionList() {
if (this.dropdownVisible && this.optionList.length > 0) {
this.$nextTick(() => {
this.bindScroll();
});
}
},
},
mounted() {
this.debounceSearch = this.debounce(this.fetchData, this.debounceTime);
this.initEcho();
},
beforeUnmount() {
this.cleanup();
},
methods: {
debounce(func, wait) {
let timeout = null;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
},
cleanup() {
if (this.scrollEl) {
this.scrollEl.removeEventListener('scroll', this.handleScroll);
this.scrollEl = null;
}
if (this.bindTimer) {
clearTimeout(this.bindTimer);
this.bindTimer = null;
}
},
findScrollContainer() {
const selectRef = this.$refs.selectRef;
if (!selectRef) return null;
const popper = selectRef.popperRef?.$el || selectRef.popperRef;
if (!popper) return null;
const wrap = popper.querySelector('.el-scrollbar__wrap');
if (wrap) return wrap;
const altWrap = popper.querySelector('.el-select-dropdown__wrap');
return altWrap || null;
},
bindScroll() {
this.cleanup();
const tryBind = (attempt = 1) => {
if (!this.dropdownVisible) return;
const scrollEl = this.findScrollContainer();
if (scrollEl) {
this.scrollEl = scrollEl;
scrollEl.addEventListener('scroll', this.handleScroll, { passive: true });
} else if (attempt < 5) {
this.bindTimer = setTimeout(() => tryBind(attempt + 1), 100 * attempt);
}
};
tryBind();
},
handleScroll(e) {
const { scrollTop, scrollHeight, clientHeight } = e.target;
const threshold = scrollHeight - clientHeight - 10;
if (this.loading || !this.hasMore) return;
if (scrollTop >= threshold) {
this.loadMore();
}
},
handleVisibleChange(visible) {
this.dropdownVisible = visible;
if (visible) {
this.reset();
this.fetchData().then(() => {
this.bindScroll();
});
} else {
this.cleanup();
}
},
handleSearch(val) {
this.searchText = val;
this.reset();
this.debounceSearch();
},
loadMore() {
if (this.loading || !this.hasMore) return;
this.pageNum++;
this.fetchData();
},
handleClear() {
this.localValue = '';
this.reset();
this.$emit('clear');
},
// 关键修改:选中后返回完整 item
handleChange(val) {
console.log('选中值:', val);
// 从当前列表查找
let selectedItem = this.optionList.find(item => item[this.valueKey] === val);
console.log('从列表找到:', selectedItem);
// 如果没找到且有回显接口,通过接口获取
if (!selectedItem && this.echoApi) {
console.log('列表未找到,通过接口获取');
this.getEchoData(val).then(item => {
console.log('接口返回:', item);
this.$emit('change', val, item);
});
} else {
// 确保发射事件
this.$nextTick(() => {
this.$emit('change', val, selectedItem);
});
}
},
// 新增:通过接口获取完整数据
async getEchoData(val) {
try {
const params = { [this.valueKey]: val };
let res;
if (this.echoMethod.toLowerCase() === 'post') {
res = await axios.post(this.echoApi, params);
} else {
res = await axios.get(this.echoApi, { params });
}
return res.data.data || {};
} catch (e) {
console.error('获取选中项详情失败', e);
return null;
}
},
async initEcho() {
const val = this.localValue;
if (!val || !this.echoApi) return;
const has = this.optionList.some(item => item[this.valueKey] === val);
if (has) return;
const info = await this.getEchoData(val);
if (info[this.labelKey] && info[this.valueKey]) {
const exist = this.optionList.some(i => i[this.valueKey] === info[this.valueKey]);
if (!exist) this.optionList.unshift(info);
}
},
async fetchData() {
this.loading = true;
try {
const sendData = {
pageNum: this.pageNum,
pageSize: this.pageSize,
[this.searchKey]: this.searchText,
...this.params,
};
let res;
if (this.apiMethod.toLowerCase() === 'post') {
res = await axios.post(this.apiUrl, sendData);
} else {
res = await axios.get(this.apiUrl, { params: sendData });
}
const data = res.data.data || {};
const list = data[this.listKey] || [];
const total = data[this.totalKey] || 0;
this.total = total;
if (this.pageNum === 1) {
this.optionList = list;
} else {
this.optionList = this.optionList.concat(list);
}
this.hasMore = this.optionList.length < total;
this.$nextTick(() => {
this.bindScroll();
});
} catch (e) {
console.error('请求失败', e);
} finally {
this.loading = false;
}
},
reset() {
this.pageNum = 1;
this.optionList = [];
this.hasMore = true;
this.total = 0;
this.cleanup();
},
},
};
</script>
<style lang="scss" scoped>
:deep(.el-select-dropdown) {
height: 200px !important;
overflow: hidden !important;
}
:deep(.el-select-dropdown .el-scrollbar__wrap) {
height: 300px !important;
overflow-y: auto !important;
overflow-x: hidden !important;
}
</style>