组件
<template>
<el-autocomplete
v-model="selectedValue"
:fetch-suggestions="fetchSuggestions"
:placeholder="placeholder"
@select="handleSelect"
clearable
v-bind="$attrs"
/>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, withDefaults } from 'vue'
import type { AutocompleteProps, AutocompleteEmits } from 'element-plus'
interface AutocompleteItem {
value: string
[key: string]: any
}
interface Props {
placeholder?: string
debounce?: number
apiFn?: (query: string) => Promise<AutocompleteItem[]> // 模拟接口函数
localData?: AutocompleteItem[] // 本地数据
filterFn?: (query: string, item: AutocompleteItem) => boolean // 自定义过滤函数
}
const props = withDefaults(defineProps<Props>(), {
placeholder: '请输入内容',
debounce: 300,
localData: () => [],
filterFn: undefined
})
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'select', item: AutocompleteItem): void
}>()
const selectedValue = ref('')
const loading = ref(false)
const suggestions = ref<AutocompleteItem[]>([])
let timeout: number | undefined
// 默认过滤函数 - 开头匹配(不区分大小写)
const defaultFilter = (query: string) => {
const lowerQuery = query.toLowerCase()
return (item: AutocompleteItem) =>
item.value.toLowerCase().startsWith(lowerQuery)
}
// 模拟接口获取数据
const mockApiFetch = async (query: string): Promise<AutocompleteItem[]> => {
// 这里模拟API请求延迟
await new Promise(resolve => setTimeout(resolve, 500))
// 模拟数据
const mockData = [
{ value: 'Vue', link: 'https://vuejs.org' },
{ value: 'React', link: 'https://reactjs.org' },
{ value: 'Angular', link: 'https://angular.io' },
{ value: 'Svelte', link: 'https://svelte.dev' },
{ value: 'TypeScript', link: 'https://www.typescriptlang.org' },
]
// 模拟接口过滤
return mockData.filter(item =>
item.value.toLowerCase().includes(query.toLowerCase())
)
}
// 获取建议列表
const fetchSuggestions = async (
query: string,
cb: (arg: AutocompleteItem[]) => void
) => {
clearTimeout(timeout)
if (!query) {
cb(props.localData)
return
}
loading.value = true
timeout = setTimeout(async () => {
try {
let results: AutocompleteItem[] = []
if (props.apiFn) {
// 使用传入的接口函数获取数据
results = await props.apiFn(query)
} else if (props.localData.length) {
// 使用本地数据过滤
const filter = props.filterFn
? (item: AutocompleteItem) => props.filterFn!(query, item)
: defaultFilter(query)
results = props.localData.filter(filter)
} else {
// 使用默认模拟接口
results = await mockApiFetch(query)
}
suggestions.value = results
cb(results)
} catch (error) {
console.error('获取建议列表失败:', error)
cb([])
} finally {
loading.value = false
}
}, props.debounce) as unknown as number
}
// 选择事件
const handleSelect = (item: AutocompleteItem) => {
emit('update:modelValue', item.value)
emit('select', item)
}
// 暴露方法
defineExpose({
focus: () => {
// 这里可以访问el-autocomplete的方法
// 需要在实际使用时通过ref获取组件实例
}
})
</script>
<style scoped>
.el-autocomplete {
width: 100%;
}
</style>
基本用法(使用内置模拟接口)
<template>
<CustomAutocomplete v-model="selectedValue" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import CustomAutocomplete from './CustomAutocomplete.vue'
const selectedValue = ref('')
</script>
使用自定义接口函数
<template>
<CustomAutocomplete
v-model="selectedValue"
:api-fn="fetchDataFromApi"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import CustomAutocomplete from './CustomAutocomplete.vue'
const selectedValue = ref('')
const fetchDataFromApi = async (query: string) => {
// 这里可以替换为真实的API调用
console.log('查询:', query)
return [
{ value: `${query}-结果1`, id: 1 },
{ value: `${query}-结果2`, id: 2 }
]
}
</script>
自定义过滤逻辑
<template>
<CustomAutocomplete
v-model="selectedValue"
:local-data="techList"
:filter-fn="customFilter"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import CustomAutocomplete from './CustomAutocomplete.vue'
const selectedValue = ref('')
const techList = ref([
{ value: 'Vue.js', category: 'frontend' },
{ value: 'React', category: 'frontend' },
{ value: 'Express', category: 'backend' }
])
const customFilter = (query: string, item: any) => {
// 同时匹配value和category
return item.value.toLowerCase().includes(query.toLowerCase()) ||
item.category.toLowerCase().includes(query.toLowerCase())
}
</script>