perf: enforce server-side pagination for tables and audit infinite scroll logic for selects

This commit is contained in:
DXC
2026-03-23 15:00:10 +08:00
parent 93dc375ba4
commit a522525ef4

View File

@ -110,6 +110,8 @@
style="width: 100%"
@change="onMaterialSelected"
default-first-option
v-loadmore="loadMoreMaterials"
popper-class="long-dropdown"
>
<el-option
v-for="item in materialOptions"
@ -122,6 +124,9 @@
<span class="opt-spec">{{ item.spec }}</span>
</div>
</el-option>
<div v-if="loadingMore" style="text-align: center; color: #999; font-size: 12px; padding: 8px; background: #f9f9f9;">
加载中...
</div>
</el-select>
</el-form-item>
</el-col>
@ -260,6 +265,26 @@ const hasFormFieldPermission = (fieldName: string) => {
return userStore.hasPermission(code)
}
// 自定义指令v-loadmore适配 Teleport 到 Body 的下拉框)
const vLoadmore = {
mounted(el: any, binding: any) {
const checkAndBind = () => {
const dropDownWrap = document.querySelector('.long-dropdown .el-select-dropdown__wrap')
if (dropDownWrap && !dropDownWrap.getAttribute('data-loadmore-bound')) {
dropDownWrap.setAttribute('data-loadmore-bound', 'true')
dropDownWrap.addEventListener('scroll', function (this: any) {
const condition = this.scrollHeight - this.scrollTop <= this.clientHeight + 1
if (condition) {
binding.value()
}
})
}
}
setTimeout(checkAndBind, 500)
el.addEventListener('click', () => setTimeout(checkAndBind, 300))
}
}
// 表格数据
const tableData = ref<ServiceItem[]>([])
const loading = ref(false)
@ -269,6 +294,10 @@ const total = ref(0)
const materialOptions = ref<any[]>([])
const searchLoading = ref(false)
const searchPage = ref(1)
const hasNextPage = ref(true)
const loadingMore = ref(false)
const searchKeyword = ref('')
const searchForm = reactive({
keyword: '',
@ -335,11 +364,15 @@ const handleMaterialDropdownVisible = (visible: boolean) => {
const handleSearchMaterial = async (query: string) => {
searchLoading.value = true
searchKeyword.value = query
searchPage.value = 1
materialOptions.value = []
try {
const res = await searchMaterialBase(query)
const res: any = await searchMaterialBase(query, 1)
if (res.code === 200) {
const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false }))
const apiResults = (res.data?.items || []).map((i: any) => ({ ...i, isHistory: false }))
materialOptions.value = apiResults
hasNextPage.value = res.data?.has_next ?? false
} else {
materialOptions.value = []
}
@ -350,6 +383,26 @@ const handleSearchMaterial = async (query: string) => {
}
}
const loadMoreMaterials = async () => {
if (searchLoading.value || loadingMore.value || !hasNextPage.value) return
loadingMore.value = true
searchPage.value += 1
try {
const res: any = await searchMaterialBase(searchKeyword.value, searchPage.value)
if (res.data && res.data.items && res.data.items.length > 0) {
const newItems = res.data.items.map((i: any) => ({ ...i, isHistory: false }))
materialOptions.value.push(...newItems)
hasNextPage.value = res.data.has_next
} else {
hasNextPage.value = false
}
} catch (e) {
searchPage.value -= 1
} finally {
loadingMore.value = false
}
}
const onMaterialSelected = (val: number) => {
const item = materialOptions.value.find(i => i.id === val)
if (item) {