html
<template>
<el-form :model="form">
<el-form-item label="选择项">
<el-select
v-model="form.selectedValue"
placeholder="请选择"
@focus="loadOptions"
:loading="loading"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive } from 'vue'
const form = reactive({
selectedValue: ''
})
const options = ref([])
const loading = ref(false)
const hasLoaded = ref(false) // 标记是否已经加载过数据
const loadOptions = async () => {
// 如果已经加载过,则不再加载
if (hasLoaded.value) {
return
}
loading.value = true
try {
// 模拟异步请求
const data = await fetchOptions()
options.value = data
hasLoaded.value = true // 标记已加载
} catch (error) {
console.error('加载选项失败', error)
} finally {
loading.value = false
}
}
const fetchOptions = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' },
{ label: '选项3', value: '3' }
])
}, 500)
})
}
</script>
一般由el-select 的焦点事件处理和数据加载时机导致的
解决方案一:使用 @focus 事件加载数据,优化处理逻辑
如代码中的方案2
html
<template>
<el-form :model="form">
<el-form-item label="选择项">
<el-select
v-model="form.selectedValue"
placeholder="请选择"
filterable
@focus="handleFocus"
@visible-change="handleVisibleChange"
:remote="true"
:remote-method="remoteMethod"
:loading="loading"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
const form = reactive({
selectedValue: ''
})
const options = ref([])
const loading = ref(false)
const hasLoaded = ref(false) // 标记是否已加载过数据
// 方案1:使用 visible-change 事件
const handleVisibleChange = (visible) => {
if (visible && !hasLoaded.value) {
loadOptions()
}
}
// 方案2:优化 focus 事件处理
const handleFocus = () => {
if (!hasLoaded.value) {
loadOptions()
}
}
const loadOptions = async () => {
if (loading.value || hasLoaded.value) return
loading.value = true
try {
// 模拟异步请求
const data = await fetchOptions()
options.value = data
hasLoaded.value = true // 标记已加载
} catch (error) {
console.error('加载选项失败', error)
} finally {
loading.value = false
}
}
const fetchOptions = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ label: '选项1', value: '1' },
{ label: '选项2', value: '2' },
{ label: '选项3', value: '3' }
])
}, 500)
})
}
// 远程搜索方法
const remoteMethod = (query) => {
if (query !== '') {
loading.value = true
setTimeout(() => {
loading.value = false
options.value = [
{ label: `搜索: ${query}1`, value: 'search1' },
{ label: `搜索: ${query}2`, value: 'search2' }
]
}, 200)
} else {
options.value = []
}
}
</script>
解决方案二:使用 :remote="true" 和缓存机制
html
<template>
<el-select
v-model="form.selectedValue"
placeholder="请选择"
filterable
remote
:remote-method="handleSearch"
:loading="loading"
@focus="handleSelectFocus"
reserve-keyword
>
<el-option
v-for="item in filteredOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
const form = reactive({
selectedValue: ''
})
const allOptions = ref([]) // 缓存所有数据
const filteredOptions = ref([])
const loading = ref(false)
const isInitialLoad = ref(true)
// 计算属性,确保选中值对应的选项存在
const computedOptions = computed(() => {
const options = [...filteredOptions.value]
// 如果当前选中的值不在选项中,添加该选项(用于显示已选中的值)
if (form.selectedValue && !options.some(opt => opt.value === form.selectedValue)) {
// 这里可以从缓存中查找,或者添加一个临时选项
const cached = allOptions.value.find(opt => opt.value === form.selectedValue)
if (cached) {
options.unshift(cached)
}
}
return options
})
const handleSelectFocus = async () => {
if (isInitialLoad.value) {
await loadInitialOptions()
isInitialLoad.value = false
}
}
const loadInitialOptions = async () => {
loading.value = true
try {
const data = await fetchOptions()
allOptions.value = data
filteredOptions.value = data
} finally {
loading.value = false
}
}
const handleSearch = (query) => {
if (query) {
loading.value = true
setTimeout(() => {
loading.value = false
filteredOptions.value = allOptions.value.filter(item =>
item.label.toLowerCase().includes(query.toLowerCase())
)
}, 200)
} else {
// 清空搜索时,显示所有选项
filteredOptions.value = [...allOptions.value]
}
}
</script>
解决方案三:使用 watch 监听变化并确保数据存在
html
<template>
<el-select
v-model="form.selectedValue"
placeholder="请选择"
@focus="loadOptions"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script setup>
import { ref, reactive, watch, onMounted } from 'vue'
const form = reactive({
selectedValue: ''
})
const options = ref([])
const loaded = ref(false)
// 监听选中值的变化,确保选项存在
watch(() => form.selectedValue, (newVal) => {
if (newVal && loaded.value) {
ensureOptionExists(newVal)
}
})
// 确保选中的值在选项列表中
const ensureOptionExists = (value) => {
const exists = options.value.some(opt => opt.value === value)
if (!exists) {
// 可以添加一个临时选项,或者重新加载数据
reloadOptionsIfNeeded()
}
}
const loadOptions = async () => {
if (loaded.value) return // 已加载则不再重复加载
try {
const data = await fetchOptions()
options.value = data
loaded.value = true
} catch (error) {
console.error('加载失败', error)
}
}
const reloadOptionsIfNeeded = async () => {
try {
const data = await fetchOptions()
options.value = data
} catch (error) {
console.error('重新加载失败', error)
}
}
onMounted(() => {
// 组件挂载时预加载数据
loadOptions()
})
</script>
解决方案四:使用 v-if 控制重新渲染(如果以上方案都不能解决)
html
<template>
<el-select
v-if="selectKey"
:key="selectKey"
v-model="form.selectedValue"
placeholder="请选择"
@focus="handleFocus"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script setup>
import { ref, reactive } from 'vue'
const form = reactive({
selectedValue: ''
})
const options = ref([])
const selectKey = ref(1) // 用于强制重新渲染的key
const handleFocus = async () => {
await loadOptions()
// 强制重新渲染select组件
selectKey.value += 1
}
const loadOptions = async () => {
if (options.value.length > 0) return
const data = await fetchOptions()
options.value = data
}
</script>
-
使用
@visible-change替代@focus- 更符合下拉框显示/隐藏的时机 -
添加加载状态标记 - 避免重复加载
-
使用
:remote="true"- Element Plus 内置的远程搜索功能更稳定 -
确保选中值在选项中 - 通过计算属性或监听器处理
-
推荐方案1