<template>
<!-- 使用transition组件实现下拉框的显示/隐藏动画效果 -->
<!-- 动画名称为el-zoom-in-top,表示从上方缩放进入 -->
<!-- @after-leave="doDestroy":当元素完全隐藏(离开动画结束后),触发doDestroy方法清理浮层 -->
<transition name="el-zoom-in-top" @after-leave="doDestroy">
<!-- 根元素div作为自动补全建议列表的容器 -->
<!-- v-show="showPopper":控制下拉建议框的显隐,由父组件控制 -->
<!-- class绑定基础类名 el-autocomplete-suggestion 和 el-popper(浮层通用样式) -->
<!-- :class 动态绑定是否处于加载状态:当parent.hideLoading为false且parent.loading为true时,添加is-loading类 -->
<!-- :style="{ width: dropdownWidth }":动态设置下拉框宽度,与输入框保持一致 -->
<!-- role="region":ARIA属性,辅助技术识别该区域为一个独立的界面区域 -->
<div
v-show="showPopper"
class="el-autocomplete-suggestion el-popper"
:class="{ 'is-loading': !parent.hideLoading && parent.loading }"
:style="{ width: dropdownWidth }"
role="region"
>
<!-- 使用ElScrollbar组件包裹建议列表,支持滚动 -->
<!-- tag="ul":将滚动容器渲染为ul标签语义 -->
<!-- wrap-class 设置滚动外层包裹容器的类名 -->
<!-- view-class 设置滚动内容区的类名 -->
<el-scrollbar
tag="ul"
wrap-class="el-autocomplete-suggestion__wrap"
view-class="el-autocomplete-suggestion__list"
>
<!-- 加载状态提示:当未隐藏加载图标且父组件loading为true时显示loading图标 -->
<li v-if="!parent.hideLoading && parent.loading">
<i class="el-icon-loading"></i>
</li>
<!-- 非加载状态下,插槽用于接收外部传入的建议项内容(通常是li列表项) -->
<!-- slot标签已被废弃,此处保留兼容性写法,实际会被Vue编译为默认插槽内容 -->
<slot v-else></slot>
</el-scrollbar>
</div>
</transition>
</template>
<script>
// 导入 Popper 工具库,用于处理浮层定位(如弹出层、下拉菜单等)
// 实现基于 popper.js 的定位逻辑,确保下拉框能正确对齐输入框
import Popper from 'element-ui/src/utils/vue-popper';
// 导入 Emitter 混入对象,提供 dispatch 和 broadcast 方法
// 用于向上派发事件或向下广播事件,在组件树中进行通信
import Emitter from 'element-ui/src/mixins/emitter';
// 导入滚动条组件 ElScrollbar,用于在建议过多时提供滚动能力
import ElScrollbar from 'element-ui/packages/scrollbar';
export default {
// 注册使用的子组件
components: { ElScrollbar },
// 使用混入(mixins)复用功能
// Popper 提供浮层定位能力
// Emitter 提供跨层级组件通信能力
mixins: [Popper, Emitter],
// 组件名称,用于调试和递归组件调用(非必须但推荐)
componentName: 'ElAutocompleteSuggestions',
// 响应式数据定义
data() {
return {
// 引用父组件实例,便于直接访问其属性和方法
parent: this.$parent,
// 下拉框宽度,初始化为空字符串,后续根据输入框宽度动态设置
dropdownWidth: ''
};
},
// 接收外部传入的属性(props)
props: {
// Popper.js 的配置选项,默认关闭GPU加速(避免某些浏览器渲染问题)
options: {
default() {
return {
gpuAcceleration: false
};
}
},
// 为建议列表分配唯一ID,用于无障碍访问(ARIA)中的关联
id: String
},
// 方法集合
methods: {
/**
* 处理建议项被选中的逻辑
* @param {Object} item - 被点击的建议项数据
* 通过 dispatch 向上派发事件到 ElAutocomplete 组件,通知其处理选中行为
*/
select(item) {
this.dispatch('ElAutocomplete', 'item-click', item);
}
},
// 生命周期钩子:组件更新后调用
// 确保 DOM 已重新渲染后,更新浮层位置(防止错位)
updated() {
this.$nextTick(() => {
// 判断 popperJS 实例是否存在,存在则调用 updatePopper 更新位置
if (this.popperJS) {
this.updatePopper();
}
});
},
// 生命周期钩子:组件挂载完成后调用
// 初始化浮层相关元素引用,并设置必要的DOM属性
mounted() {
// 将当前组件的根元素 $el 赋值给父组件的 popperElm 和自身的 popperElm
// 以便 Popper 混入可以获取浮层元素
this.$parent.popperElm = this.popperElm = this.$el;
// 获取参考元素(即输入框),优先取 input 或 textarea 元素
this.referenceElm =
this.$parent.$refs.input.$refs.input || this.$parent.$refs.input.$refs.textarea;
// 获取建议列表的滚动内容区域
this.referenceList = this.$el.querySelector('.el-autocomplete-suggestion__list');
// 为列表添加 ARIA 语义化角色 listbox,提升可访问性
this.referenceList.setAttribute('role', 'listbox');
// 为列表设置唯一的 ID,与输入框的 aria-owns 关联
this.referenceList.setAttribute('id', this.id);
},
// 生命周期钩子:组件创建完成时调用
// 监听来自父组件的 'visible' 事件,控制下拉框显隐和宽度
created() {
// 使用 $on 监听事件总线上的 'visible' 事件
// 参数 val 表示是否显示,inputWidth 是输入框宽度
this.$on('visible', (val, inputWidth) => {
// 动态设置下拉框宽度,加上 'px' 单位
this.dropdownWidth = inputWidth + 'px';
// 控制浮层显隐
this.showPopper = val;
});
}
};
</script>
<template>
<!-- 使用 transition 组件实现下拉框的显示/隐藏动画效果,名称为 el-zoom-in-top -->
<!-- @after-leave:动画结束后触发 doDestroy 方法,用于销毁 Popper 实例 -->
<transition name="el-zoom-in-top" @after-leave="doDestroy">
<!-- v-show 控制下拉建议框的显隐状态 -->
<!-- class 固定为 el-autocomplete-suggestion 和 el-popper,基础样式类 -->
<!-- 动态 class:当父组件未隐藏加载图标且 loading 状态为 true 时,添加 is-loading 类 -->
<!-- :style 动态绑定宽度,确保下拉框与输入框宽度一致 -->
<!-- role="region" 提高可访问性,表示这是一个页面区域 -->
<div
v-show="showPopper"
class="el-autocomplete-suggestion el-popper"
:class="{ 'is-loading': !parent.hideLoading && parent.loading }"
:style="{ width: dropdownWidth }"
role="region"
>
<!-- 使用 ElScrollbar 滚动组件包裹列表内容,支持滚动 -->
<!-- tag="ul" 表示最外层渲染为 ul 标签 -->
<!-- wrap-class 设置滚动容器的类名 -->
<!-- view-class 设置滚动视图的类名 -->
<el-scrollbar
tag="ul"
wrap-class="el-autocomplete-suggestion__wrap"
view-class="el-autocomplete-suggestion__list"
>
<!-- 加载状态:若父组件不隐藏加载图标且处于 loading,则显示加载动画 -->
<li v-if="!parent.hideLoading && parent.loading">
<i class="el-icon-loading"></i>
</li>
<!-- 非加载状态下插槽内容(即实际的建议项列表) -->
<!-- slot 默认插槽,由 autocomplete.vue 中的 scoped slot 提供具体选项渲染逻辑 -->
<slot v-else></slot>
</el-scrollbar>
</div>
</transition>
</template>
<script>
// 导入依赖模块:
// Popper:提供下拉定位能力,基于 popper.js 实现元素定位
// Emitter:事件通信混入,支持向上派发事件(dispatch)
// ElScrollbar:Element UI 的滚动条组件,用于美化滚动区域
import Popper from 'element-ui/src/utils/vue-popper';
import Emitter from 'element-ui/src/mixins/emitter';
import ElScrollbar from 'element-ui/packages/scrollbar';
export default {
// 注册子组件
components: { ElScrollbar },
// 使用混入(mixins)复用代码逻辑
// Popper 提供弹出层定位功能
// Emitter 提供父子组件间事件通信能力
mixins: [Popper, Emitter],
// 当前组件的名称,用于调试和递归引用
componentName: 'ElAutocompleteSuggestions',
// 响应式数据定义
data() {
return {
// 引用父组件(即 autocomplete.vue 实例),便于直接访问其属性和方法
parent: this.$parent,
// 下拉框宽度,初始为空,后续通过监听事件动态设置
dropdownWidth: ''
};
},
// 接收外部传入的属性(props)
props: {
// Popper.js 的配置选项,默认关闭 GPU 加速(避免某些浏览器渲染问题)
options: {
default() {
return {
gpuAcceleration: false
};
}
},
// 下拉列表的唯一标识 id,用于 ARIA 可访问性支持
id: String
},
// 方法集合
methods: {
/**
* 处理建议项被选中的逻辑
* 调用 dispatch 向上级组件(ElAutocomplete)发送 'item-click' 事件
* 并传递当前选中的 item 数据
* @param {Object} item - 当前点击的建议项数据
*/
select(item) {
this.dispatch('ElAutocomplete', 'item-click', item);
}
},
// 生命周期钩子:组件更新后调用
// 确保 DOM 已重新渲染后,更新 Popper 定位以应对位置变化
updated() {
this.$nextTick(() => {
// 判断 popperJS 实例是否存在,存在则调用 update 方法刷新定位
if (this.popperJS) {
this.updatePopper();
}
});
},
// 生命周期钩子:组件挂载完成后执行
// 初始化 Popper 相关的 DOM 元素引用,并设置语义化标签
mounted() {
// 将当前组件的根元素 $el 赋值给父组件的 popperElm 和自身的 popperElm
this.$parent.popperElm = this.popperElm = this.$el;
// 获取父组件中 input 或 textarea 的原生 DOM 引用作为 reference 元素(定位参考)
this.referenceElm =
this.$parent.$refs.input.$refs.input || this.$parent.$refs.input.$refs.textarea;
// 获取建议列表 DOM 节点,用于后续属性设置
this.referenceList = this.$el.querySelector('.el-autocomplete-suggestion__list');
// 为列表添加 ARIA 语义化角色 listbox,提升无障碍访问体验
this.referenceList.setAttribute('role', 'listbox');
// 设置列表的 id 属性,与输入框 aria-owns 对应,建立关联
this.referenceList.setAttribute('id', this.id);
},
// 生命周期钩子:组件创建完成时执行
// 监听来自父组件的 'visible' 事件,控制下拉框显隐和宽度
created() {
this.$on('visible', (val, inputWidth) => {
// 根据事件参数设置下拉框宽度(像素字符串格式)
this.dropdownWidth = inputWidth + 'px';
// 控制 Popper 显示/隐藏状态
this.showPopper = val;
});
}
};
</script>