demo :

https://codepen.io/bangking007/pen/qGNxvv

核心代码:

comboGrid.js

Vue.component("comboxGrid", {
            props: {
                // input的数据源(v-model)
                value: {
                    type: String,
                    default: ''
                },
                // 下拉表格的数据源(由JObject组成的数组)
                rowSource: {
                    type: Array,
                    required: true
                },
                // 当搜索成功或选取行数据后,将要获取其中的字段值写入【fylSource】的字段名,如果没有指定,将默认使用第一列
                returnFld: {
                    type: String
                },
                // 下拉表格各列的定义,如[{field:"code",title:"编号",width:100},...]
                columns: {
                    type: Array,
                    required: true
                },
                // 输入框是否显示清除按钮,默认为true
                clearable: {
                    type: Boolean,
                    default: true
                },
                // 输入框是否只读,默认 false
                readonly: {
                    type: Boolean,
                    default: false
                },
                // 是否框是否为Disabled,默认 false
                inputDisabled: {
                    type: Boolean,
                    default: false
                },
                // 是否得到焦点时才允许显示下拉按钮和清除按钮(有值并且不是只读)
                // 默认为否,即不管是否得到焦点,都允许显示
                isSparse: {
                    type: Boolean,
                    default: false
                },
                // 更新数据前的回调函数,如果返回false,将不更新
                beforeUpdate: {
                    type: Function
                }
            },
            data() {
                return {
                    // 本组件中的input元素 绑定的数据源v-model,初始值是本组件传入的v-model
                    controlSource: "test",
                    // 内部使用,如果mounted后,会自动为true
                    isMounted: false,
                    //内部使用,指示本组件中的el-popover是否显示
                    isShow: false,
                    //本组件的input元素得到焦点时为true,否则为false
                    isInputFocus: false,
                    //el-popover得到焦点时为true,否则为false
                    isPopFocus: false,
                    // 是否过滤,当点击下拉按钮或按F4,将显示全部,并且定位到相关的行
                    isFilter: true,
                    // 下拉表格最后选中的当前行数据
                    currentRow: {
                        recno: -1,
                        data: null
                    },
                    // 内部使用,当点击了清除按钮,会触发为true,从而改变了controlSource,controlSource的监控器会自动把它reset为false
                    clearing: false,
                    // 内部请求hide的时候,会触发为true,从而改变了controlSource,controlSource的监控器会自动把它reset为false
                    // 其实用watch也可以实现,目的是减少watch的滥用
                    hiding: false,
                    // 内部使用,当本组件的vmodel数据源发生变化是,此值为true
                    vmodel_changing: false

                }
            },
            mounted() {
                const that = this;
                that.$nextTick(function() {
                    // 初始input元素的值
                    that.controlSource = that.value;
                    // 禁止下拉按钮具有焦点
                    let $btnContainer = $(that.$el).find("i").parent();
                    if ($btnContainer.length > 0) {
                        $btnContainer[0].removeAttribute("tabindex");
                    }
                });
            },
            computed: {
                // 当input或pop得点焦点时为true,否则为false(如果为false,强制隐藏pop)
                isFocus() {
                    const that = this;
                    let result = that.isInputFocus || that.isPopFocus;
                    return result;
                },
                // 清除按钮的动态显示实现
                showClear() {
                    const that = this;

                    return that.clearable &&
                        !that.inputDisabled &&
                        !that.readonly &&
                        (!that.isSparse || that.isSparse && that.isFocus) &&
                        ((that.controlSource + "").length > 0);
                },
                // 下拉钮按的动态显示实现(如果readonly和disabled都为flase时,返回true)
                showDropDown() {
                    const that = this;
                    return !that.inputDisabled &&
                        !that.readonly &&
                        (!that.isSparse || that.isSparse && that.isFocus)
                },
                // 实时过滤结果,则根据input元素的内容,在数组rowSource模糊过滤返回数组
                searchResult: function() {
                    const that = this;
                    // 如果没有dataGrid显示,不搜索
                    if (!that.isShow) {
                        return [];
                    }
                    let result = that.rowSource,
                        expr = that.controlSource.trim(),
                        recno = -1;
                    if (expr) {
                        if (this.isFilter) {
                            // 模糊过滤结果
                            const reg = new RegExp(expr, "i");
                            result = that.rowSource.filter(item => {
                                return that.columns.some(column => {
                                    return reg.test(String(item[column.field]))
                                })
                            });
                        }
                    }
                    return result;
                }
            },
            watch: {
                controlSource: function(curVal, oldVal) {
                    const that = this;
                    if (!that.isMounted) {
                        that.isMounted = true;
                    } else {
                        if (!that.isShow && that.isFocus && that.showDropDown) {
                            // 如果是因为点击清除按钮的就不触发
                            if (that.clearing || that.hiding || that.vmodel_changing) {
                                that.clearing = that.hiding = that.vmodel_changing = false;
                            } else {
                                that.show();
                            }
                        }
                        // 如果pop已经显示,搜索完全匹配的一行,如果没有找到,就取模糊过滤结果的第一行作为当前行
                        if (that.isShow) {
                            let recno = that.seek();
                            that.setCurrentRow(recno);
                        }
                    }
                    if (!that.isFilter) {
                        that.isFilter = true;
                    }
                },
                value: function(curVal, oldVal) {
                    if (curVal !== this.controlSource) {
                        this.vmodel_changing = true;
                        this.controlSource = curVal;
                    }
                },
                // 当本组件失去焦点时,验证input元素的值是否是rowSource中的,并且强制隐藏pop
                isFocus: function(curVal, oldVal) {
                    if (!curVal && oldVal) {
                        const that = this;
                        //失去焦点,更新绑定的数据源
                        that.valid();
                        that.hide();
                    }
                }
            },
            methods: {
                // 显示或隐藏pop的方法,并且强制使input元素得到焦点
                toggle(e) {
                    this.reset();
                    this.isShow = !this.isShow
                    this.focus();
                    if (this.isShow) {
                        let recno = this.seek();
                        this.setCurrentRow(recno);
                        this.isFilter = false;
                    }
                },
                // 隐藏pop
                hide() {
                    if (this.isShow) {
                        this.reset();
                        this.isShow = false;
                    }
                },
                //显示pop
                show() {

                    if (!this.isShow && this.showDropDown) {
                        this.reset();
                        this.isShow = true;
                    }
                },
                // 使弹出框每次都引用本控件
                reset() {
                    let pop = this.$refs.elpopover;
                    pop.referenceElm = this.$el;
                },
                // 后备用
                refresh(e) {
                    const that = this;
                },
                //input元素按下回车发生的事件
                enter(e) {
                    // 回车时,如果弹窗已打开,默认使用选定的
                    const that = this;
                    if (that.isShow) {
                        //阻止默认行为
                        e.preventDefault();
                        //阻止冒泡                        
                        e.stopPropagation();
                        let fld = that.returnFld || that.columns[0].field;
                        if (fld in that.currentRow.data && that.currentRow.data[fld] !== that.controlSource) {
                            that.hiding = true;
                            that.controlSource = that.currentRow.data[fld];
                        }
                        //that.valid();
                        that.hide();
                    }
                },
                // 下拉按钮按了空格键或input按了空格键触发(不知道为什么keydown.space.stop阻止不了)
                onSpace(e) {
                    const that = this;
                    let stop = true,
                        toggle = true;
                    if (e.target.nodeName.toLowerCase() === "input") {
                        // input按了空格键
                        if (that.controlSource.trim()) {
                            stop = false;
                        }
                        toggle = false;
                    }
                    if (stop) {
                        //阻止默认行为
                        e.preventDefault();
                        //阻止冒泡                        
                        e.stopPropagation();
                        if (toggle) {
                            that.toggle()
                        }
                    }
                },
                // 设置pop中的grid的高亮行(即选定的行)
                // tRecno:将要高亮的行
                // tDirection:滚动方向
                setCurrentRow(tRecno, tDirection) {
                    const that = this,
                        result = that.searchResult;
                    tRecno = (tRecno >= 0 && tRecno < result.length) ? tRecno : -1;
                    let oldCurrentRow = that.currentRow;
                    that.currentRow = {
                        recno: tRecno,
                        data: (tRecno >= 0) ? result[tRecno] : null
                    };

                    that.$refs.dataGrid.setCurrentRow(that.currentRow.data);
                    // 如果过滤的集合行数大于1,有可能将要定位的那行未能显示,故要滚动到这行
                    if (result.length > 1 && tRecno >= 0 && typeof(tDirection) === "number") {
                        const tableWrapper = that.$refs.dataGrid.$el.getElementsByClassName("el-table__body-wrapper")[0];
                        const table = tableWrapper.getElementsByTagName(
                            "table")[0];

                        const direction = (tDirection < 0);
                        let scrollIntoView = () => {
                            // 下滚false,上滚 true

                            if (!that.isElementInViewport(tableWrapper, table.rows[tRecno])) {
                                table.rows[tRecno].scrollIntoView(direction);
                            }
                        };
                        if (table.rows.length <= 0) {
                            let tryCount = 10;
                            let interval = setInterval(() => {
                                if (table.rows.length > 0) {
                                    if (tRecno < table.rows.length) {
                                        scrollIntoView();
                                        tryCount = -1;
                                    }
                                }
                                tryCount--;
                                if (tryCount < 0) {
                                    clearInterval(interval);
                                }
                            }, 100);
                        } else {
                            scrollIntoView();
                        }
                    }

                    //trList[that.currentRow.recno].scrollIntoView(true);
                    //console.info(that.$refs.dataGrid.$el.getElementsByClassName("el-table__body-wrapper"))
                    //console.info(that.$refs.dataGrid.$el.getElementsByTagName())
                    //anchor.scrollIntoView(true);
                },
                // 获取本组件的input元素
                getInput() {
                    return this.$el.getElementsByTagName("input")[0];
                },
                // 使本组件的input元素得到焦点的方法
                focus() {
                    let input = this.getInput();
                    input.focus()
                    input.select();
                },
                // 清除按钮触发的方法
                clear(e) {
                    this.clearing = true;
                    this.controlSource = "";
                    this.$emit('clear');
                    this.valid();
                    this.focus();
                },
                // 模糊搜到的字符串改变颜色
                formatOutput(tFldValue) {
                    if (tFldValue.indexOf(this.controlSource) !== -1 && this.controlSource !== '') {
                        //return tFldValue.replace(this.controlSource, '<font color="white" style="background-color:#FF9800;">' + this.controlSource + '</font>')
                        return tFldValue.replace(this.controlSource, '<font color="#409EFF">' + this.controlSource + '</font>')
                    } else {
                        return tFldValue
                    }
                },
                // 用户按下 up或down时的事件处理
                move(e, tDirection) {
                    const that = this;
                    //阻止默认行为
                    e.preventDefault();
                    //阻止冒泡
                    e.stopPropagation();
                    if (that.isShow) {
                        const recno = that.currentRow.recno + tDirection;
                        if (recno >= 0 && recno < that.searchResult.length) {
                            that.setCurrentRow(recno, tDirection);
                        }
                    }
                },

                // 在当前过滤结果中查找首个与输入框匹配的行,作为默认高亮显示
                seek() {
                    const that = this;
                    let recno = -1;
                    that.searchResult.some((item, index) => {
                        let found = that.columns.some(column => {
                            return item[column.field] == that.controlSource;
                        })
                        if (found) {
                            recno = index;
                        }
                        return found;
                    });
                    if (recno < 0 && that.searchResult.length > 0) {
                        recno = 0;
                    }
                    return recno;
                },

                // 更新本组件绑定的数据源
                update(tNewValue) {
                    const that = this;
                    if (that.controlSource != tNewValue) {
                        that.controlSource = tNewValue;
                    }
                    /*
                    if (that.value != tNewValue) {
                        that.value = tNewValue;
                    }
                    */
                    that.$emit('input', tNewValue)
                },
                // 离开验证并准备,如有变化,执行update更新本组件绑定的数据源
                valid() {
                    const that = this;
                    let isPass = true;
                    if (that.isShow) {
                        that.hide();
                    }
                    if (that.currentRow && that.currentRow.data && that.controlSource) {
                        let fld = that.returnFld || that.columns[0].field;
                        if (fld in that.currentRow.data && that.currentRow.data[fld] !== that.value) {
                            if (that.beforeUpdate) {
                                isPass = that.beforeUpdate(that.currentRow.data);
                                if (isPass === false) {
                                    that.controlSource = that.value;
                                }
                            }
                            if (isPass || isPass === undefined) {
                                that.update(that.currentRow.data[fld]);
                            }
                        }
                    } else {
                        if (!that.controlSource && that.value) {
                            if (that.beforeUpdate) {
                                isPass = that.beforeUpdate(that.currentRow.data);
                                if (isPass === false) {
                                    that.controlSource = that.value;
                                }
                            }
                            if (isPass || isPass === undefined) {
                                that.update("");
                            }
                        }
                    }
                },
                // 双击pop里grid的行触发的事件,则把该行作为数据返回
                dblclick(row, column, event) {
                    const that = this;
                    that.currentRow.recno = null;
                    that.currentRow.data = row;

                    that.hiding=true;
                    let fld = that.returnFld || that.columns[0].field;
                    if (fld in that.currentRow.data && that.currentRow.data[fld] !== that.controlSource) {
                        that.focus();
                        that.controlSource = that.currentRow.data[fld];
                    }
                    that.valid();
                },
                //判断元素tEl的各边界是否完全在父级容器tContainer所在的区域内,如果是,返回true,否则返回false
                //主要是datagrid的当前行,如果不是在表格内显示,将要滚动它
                isElementInViewport(tContainer, tEl) {
                    var elRect = tEl.getBoundingClientRect(),
                        parRect = tContainer.getBoundingClientRect();
                    return (
                        elRect.top >= parRect.top &&
                        elRect.left >= parRect.left &&
                        elRect.bottom <= parRect.bottom &&
                        elRect.right <= parRect.right
                    );
                }

            },
            template: `
            <div class="combox"
            v-bind:class="[isFocus ? 'combox-focus' : '']"
            >
                
                <div @focusin="isInputFocus=true" @focusout="isInputFocus=false"
                    class="input-with-select el-input el-input-group el-input-group--append el-input--suffix">
                    <input v-bind:readonly="readonly" type="text" autocomplete="off" placeholder="请输入" 
                    v-model="controlSource"                    
                    @keydown.esc="hide()"
                    @keydown.f4="toggle()"
                    @keydown.up.stop="move($event,-1)"
                    @keydown.down.stop="move($event,1)"
                    @keydown.enter="enter"
                    @keydown.space="onSpace"
                    class="el-input__inner">
                    
                    <span v-if="showClear" class="el-input__suffix" style="transform:translateX(-16px);" @click="clear">
                        <span class="el-input__suffix-inner">
                            <i style="padding:0 8px;" class="el-input__icon el-icon-circle-close el-input__clear"></i>
                        </span>
                    </span>
                                        
                    <div v-if="showDropDown" class="el-input-group__append waves-effect" @keydown.space="onSpace" @click="toggle">                                                
                        <el-popover ref="elpopover" 
                        placement='bottom-start' 
                        v-model="isShow"
                        trigger="manual" >
                            <div tabindex="0" style="outline:none;"
                            @keydown.up.stop="move($event,-1)"
                            @keydown.down.stop="move($event,1)"                            
                            @focusin="isPopFocus=true" @focusout="isPopFocus=false">
                                <!--
                                <el-button class="waves-effect" icon="el-icon-refresh" style="float: right; " @click="refresh"></el-button>
                                -->
                                <slot></slot>
                                <el-table ref="dataGrid" max-height="500" :hieght="400" @row-dblclick="dblclick" :data="searchResult" highlight-current-row>
                                    <el-table-column v-for="(column,colno) in columns" 
                                        :key="column.field" 
                                        :prop="column.field" 
                                        :label="column.title" 
                                        :width="column.width">
                                        <template slot-scope="scope">
                                            <span :id="'test'+scope.$index+'_'+colno" v-html="formatOutput(scope.row[column.field])" ></span>
                                        </template>
                                    </el-table-column>        
                                </el-table>
                            </div>        
                            <div slot="reference">
                                <transition name="el-zoom-in-top">
                                <i style="font-size: 16px;padding: 0 8px;"  v-bind:class="[isShow ? 'el-icon-arrow-up' : 'el-icon-arrow-down']"></i>
                                </transition>
                            </div>
                        </el-popover>                        
                    </div>
                </div>
            </div>
            `
        });

 

使用方法:

<div>
                <combox-grid style="left:50px;width: 200px;" v-model="state" :before-update="check" :columns="subjects_columns"
                 :row-source="test1">
                </combox-grid>
            </div>


            <div>

<!--使用v-slot,在表格前插入按钮-->
                <combox-grid style="left:50px;width: 200px;" v-model="state" :columns="columns" :is-sparse="true" :row-source="test2">
                    <template v-slot="">
                        <el-button class="waves-effect" icon="el-icon-refresh" style="float: right; "></el-button>
                        <button>test</button>
                    </template>
                </combox-grid>
            </div>
 

 

 

Logo

前往低代码交流专区

更多推荐