因业务需求,需要封装底层picker组件,组件需要实时请求接口筛选出最新数据,同时要含有自定义表头,label,id支持自定义。之后扩展业务组件的时候,可以基于这个底层组件再扩展一些自定义的功能,这样就大大减少了业务组件开发效率。
基于Vue2 + TypeScript + ElementUI的下拉组件
一、使用方法
1、举个例子,封装一个产品组件SkuPicker
SkuPicker.vue
<template>
<div class="sku-picker">
<select-picker
v-model="selected"
:attr-for-label="attrForLabel"
:attr-for-value="attrForValue"
:placeholder="placeholder"
:columns="columns"
:multiple="multiple"
:clearable="clearable"
:query-by-keyword-url="_getKeywordUrl()"
:query-by-value-url="_getValueUrl()"
:is-view="isView"
:size="size"
:width="width"
@changeValue="onChange"
@changeItems="onChangeItems"
@changeItem="onChangeItem"
@focus="_focus"
/>
</div>
</template>
2、使用组件
<sku-picker
v-model="productId"
@change="skuChanged"
:multiple="true"
></sku-picker>
效果
二、具体底层picker代码
SelectPicker.vue
<template>
<div :style="{ width: `${width}px` }">
<el-select
v-if="!isView"
ref="SelectPicker"
v-model="selectVal"
:size="size"
:style="{ width: `${width}px` }"
:popper-append-to-body="popperAppendToBody"
:multiple="multiple"
:filterable="filterable"
:placeholder="placeholder"
:value-key="attrForValue"
:multiple-limit="multipleLimit"
:filter-method="filterMethod || filterMethodHandle"
:clearable="clearable"
@remove-tag="removeTag"
@focus="_focus"
@clear="clearAll"
>
<template>
<el-table
class="table"
:data.sync="tableData"
max-height="300"
:row-class-name="tableRowClassName"
@row-click="rowClick"
>
<template v-for="(item, index) in columns">
<el-table-column
:key="index + 'i'"
:type="item.type"
:label="item.label"
:prop="item.prop"
:min-width="item['min-width'] || item.minWidth || item.width"
:align="item.align || 'center'"
:fixed="item.fixed"
v-bind="{ ...item.bind, ...$attrs }"
v-on="$listeners"
>
<template slot-scope="scope">
<!-- 作用域插槽 -->
<template v-if="item.slotName">
<slot :name="item.slotName" :scope="scope"></slot>
</template>
<div v-if="!item.render && !item.slotName">
<span>{{ scope.row[item.prop] }}</span>
<el-option
v-show="false"
:key="labelComputed(scope.row, attrForValue)"
:label="labelComputed(scope.row, attrForLabel)"
:value="scope.row"
></el-option>
</div>
</template>
</el-table-column>
</template>
<slot></slot>
</el-table>
</template>
</el-select>
<div v-else class="view-text">
{{ getViewLabels(selectVal) }}
</div>
</div>
</template>
组件使用绑定值为对象类型来写,通过value-key作为 value 唯一标识的键名,即外层组件传入attrForValue
因为绑定的是对象类型,这里的el-select里面一定是要嵌套el-option,目的是为了能正确回显,但是我们并不需要el-option来显示下拉数据,所以这里做了个隐藏
1、自定义label显示
用attrForLabel,外层组件可以传个函数,例如:
const attrForLabel = (item: Record<string, string>) => {
return item ? `${item.category}|${item.designation}` : ''
}
2、关键字筛选
通过外层组件传queryByKeywordUrl来查询最新数据
async filterMethodHandle(query?: string) {
this.loading = true
if (!this.filterable) return
const res = await get(this.queryByKeywordUrl, { keyword: query }, false)
if (res.data && isArray(res.data)) {
this.tableData = res.data
} else {
this.tableData = []
}
this.loading = false
}
3、回显数据
当有值要回显的时候,可以通过接口queryByValueUrl来查询需要回显数据
async queryByValueData(ids: number | string | Array<any>) {
this.loading = true
const res = await get(this.queryByValueUrl, { ids }, false)
if (!res.data && isArray(res.data)) {
this.tableData = res.data
if (!res.data || res.data.length <= 0) return
if (this.multiple) { // 多选
const items: Array<Record<string, any>> = []
this.selectVal = this.tableData.map((e) => {
items.push(e)
return e
})
this.selectItems = items
} else { // 单选
const item = this.tableData[0]
this.selectVal = item
this.selectItems = [item]
this.selectItem = item
}
this.emitEvent()
}
this.loading = false
}
4、选中时数据处理
判断单选或者多选,把处理好的数据同步更新到外层组件
rowClick(item) {
if (this.multiple) {// 多选
// 判断当前选中的数据是否已经选中了
const flag = this.selectItems.some(
(e) => e[this.attrForValue] === item[this.attrForValue]
)
if (flag) {
this.close()
return
}
this.selectVal = Array.from(new Set([...this.selectItems, item]))
this.selectItems = Array.from(new Set([...this.selectItems, item]))
} else {
this.selectVal = item
this.selectItem = item
this.selectItems = [item]
}
this.emitEvent()
this.close()
}
5、多选时删除移除tag值
el-select组件有个remove-tag事件,我们可以利用remove-tag事件拿到删除的对象,通过外层传入的attrForValue键值进行数据处理
removeTag(tag) {
if (this.multiple) {
const rowIndex = this.selectItems.findIndex(
(item) => item[this.attrForValue] === tag[this.attrForValue]
)
this.selectItems.splice(rowIndex, 1)
} else {
this.selectItems = []
this.selectItem = {}
}
this.emitEvent()
}
6、处理label或者value显示
labelComputed(
item: Record<string, any>,
attrForLabel: string | Function
): string {
if (util.isEmptyObject(item)) return ''
if (util.isFunction(attrForLabel)) {
return attrForLabel && (attrForLabel as Function)(item)
} else {
return item[attrForLabel as string]
}
}
7、统一抛出事件
emitEvent() {
const ids = this.selectItems.map((e) => e[this.attrForValue])
this.selectValue = ids.join(',')
this.$emit(CHANGE_ITEM_EVENT, this.selectItem) // 单选抛出对象
this.$emit(CHANGE_ITEMS_EVENT, this.selectItems)// 抛出数组
this.$emit(UPDATE_MODEL_EVENT, this.selectValue)
this.$emit(INPUT_EVENT, this.selectValue)
}