介绍了一种通过 el-select
插槽实现表格样式数据展示的方案,可更直观地辅助用户选择。支持列配置、行数据绑定及自定义搜索,简洁高效,适用于复杂选择场景。完整代码见GitHub 仓库。
背景
在进行业务开发选择订单时,如果单纯的根据单号是无法编写是哪一条订单的,这个时候就可以通过表格的方式去展示这条订单的其他信息,辅助用户分辨出订单,不用再去查,更快速、更友好。
效果如下:
实现
环境与依赖
- node 22
- vue 3
- element-plus
思路
原理很简单,利用插槽。
虽然通过 el-table
可以实现表格效果,但对这种简单需求来说过于臃肿。而直接使用原生 table
标签结构实现太过于繁琐,不便于实现遍历 el-option
。
1 2 3 4 5 6 7
| <el-select> <el-option> </el-option> </el-select>
|
因此,我采用列表结构实现,结合 CSS 美化出表格效果。具体做法是遍历「列配置」生成表头,再遍历「options」生成行数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <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
部分如下:
1 2 3 4 5 6
| <BaseTableSelect v-model="selectValue" :options="options" :columnConfig="columnConfig" > </BaseTableSelect>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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()
将选项对象转为字符串,并检测是否包含搜索关键词:
1 2 3
| const filterMethod = queryString => { showOptions.value = props.options.filter(item => (JSON.stringify(item).includes(queryString) ? true : false)) }
|
不过,这种方式会将未展示的属性也纳入搜索,导致结果不准确。因此,优化后的搜索方法仅匹配已展示的列数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
) 或其他交互行为,可通过额外配置实现。但基础版本已满足我的需求,便不写太多不利于阅读代码。
组件代码
仓库:🔗 el-select-table-option
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| <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 |
‘’ |
参考
无