介绍了一种通过 el-select
插槽实现表格样式数据展示的方案,可更直观地辅助用户选择。支持列配置、行数据绑定及自定义搜索,简洁高效,适用于复杂选择场景。完整代码见GitHub 仓库。
背景
在进行业务开发选择订单时,如果单纯的根据单号是无法编写是哪一条订单的,这个时候就可以通过表格的方式去展示这条订单的其他信息,辅助用户分辨出订单,不用再去查,更快速、更友好。
效果如下:
实现
环境与依赖
- node 22
- vue 3
- element-plus
思路
原理很简单,利用插槽。
虽然通过 el-table
可以实现表格效果,但对这种简单需求来说过于臃肿。而直接使用原生 table
标签结构实现太过于繁琐,不便于实现遍历 el-option
。
html
<el-select>
<!-- 表头,遍历 -->
<!-- 表体,遍历 -->
<el-option>
<!-- 行数据,遍历 -->
</el-option>
</el-select>
因此,我采用列表结构实现,结合 CSS 美化出表格效果。具体做法是遍历「列配置」生成表头,再遍历「options」生成行数据。
vue
<el-select
ref="inputRef"
v-model="selectValue"
clearable
filterable
:placeholder="placeholder"
:disabled="disabled"
:filter-method="filterMethod"
@change="handleChange"
>
<!-- 表头 -->
<ul class="select-ul">
<li v-for="column in columnConfig" :key="column.label">{{ column.label }}</li>
</ul>
<el-option v-for="item in showOptions" :key="item[keyNameCom]" :value="item[valueName]">
<ul class="select-ul select-ul-data">
<li v-for="column in columnConfig" :key="column.label">{{ item[column.prop] }}</li>
</ul>
</el-option>
</el-select>
其中,el-option
的数据绑定属性名默认是 id
,可通过配置修改。同时,遍历 key
属性支持自定义,若为空则默认使用 value
属性名,减少额外数据处理。
如何使用
一个简单演示。
options
:行数据columnConfig
:列配置
template
结构和 js
部分如下:
vue
<BaseTableSelect
v-model="selectValue"
:options="options"
:columnConfig="columnConfig"
>
</BaseTableSelect>
JavaScript
const options = ref([
{id: "213", department: '古典风格号', createTime: '2024-12-23', place: '学校'},
{id: "546", department: '都听好', createTime: '2024-12-23', place: '家里'},
{id: "345", department: '按时到岗', createTime: '2024-12-23', place: '医院'}
])
const columnConfig = ref([
{label: '单号', prop: 'id'},
{label: '部门', prop: 'department'},
{label: '时间', prop: 'createTime'},
{label: '地点', prop: 'place'},
])
自定义全表格搜索
组件 el-select
默认的搜索功能只会根据 label
属性的值去搜索,在表格展示的场景下并不符合,因此需要用到 filter-method
自定义搜索方法属性。
初步简单实现可使用 JSON.stringify()
将选项对象转为字符串,并检测是否包含搜索关键词:
js
const filterMethod = queryString => {
showOptions.value = props.options.filter(item => (JSON.stringify(item).includes(queryString) ? true : false))
}
不过,这种方式会将未展示的属性也纳入搜索,导致结果不准确。因此,优化后的搜索方法仅匹配已展示的列数据:
js
const filterMethod = queryString => {
if (!queryString) {
showOptions.value = props.options;
return;
}
showOptions.value = props.options.filter(item => {
// 针对每个要过滤的列进行判断
return props.columnConfig.some(config => {
const propValue = item[config.prop];
// 将属性值转换为字符串并检查是否包含查询字符串
return propValue && propValue.toString().includes(queryString);
});
});
}
扩展
本文提供的是基础实现。如果需要进一步功能扩展,例如控制搜索功能 (filterable
) 或其他交互行为,可通过额外配置实现。但基础版本已满足我的需求,便不写太多不利于阅读代码。
组件代码
vue
<template>
<el-select
ref="inputRef"
v-model="selectValue"
clearable
filterable
:placeholder="placeholder"
:disabled="disabled"
:filter-method="filterMethod"
@change="handleChange"
>
<!-- 表头 -->
<ul class="select-ul">
<li v-for="column in columnConfig" :key="column.label">{{ column.label }}</li>
</ul>
<el-option v-for="item in showOptions" :key="item[keyNameCom]" :value="item[valueName]">
<ul class="select-ul select-ul-data">
<li v-for="column in columnConfig" :key="column.label">{{ item[column.prop] }}</li>
</ul>
</el-option>
</el-select>
</template>
<script setup>
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
defineOptions({
name: 'BaseTableSelect'
})
const emit = defineEmits(['update:modelValue', 'keyup-enter', 'focus', 'change'])
const props = defineProps({
modelValue: null,
disabled: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: '请选择'
},
/* 列配置 */
columnConfig: {
type: Array,
default(rawProps) {
return []
}
},
options: {
type: Array,
default(rawProps) {
return []
}
},
valueName: {
type: String,
default: 'id'
},
keyName: {
type: String,
default: ''
}
})
const selectValue = computed({
get: () => props.modelValue,
set: val => {
emit('update:modelValue', val)
}
})
// 实际 keyName
// 优先使用 keyName,keyName 为空时,使用 valueName
const keyNameCom = computed(() => {
return props.keyName !== '' ? props.keyName : props.valueName
})
const handleChange = val => {
emit('change', val)
}
// ref
const inputRef = ref(null)
// 渲染用的options
const showOptions = ref(props.options)
watch(
() => props.options,
newValue => {
showOptions.value = newValue
selectValue.value = ''
},
)
// 筛选
const filterMethod = queryString => {
if (!queryString) {
showOptions.value = props.options;
return;
}
showOptions.value = props.options.filter(item => {
// 针对每个要过滤的列进行判断
return props.columnConfig.some(config => {
const propValue = item[config.prop];
// 将属性值转换为字符串并检查是否包含查询字符串
return propValue && propValue.toString().includes(queryString);
});
});
}
// 得到焦点
const getFocus = () => {
nextTick(() => {
inputRef.value.focus()
})
}
defineExpose({
getFocus
})
</script>
<style lang="scss" scoped></style>
<style scoped lang="scss">
.select-ul {
padding-right: 0;
list-style: none;
display: flex;
justify-content: space-between;
text-align: center;
padding-inline-start: 0;
padding: 0 20px;
> li {
display: inline-block;
width: 100px;
// margin: 6px;
overflow: hidden;
text-overflow: ellipsis;
}
}
.el-select-dropdown__item {
padding: 0;
}
</style>
属性
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
disabled | 是否禁用 | boolean | false |
placeholder | 请选择 | string | '请选择' |
columnConfig | 列配置,[{label: '', prop: ''}] | array | [] |
options | 选项 | array | [] |
valueName | 选项值的属性名 | string | 'id' |
keyName | 遍历选项时的 key |
string | '' |
参考
无